MediaWiki  master
ApiMove.php
Go to the documentation of this file.
1 <?php
28 
33 class ApiMove extends ApiBase {
34 
36 
38  private $movePageFactory;
39 
41  private $repoGroup;
42 
43  public function __construct(
44  ApiMain $mainModule,
45  $moduleName,
46  MovePageFactory $movePageFactory,
47  RepoGroup $repoGroup,
48  WatchlistManager $watchlistManager,
49  UserOptionsLookup $userOptionsLookup
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 = $params['watchlist'] ?? 'preferences';
187  $watchlistExpiry = $this->getExpiryFromParams( $params );
188 
189  // Watch pages
190  $this->setWatch( $watch, $fromTitle, $user, 'watchmoves', $watchlistExpiry );
191  $this->setWatch( $watch, $toTitle, $user, 'watchmoves', $watchlistExpiry );
192 
193  $result->addValue( null, $this->getModuleName(), $r );
194  }
195 
204  public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags = [] ) {
205  $retval = [];
206 
207  $mp = $this->movePageFactory->newMovePage( $fromTitle, $toTitle );
208  $result =
209  $mp->moveSubpagesIfAllowed( $this->getAuthority(), $reason, !$noredirect, $changeTags );
210  if ( !$result->isOK() ) {
211  // This means the whole thing failed
212  return [ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $result ) ];
213  }
214 
215  // At least some pages could be moved
216  // Report each of them separately
217  foreach ( $result->getValue() as $oldTitle => $status ) {
219  $r = [ 'from' => $oldTitle ];
220  if ( $status->isOK() ) {
221  $r['to'] = $status->getValue();
222  } else {
223  $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
224  }
225  $retval[] = $r;
226  }
227 
228  return $retval;
229  }
230 
231  public function mustBePosted() {
232  return true;
233  }
234 
235  public function isWriteMode() {
236  return true;
237  }
238 
239  public function getAllowedParams() {
240  $params = [
241  'from' => null,
242  'fromid' => [
243  ParamValidator::PARAM_TYPE => 'integer'
244  ],
245  'to' => [
246  ParamValidator::PARAM_TYPE => 'string',
247  ParamValidator::PARAM_REQUIRED => true
248  ],
249  'reason' => '',
250  'movetalk' => false,
251  'movesubpages' => false,
252  'noredirect' => false,
253  ];
254 
255  // Params appear in the docs in the order they are defined,
256  // which is why this is here and not at the bottom.
257  $params += $this->getWatchlistParams();
258 
259  return $params + [
260  'ignorewarnings' => false,
261  'tags' => [
262  ParamValidator::PARAM_TYPE => 'tags',
263  ParamValidator::PARAM_ISMULTI => true,
264  ],
265  ];
266  }
267 
268  public function needsToken() {
269  return 'csrf';
270  }
271 
272  protected function getExamplesMessages() {
273  return [
274  'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
275  'reason=Misspelled%20title&movetalk=&noredirect='
276  => 'apihelp-move-example-move',
277  ];
278  }
279 
280  public function getHelpUrls() {
281  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Move';
282  }
283 }
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.
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:57
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition: ApiBase.php:1455
getErrorFormatter()
Definition: ApiBase.php:641
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:904
getResult()
Get the result object.
Definition: ApiBase.php:630
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:766
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:499
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1516
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1300
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:55
API Module to move pages.
Definition: ApiMove.php:33
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiMove.php:268
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiMove.php:235
__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:204
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:231
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiMove.php:280
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiMove.php:272
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiMove.php:239
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:521
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:373
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