MediaWiki  master
ApiMove.php
Go to the documentation of this file.
1 <?php
28 
33 class ApiMove extends ApiBase {
34 
36 
39 
41  private $repoGroup;
42 
43  public function __construct(
44  ApiMain $mainModule,
45  $moduleName,
50  ) {
51  parent::__construct( $mainModule, $moduleName );
52 
53  $this->movePageFactory = $movePageFactory;
54  $this->repoGroup = $repoGroup;
55 
56  // Variables needed in ApiWatchlistTrait trait
57  $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
58  $this->watchlistMaxDuration =
59  $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
60  $this->watchlistManager = $watchlistManager;
61  $this->userOptionsLookup = $userOptionsLookup;
62  }
63 
64  public function execute() {
66 
67  $user = $this->getUser();
68  $params = $this->extractRequestParams();
69 
70  $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
71 
72  if ( isset( $params['from'] ) ) {
73  $fromTitle = Title::newFromText( $params['from'] );
74  if ( !$fromTitle || $fromTitle->isExternal() ) {
75  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['from'] ) ] );
76  }
77  } elseif ( isset( $params['fromid'] ) ) {
78  $fromTitle = Title::newFromID( $params['fromid'] );
79  if ( !$fromTitle ) {
80  $this->dieWithError( [ 'apierror-nosuchpageid', $params['fromid'] ] );
81  }
82  } else {
83  throw new LogicException( 'Unreachable due to requireOnlyOneParameter' );
84  }
85 
86  if ( !$fromTitle->exists() ) {
87  $this->dieWithError( 'apierror-missingtitle' );
88  }
89  $fromTalk = $fromTitle->getTalkPage();
90 
91  $toTitle = Title::newFromText( $params['to'] );
92  if ( !$toTitle || $toTitle->isExternal() ) {
93  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
94  }
95  $toTalk = $toTitle->getTalkPageIfDefined();
96 
97  if ( $toTitle->getNamespace() === NS_FILE
98  && !$this->repoGroup->getLocalRepo()->findFile( $toTitle )
99  && $this->repoGroup->findFile( $toTitle )
100  ) {
101  if ( !$params['ignorewarnings'] &&
102  $this->getAuthority()->isAllowed( 'reupload-shared' ) ) {
103  $this->dieWithError( 'apierror-fileexists-sharedrepo-perm' );
104  } elseif ( !$this->getAuthority()->isAllowed( 'reupload-shared' ) ) {
105  $this->dieWithError( 'apierror-cantoverwrite-sharedfile' );
106  }
107  }
108 
109  // Rate limit
110  if ( $user->pingLimiter( 'move' ) ) {
111  $this->dieWithError( 'apierror-ratelimited' );
112  }
113 
114  // Move the page
115  $toTitleExists = $toTitle->exists();
116  $mp = $this->movePageFactory->newMovePage( $fromTitle, $toTitle );
117  $status = $mp->moveIfAllowed(
118  $this->getAuthority(),
119  $params['reason'],
120  !$params['noredirect'],
121  $params['tags'] ?: []
122  );
123  if ( !$status->isOK() ) {
124  $this->dieStatus( $status );
125  }
126 
127  $r = [
128  'from' => $fromTitle->getPrefixedText(),
129  'to' => $toTitle->getPrefixedText(),
130  'reason' => $params['reason']
131  ];
132 
133  // NOTE: we assume that if the old title exists, it's because it was re-created as
134  // a redirect to the new title. This is not safe, but what we did before was
135  // even worse: we just determined whether a redirect should have been created,
136  // and reported that it was created if it should have, without any checks.
137  $r['redirectcreated'] = $fromTitle->exists();
138 
139  $r['moveoverredirect'] = $toTitleExists;
140 
141  // Move the talk page
142  if ( $params['movetalk'] && $toTalk && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
143  $toTalkExists = $toTalk->exists();
144  $mp = $this->movePageFactory->newMovePage( $fromTalk, $toTalk );
145  $status = $mp->moveIfAllowed(
146  $this->getAuthority(),
147  $params['reason'],
148  !$params['noredirect'],
149  $params['tags'] ?: []
150  );
151  if ( $status->isOK() ) {
152  $r['talkfrom'] = $fromTalk->getPrefixedText();
153  $r['talkto'] = $toTalk->getPrefixedText();
154  $r['talkmoveoverredirect'] = $toTalkExists;
155  } else {
156  // We're not going to dieWithError() on failure, since we already changed something
157  $r['talkmove-errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
158  }
159  }
160 
161  $result = $this->getResult();
162 
163  // Move subpages
164  if ( $params['movesubpages'] ) {
165  $r['subpages'] = $this->moveSubpages(
166  $fromTitle,
167  $toTitle,
168  $params['reason'],
169  $params['noredirect'],
170  $params['tags'] ?: []
171  );
172  ApiResult::setIndexedTagName( $r['subpages'], 'subpage' );
173 
174  if ( $params['movetalk'] && $toTalk ) {
175  $r['subpages-talk'] = $this->moveSubpages(
176  $fromTalk,
177  $toTalk,
178  $params['reason'],
179  $params['noredirect'],
180  $params['tags'] ?: []
181  );
182  ApiResult::setIndexedTagName( $r['subpages-talk'], 'subpage' );
183  }
184  }
185 
186  $watch = 'preferences';
187  if ( isset( $params['watchlist'] ) ) {
188  $watch = $params['watchlist'];
189  }
190  $watchlistExpiry = $this->getExpiryFromParams( $params );
191 
192  // Watch pages
193  $this->setWatch( $watch, $fromTitle, $user, 'watchmoves', $watchlistExpiry );
194  $this->setWatch( $watch, $toTitle, $user, 'watchmoves', $watchlistExpiry );
195 
196  $result->addValue( null, $this->getModuleName(), $r );
197  }
198 
207  public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags = [] ) {
208  $retval = [];
209 
210  $mp = $this->movePageFactory->newMovePage( $fromTitle, $toTitle );
211  $result =
212  $mp->moveSubpagesIfAllowed( $this->getAuthority(), $reason, !$noredirect, $changeTags );
213  if ( !$result->isOK() ) {
214  // This means the whole thing failed
215  return [ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $result ) ];
216  }
217 
218  // At least some pages could be moved
219  // Report each of them separately
220  foreach ( $result->getValue() as $oldTitle => $status ) {
222  $r = [ 'from' => $oldTitle ];
223  if ( $status->isOK() ) {
224  $r['to'] = $status->getValue();
225  } else {
226  $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
227  }
228  $retval[] = $r;
229  }
230 
231  return $retval;
232  }
233 
234  public function mustBePosted() {
235  return true;
236  }
237 
238  public function isWriteMode() {
239  return true;
240  }
241 
242  public function getAllowedParams() {
243  $params = [
244  'from' => null,
245  'fromid' => [
246  ParamValidator::PARAM_TYPE => 'integer'
247  ],
248  'to' => [
249  ParamValidator::PARAM_TYPE => 'string',
250  ParamValidator::PARAM_REQUIRED => true
251  ],
252  'reason' => '',
253  'movetalk' => false,
254  'movesubpages' => false,
255  'noredirect' => false,
256  ];
257 
258  // Params appear in the docs in the order they are defined,
259  // which is why this is here and not at the bottom.
260  $params += $this->getWatchlistParams();
261 
262  return $params + [
263  'ignorewarnings' => false,
264  'tags' => [
265  ParamValidator::PARAM_TYPE => 'tags',
266  ParamValidator::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 }
getExpiryFromParams(array $params)
Get formatted expiry from the given parameters, or null if no expiry was provided.
setWatch(string $watch, Title $title, User $user, ?string $userOption=null, ?string $expiry=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
getWatchlistParams(array $watchOptions=[])
Get additional allow params specific to watchlisting.
WatchlistManager $watchlistManager
UserOptionsLookup $userOptionsLookup
const NS_FILE
Definition: Defines.php:70
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:56
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1458
getErrorFormatter()
Definition: ApiBase.php:640
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:903
getResult()
Get the result object.
Definition: ApiBase.php:629
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:765
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:498
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1521
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1303
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:52
API Module to move pages.
Definition: ApiMove.php:33
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiMove.php:271
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiMove.php:238
RepoGroup $repoGroup
Definition: ApiMove.php:41
__construct(ApiMain $mainModule, $moduleName, MovePageFactory $movePageFactory, RepoGroup $repoGroup, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup)
Definition: ApiMove.php:43
moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags=[])
Definition: ApiMove.php:207
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiMove.php:64
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiMove.php:234
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiMove.php:283
MovePageFactory $movePageFactory
Definition: ApiMove.php:38
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiMove.php:275
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiMove.php:242
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
A class containing constants representing the names of configuration variables.
Provides access to user options.
Prioritized list of file repositories.
Definition: RepoGroup.php:29
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:518
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:370
Service for formatting and validating API parameters.
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
Service for page rename actions.
return true
Definition: router.php:90