MediaWiki  master
ApiMove.php
Go to the documentation of this file.
1 <?php
24 
29 class ApiMove extends ApiBase {
30 
32 
33  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
34  parent::__construct( $mainModule, $moduleName, $modulePrefix );
35 
36  $this->watchlistExpiryEnabled = $this->getConfig()->get( 'WatchlistExpiry' );
37  $this->watchlistMaxDuration = $this->getConfig()->get( 'WatchlistExpiryMaxDuration' );
38  }
39 
40  public function execute() {
42 
43  $user = $this->getUser();
44  $params = $this->extractRequestParams();
45 
46  $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
47 
48  if ( isset( $params['from'] ) ) {
49  $fromTitle = Title::newFromText( $params['from'] );
50  if ( !$fromTitle || $fromTitle->isExternal() ) {
51  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['from'] ) ] );
52  }
53  } elseif ( isset( $params['fromid'] ) ) {
54  $fromTitle = Title::newFromID( $params['fromid'] );
55  if ( !$fromTitle ) {
56  $this->dieWithError( [ 'apierror-nosuchpageid', $params['fromid'] ] );
57  }
58  }
59 
60  if ( !$fromTitle->exists() ) {
61  $this->dieWithError( 'apierror-missingtitle' );
62  }
63  $fromTalk = $fromTitle->getTalkPage();
64 
65  $toTitle = Title::newFromText( $params['to'] );
66  if ( !$toTitle || $toTitle->isExternal() ) {
67  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
68  }
69  $toTalk = $toTitle->getTalkPageIfDefined();
70 
71  $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
72  if ( $toTitle->getNamespace() === NS_FILE
73  && !$repoGroup->getLocalRepo()->findFile( $toTitle )
74  && $repoGroup->findFile( $toTitle )
75  ) {
76  if ( !$params['ignorewarnings'] &&
77  $this->getPermissionManager()->userHasRight( $user, 'reupload-shared' ) ) {
78  $this->dieWithError( 'apierror-fileexists-sharedrepo-perm' );
79  } elseif ( !$this->getPermissionManager()->userHasRight( $user, 'reupload-shared' ) ) {
80  $this->dieWithError( 'apierror-cantoverwrite-sharedfile' );
81  }
82  }
83 
84  // Rate limit
85  if ( $user->pingLimiter( 'move' ) ) {
86  $this->dieWithError( 'apierror-ratelimited' );
87  }
88 
89  // Check if the user is allowed to add the specified changetags
90  if ( $params['tags'] ) {
91  $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
92  if ( !$ableToTag->isOK() ) {
93  $this->dieStatus( $ableToTag );
94  }
95  }
96 
97  // Move the page
98  $toTitleExists = $toTitle->exists();
99  $status = $this->movePage( $fromTitle, $toTitle, $params['reason'], !$params['noredirect'],
100  $params['tags'] ?: [] );
101  if ( !$status->isOK() ) {
102  $user->spreadAnyEditBlock();
103  $this->dieStatus( $status );
104  }
105 
106  $r = [
107  'from' => $fromTitle->getPrefixedText(),
108  'to' => $toTitle->getPrefixedText(),
109  'reason' => $params['reason']
110  ];
111 
112  // NOTE: we assume that if the old title exists, it's because it was re-created as
113  // a redirect to the new title. This is not safe, but what we did before was
114  // even worse: we just determined whether a redirect should have been created,
115  // and reported that it was created if it should have, without any checks.
116  $r['redirectcreated'] = $fromTitle->exists();
117 
118  $r['moveoverredirect'] = $toTitleExists;
119 
120  // Move the talk page
121  if ( $params['movetalk'] && $toTalk && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
122  $toTalkExists = $toTalk->exists();
123  $status = $this->movePage(
124  $fromTalk,
125  $toTalk,
126  $params['reason'],
127  !$params['noredirect'],
128  $params['tags'] ?: []
129  );
130  if ( $status->isOK() ) {
131  $r['talkfrom'] = $fromTalk->getPrefixedText();
132  $r['talkto'] = $toTalk->getPrefixedText();
133  $r['talkmoveoverredirect'] = $toTalkExists;
134  } else {
135  // We're not going to dieWithError() on failure, since we already changed something
136  $r['talkmove-errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
137  }
138  }
139 
140  $result = $this->getResult();
141 
142  // Move subpages
143  if ( $params['movesubpages'] ) {
144  $r['subpages'] = $this->moveSubpages(
145  $fromTitle,
146  $toTitle,
147  $params['reason'],
148  $params['noredirect'],
149  $params['tags'] ?: []
150  );
151  ApiResult::setIndexedTagName( $r['subpages'], 'subpage' );
152 
153  if ( $params['movetalk'] ) {
154  $r['subpages-talk'] = $this->moveSubpages(
155  $fromTalk,
156  $toTalk,
157  $params['reason'],
158  $params['noredirect'],
159  $params['tags'] ?: []
160  );
161  ApiResult::setIndexedTagName( $r['subpages-talk'], 'subpage' );
162  }
163  }
164 
165  $watch = 'preferences';
166  if ( isset( $params['watchlist'] ) ) {
167  $watch = $params['watchlist'];
168  }
169  $watchlistExpiry = $this->getExpiryFromParams( $params );
170 
171  // Watch pages
172  $this->setWatch( $watch, $fromTitle, 'watchmoves', $watchlistExpiry );
173  $this->setWatch( $watch, $toTitle, 'watchmoves', $watchlistExpiry );
174 
175  $result->addValue( null, $this->getModuleName(), $r );
176  }
177 
186  protected function movePage( Title $from, Title $to, $reason, $createRedirect, $changeTags ) {
187  $mp = MediaWikiServices::getInstance()->getMovePageFactory()->newMovePage( $from, $to );
188  $valid = $mp->isValidMove();
189  if ( !$valid->isOK() ) {
190  return $valid;
191  }
192 
193  $user = $this->getUser();
194  $permStatus = $mp->checkPermissions( $user, $reason );
195  if ( !$permStatus->isOK() ) {
196  return $permStatus;
197  }
198 
199  // Check suppressredirect permission
200  if ( !$this->getPermissionManager()->userHasRight( $user, 'suppressredirect' ) ) {
201  $createRedirect = true;
202  }
203 
204  return $mp->move( $user, $reason, $createRedirect, $changeTags );
205  }
206 
215  public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags = [] ) {
216  $retval = [];
217 
218  $mp = new MovePage( $fromTitle, $toTitle );
219  $result =
220  $mp->moveSubpagesIfAllowed( $this->getUser(), $reason, !$noredirect, $changeTags );
221  if ( !$result->isOK() ) {
222  // This means the whole thing failed
223  return [ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $result ) ];
224  }
225 
226  // At least some pages could be moved
227  // Report each of them separately
228  foreach ( $result->getValue() as $oldTitle => $status ) {
230  $r = [ 'from' => $oldTitle ];
231  if ( $status->isOK() ) {
232  $r['to'] = $status->getValue();
233  } else {
234  $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
235  }
236  $retval[] = $r;
237  }
238 
239  return $retval;
240  }
241 
242  public function mustBePosted() {
243  return true;
244  }
245 
246  public function isWriteMode() {
247  return true;
248  }
249 
250  public function getAllowedParams() {
251  $params = [
252  'from' => null,
253  'fromid' => [
254  ApiBase::PARAM_TYPE => 'integer'
255  ],
256  'to' => [
257  ApiBase::PARAM_TYPE => 'string',
259  ],
260  'reason' => '',
261  'movetalk' => false,
262  'movesubpages' => false,
263  'noredirect' => false,
264  ];
265 
266  // Params appear in the docs in the order they are defined,
267  // which is why this is here and not at the bottom.
268  $params += $this->getWatchlistParams();
269 
270  return $params + [
271  'ignorewarnings' => false,
272  'tags' => [
273  ApiBase::PARAM_TYPE => 'tags',
274  ApiBase::PARAM_ISMULTI => true,
275  ],
276  ];
277  }
278 
279  public function needsToken() {
280  return 'csrf';
281  }
282 
283  protected function getExamplesMessages() {
284  return [
285  'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
286  'reason=Misspelled%20title&movetalk=&noredirect='
287  => 'apihelp-move-example-move',
288  ];
289  }
290 
291  public function getHelpUrls() {
292  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Move';
293  }
294 }
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:47
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:67
ApiMove\__construct
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Stable to call.
Definition: ApiMove.php:33
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:329
getExpiryFromParams
getExpiryFromParams(array $params)
Get formatted expiry from the given parameters, or null if no expiry was provided.
Definition: ApiWatchlistTrait.php:132
ApiBase\PARAM_REQUIRED
const PARAM_REQUIRED
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:77
ApiMove\needsToken
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiMove.php:279
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:154
ApiMove\moveSubpages
moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags=[])
Definition: ApiMove.php:215
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1382
true
return true
Definition: router.php:90
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:71
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:565
NS_FILE
const NS_FILE
Definition: Defines.php:75
ContextSource\getUser
getUser()
Stable to override.
Definition: ContextSource.php:131
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:52
ApiMove\movePage
movePage(Title $from, Title $to, $reason, $createRedirect, $changeTags)
Definition: ApiMove.php:186
ApiMove\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiMove.php:250
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
StatusValue\getValue
getValue()
Definition: StatusValue.php:138
MovePage
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:42
ApiMove\isWriteMode
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiMove.php:246
ApiMove\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiMove.php:283
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:717
ApiMove
API Module to move pages.
Definition: ApiMove.php:29
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
ApiBase\requireOnlyOneParameter
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:854
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:637
ApiBase\useTransactionalTimeLimit
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1239
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1487
ApiMove\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiMove.php:291
ApiWatchlistTrait
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
Definition: ApiWatchlistTrait.php:17
ChangeTags\canAddTagsAccompanyingChange
static canAddTagsAccompanyingChange(array $tags, User $user=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Definition: ChangeTags.php:544
Title
Represents a title within MediaWiki.
Definition: Title.php:42
getWatchlistParams
getWatchlistParams(array $watchOptions=[])
Get additional allow params specific to watchlisting.
Definition: ApiWatchlistTrait.php:35
ApiMove\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiMove.php:40
setWatch
setWatch(string $watch, Title $titleObj, ?string $userOption=null, ?string $expiry=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiWatchlistTrait.php:71
ApiBase\dieStatus
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1440
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:444
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:70
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:473
ApiMove\mustBePosted
mustBePosted()
Indicates whether this module must be called with a POST request Stable to override.
Definition: ApiMove.php:242
ApiBase\getErrorFormatter
getErrorFormatter()
Get the error formatter Stable to override.
Definition: ApiBase.php:580