MediaWiki  master
ApiMove.php
Go to the documentation of this file.
1 <?php
24 
29 class ApiMove extends ApiBase {
30 
31  public function execute() {
33 
34  $user = $this->getUser();
35  $params = $this->extractRequestParams();
36 
37  $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
38 
39  if ( isset( $params['from'] ) ) {
40  $fromTitle = Title::newFromText( $params['from'] );
41  if ( !$fromTitle || $fromTitle->isExternal() ) {
42  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['from'] ) ] );
43  }
44  } elseif ( isset( $params['fromid'] ) ) {
45  $fromTitle = Title::newFromID( $params['fromid'] );
46  if ( !$fromTitle ) {
47  $this->dieWithError( [ 'apierror-nosuchpageid', $params['fromid'] ] );
48  }
49  }
50 
51  if ( !$fromTitle->exists() ) {
52  $this->dieWithError( 'apierror-missingtitle' );
53  }
54  $fromTalk = $fromTitle->getTalkPage();
55 
56  $toTitle = Title::newFromText( $params['to'] );
57  if ( !$toTitle || $toTitle->isExternal() ) {
58  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
59  }
60  $toTalk = $toTitle->getTalkPageIfDefined();
61 
62  $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
63  if ( $toTitle->getNamespace() == NS_FILE
64  && !$repoGroup->getLocalRepo()->findFile( $toTitle )
65  && $repoGroup->findFile( $toTitle )
66  ) {
67  if ( !$params['ignorewarnings'] &&
68  $this->getPermissionManager()->userHasRight( $user, 'reupload-shared' ) ) {
69  $this->dieWithError( 'apierror-fileexists-sharedrepo-perm' );
70  } elseif ( !$this->getPermissionManager()->userHasRight( $user, 'reupload-shared' ) ) {
71  $this->dieWithError( 'apierror-cantoverwrite-sharedfile' );
72  }
73  }
74 
75  // Rate limit
76  if ( $user->pingLimiter( 'move' ) ) {
77  $this->dieWithError( 'apierror-ratelimited' );
78  }
79 
80  // Check if the user is allowed to add the specified changetags
81  if ( $params['tags'] ) {
82  $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
83  if ( !$ableToTag->isOK() ) {
84  $this->dieStatus( $ableToTag );
85  }
86  }
87 
88  // Move the page
89  $toTitleExists = $toTitle->exists();
90  $status = $this->movePage( $fromTitle, $toTitle, $params['reason'], !$params['noredirect'],
91  $params['tags'] ?: [] );
92  if ( !$status->isOK() ) {
93  $user->spreadAnyEditBlock();
94  $this->dieStatus( $status );
95  }
96 
97  $r = [
98  'from' => $fromTitle->getPrefixedText(),
99  'to' => $toTitle->getPrefixedText(),
100  'reason' => $params['reason']
101  ];
102 
103  // NOTE: we assume that if the old title exists, it's because it was re-created as
104  // a redirect to the new title. This is not safe, but what we did before was
105  // even worse: we just determined whether a redirect should have been created,
106  // and reported that it was created if it should have, without any checks.
107  $r['redirectcreated'] = $fromTitle->exists();
108 
109  $r['moveoverredirect'] = $toTitleExists;
110 
111  // Move the talk page
112  if ( $params['movetalk'] && $toTalk && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
113  $toTalkExists = $toTalk->exists();
114  $status = $this->movePage(
115  $fromTalk,
116  $toTalk,
117  $params['reason'],
118  !$params['noredirect'],
119  $params['tags'] ?: []
120  );
121  if ( $status->isOK() ) {
122  $r['talkfrom'] = $fromTalk->getPrefixedText();
123  $r['talkto'] = $toTalk->getPrefixedText();
124  $r['talkmoveoverredirect'] = $toTalkExists;
125  } else {
126  // We're not going to dieWithError() on failure, since we already changed something
127  $r['talkmove-errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
128  }
129  }
130 
131  $result = $this->getResult();
132 
133  // Move subpages
134  if ( $params['movesubpages'] ) {
135  $r['subpages'] = $this->moveSubpages(
136  $fromTitle,
137  $toTitle,
138  $params['reason'],
139  $params['noredirect'],
140  $params['tags'] ?: []
141  );
142  ApiResult::setIndexedTagName( $r['subpages'], 'subpage' );
143 
144  if ( $params['movetalk'] ) {
145  $r['subpages-talk'] = $this->moveSubpages(
146  $fromTalk,
147  $toTalk,
148  $params['reason'],
149  $params['noredirect'],
150  $params['tags'] ?: []
151  );
152  ApiResult::setIndexedTagName( $r['subpages-talk'], 'subpage' );
153  }
154  }
155 
156  $watch = 'preferences';
157  if ( isset( $params['watchlist'] ) ) {
158  $watch = $params['watchlist'];
159  }
160 
161  // Watch pages
162  $this->setWatch( $watch, $fromTitle, 'watchmoves' );
163  $this->setWatch( $watch, $toTitle, 'watchmoves' );
164 
165  $result->addValue( null, $this->getModuleName(), $r );
166  }
167 
176  protected function movePage( Title $from, Title $to, $reason, $createRedirect, $changeTags ) {
177  $mp = MediaWikiServices::getInstance()->getMovePageFactory()->newMovePage( $from, $to );
178  $valid = $mp->isValidMove();
179  if ( !$valid->isOK() ) {
180  return $valid;
181  }
182 
183  $user = $this->getUser();
184  $permStatus = $mp->checkPermissions( $user, $reason );
185  if ( !$permStatus->isOK() ) {
186  return $permStatus;
187  }
188 
189  // Check suppressredirect permission
190  if ( !$this->getPermissionManager()->userHasRight( $user, 'suppressredirect' ) ) {
191  $createRedirect = true;
192  }
193 
194  return $mp->move( $user, $reason, $createRedirect, $changeTags );
195  }
196 
205  public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags = [] ) {
206  $retval = [];
207 
208  $mp = new MovePage( $fromTitle, $toTitle );
209  $result =
210  $mp->moveSubpagesIfAllowed( $this->getUser(), $reason, !$noredirect, $changeTags );
211  if ( !$result->isOK() ) {
212  // This means the whole thing failed
213  return [ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $result ) ];
214  }
215 
216  // At least some pages could be moved
217  // Report each of them separately
218  foreach ( $result->getValue() as $oldTitle => $status ) {
220  $r = [ 'from' => $oldTitle ];
221  if ( $status->isOK() ) {
222  $r['to'] = $status->getValue();
223  } else {
224  $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
225  }
226  $retval[] = $r;
227  }
228 
229  return $retval;
230  }
231 
232  public function mustBePosted() {
233  return true;
234  }
235 
236  public function isWriteMode() {
237  return true;
238  }
239 
240  public function getAllowedParams() {
241  return [
242  'from' => null,
243  'fromid' => [
244  ApiBase::PARAM_TYPE => 'integer'
245  ],
246  'to' => [
247  ApiBase::PARAM_TYPE => 'string',
249  ],
250  'reason' => '',
251  'movetalk' => false,
252  'movesubpages' => false,
253  'noredirect' => false,
254  'watchlist' => [
255  ApiBase::PARAM_DFLT => 'preferences',
257  'watch',
258  'unwatch',
259  'preferences',
260  'nochange'
261  ],
262  ],
263  'ignorewarnings' => false,
264  'tags' => [
265  ApiBase::PARAM_TYPE => 'tags',
266  ApiBase::PARAM_ISMULTI => true,
267  ],
268  ];
269  }
270 
271  public function needsToken() {
272  return 'csrf';
273  }
274 
275  protected function getExamplesMessages() {
276  return [
277  'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
278  'reason=Misspelled%20title&movetalk=&noredirect='
279  => 'apihelp-move-example-move',
280  ];
281  }
282 
283  public function getHelpUrls() {
284  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Move';
285  }
286 }
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:332
ApiBase\PARAM_REQUIRED
const PARAM_REQUIRED
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:66
ApiMove\needsToken
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiMove.php:271
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:144
ApiMove\moveSubpages
moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags=[])
Definition: ApiMove.php:205
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1379
true
return true
Definition: router.php:90
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:60
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:538
NS_FILE
const NS_FILE
Definition: Defines.php:75
ContextSource\getUser
getUser()
Definition: ContextSource.php:120
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:48
ApiMove\movePage
movePage(Title $from, Title $to, $reason, $createRedirect, $changeTags)
Definition: ApiMove.php:176
ApiMove\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiMove.php:240
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:42
StatusValue\getValue
getValue()
Definition: StatusValue.php:137
MovePage
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:40
ApiMove\isWriteMode
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiMove.php:236
ApiMove\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiMove.php:275
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:659
ApiMove
API Module to move pages.
Definition: ApiMove.php:29
ApiBase\setWatch
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiBase.php:1115
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:796
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:608
ApiBase\useTransactionalTimeLimit
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1236
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1485
ApiMove\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiMove.php:283
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:528
Title
Represents a title within MediaWiki.
Definition: Title.php:42
ApiMove\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiMove.php:31
ApiBase\PARAM_DFLT
const PARAM_DFLT
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:58
ApiBase\dieStatus
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1437
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:418
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:59
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:476
ApiMove\mustBePosted
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiMove.php:232
ApiBase\getErrorFormatter
getErrorFormatter()
Get the error formatter.
Definition: ApiBase.php:552