MediaWiki  master
ApiMove.php
Go to the documentation of this file.
1 <?php
30 
35 class ApiMove extends ApiBase {
36 
38 
39  private MovePageFactory $movePageFactory;
40  private RepoGroup $repoGroup;
41 
42  public function __construct(
43  ApiMain $mainModule,
44  $moduleName,
45  MovePageFactory $movePageFactory,
46  RepoGroup $repoGroup,
47  WatchlistManager $watchlistManager,
48  UserOptionsLookup $userOptionsLookup
49  ) {
50  parent::__construct( $mainModule, $moduleName );
51 
52  $this->movePageFactory = $movePageFactory;
53  $this->repoGroup = $repoGroup;
54 
55  // Variables needed in ApiWatchlistTrait trait
56  $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
57  $this->watchlistMaxDuration =
58  $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
59  $this->watchlistManager = $watchlistManager;
60  $this->userOptionsLookup = $userOptionsLookup;
61  }
62 
63  public function execute() {
65 
66  $user = $this->getUser();
67  $params = $this->extractRequestParams();
68 
69  $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
70 
71  if ( isset( $params['from'] ) ) {
72  $fromTitle = Title::newFromText( $params['from'] );
73  if ( !$fromTitle || $fromTitle->isExternal() ) {
74  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['from'] ) ] );
75  }
76  } elseif ( isset( $params['fromid'] ) ) {
77  $fromTitle = Title::newFromID( $params['fromid'] );
78  if ( !$fromTitle ) {
79  $this->dieWithError( [ 'apierror-nosuchpageid', $params['fromid'] ] );
80  }
81  } else {
82  throw new LogicException( 'Unreachable due to requireOnlyOneParameter' );
83  }
84 
85  if ( !$fromTitle->exists() ) {
86  $this->dieWithError( 'apierror-missingtitle' );
87  }
88  $fromTalk = $fromTitle->getTalkPage();
89 
90  $toTitle = Title::newFromText( $params['to'] );
91  if ( !$toTitle || $toTitle->isExternal() ) {
92  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
93  }
94  $toTalk = $toTitle->getTalkPageIfDefined();
95 
96  if ( $toTitle->getNamespace() === NS_FILE
97  && !$this->repoGroup->getLocalRepo()->findFile( $toTitle )
98  && $this->repoGroup->findFile( $toTitle )
99  ) {
100  if ( !$params['ignorewarnings'] &&
101  $this->getAuthority()->isAllowed( 'reupload-shared' ) ) {
102  $this->dieWithError( 'apierror-fileexists-sharedrepo-perm' );
103  } elseif ( !$this->getAuthority()->isAllowed( 'reupload-shared' ) ) {
104  $this->dieWithError( 'apierror-cantoverwrite-sharedfile' );
105  }
106  }
107 
108  // Move the page
109  $toTitleExists = $toTitle->exists();
110  $mp = $this->movePageFactory->newMovePage( $fromTitle, $toTitle );
111  $status = $mp->moveIfAllowed(
112  $this->getAuthority(),
113  $params['reason'],
114  !$params['noredirect'],
115  $params['tags'] ?: []
116  );
117  if ( !$status->isOK() ) {
118  $this->dieStatus( $status );
119  }
120 
121  $r = [
122  'from' => $fromTitle->getPrefixedText(),
123  'to' => $toTitle->getPrefixedText(),
124  'reason' => $params['reason']
125  ];
126 
127  // NOTE: we assume that if the old title exists, it's because it was re-created as
128  // a redirect to the new title. This is not safe, but what we did before was
129  // even worse: we just determined whether a redirect should have been created,
130  // and reported that it was created if it should have, without any checks.
131  $r['redirectcreated'] = $fromTitle->exists();
132 
133  $r['moveoverredirect'] = $toTitleExists;
134 
135  // Move the talk page
136  if ( $params['movetalk'] && $toTalk && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
137  $toTalkExists = $toTalk->exists();
138  $mp = $this->movePageFactory->newMovePage( $fromTalk, $toTalk );
139  $status = $mp->moveIfAllowed(
140  $this->getAuthority(),
141  $params['reason'],
142  !$params['noredirect'],
143  $params['tags'] ?: []
144  );
145  if ( $status->isOK() ) {
146  $r['talkfrom'] = $fromTalk->getPrefixedText();
147  $r['talkto'] = $toTalk->getPrefixedText();
148  $r['talkmoveoverredirect'] = $toTalkExists;
149  } else {
150  // We're not going to dieWithError() on failure, since we already changed something
151  $r['talkmove-errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
152  }
153  }
154 
155  $result = $this->getResult();
156 
157  // Move subpages
158  if ( $params['movesubpages'] ) {
159  $r['subpages'] = $this->moveSubpages(
160  $fromTitle,
161  $toTitle,
162  $params['reason'],
163  $params['noredirect'],
164  $params['tags'] ?: []
165  );
166  ApiResult::setIndexedTagName( $r['subpages'], 'subpage' );
167 
168  if ( $params['movetalk'] && $toTalk ) {
169  $r['subpages-talk'] = $this->moveSubpages(
170  $fromTalk,
171  $toTalk,
172  $params['reason'],
173  $params['noredirect'],
174  $params['tags'] ?: []
175  );
176  ApiResult::setIndexedTagName( $r['subpages-talk'], 'subpage' );
177  }
178  }
179 
180  $watch = $params['watchlist'] ?? 'preferences';
181  $watchlistExpiry = $this->getExpiryFromParams( $params );
182 
183  // Watch pages
184  $this->setWatch( $watch, $fromTitle, $user, 'watchmoves', $watchlistExpiry );
185  $this->setWatch( $watch, $toTitle, $user, 'watchmoves', $watchlistExpiry );
186 
187  $result->addValue( null, $this->getModuleName(), $r );
188  }
189 
198  public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags = [] ) {
199  $retval = [];
200 
201  $mp = $this->movePageFactory->newMovePage( $fromTitle, $toTitle );
202  $result =
203  $mp->moveSubpagesIfAllowed( $this->getAuthority(), $reason, !$noredirect, $changeTags );
204  if ( !$result->isOK() ) {
205  // This means the whole thing failed
206  return [ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $result ) ];
207  }
208 
209  // At least some pages could be moved
210  // Report each of them separately
211  foreach ( $result->getValue() as $oldTitle => $status ) {
213  $r = [ 'from' => $oldTitle ];
214  if ( $status->isOK() ) {
215  $r['to'] = $status->getValue();
216  } else {
217  $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
218  }
219  $retval[] = $r;
220  }
221 
222  return $retval;
223  }
224 
225  public function mustBePosted() {
226  return true;
227  }
228 
229  public function isWriteMode() {
230  return true;
231  }
232 
233  public function getAllowedParams() {
234  $params = [
235  'from' => null,
236  'fromid' => [
237  ParamValidator::PARAM_TYPE => 'integer'
238  ],
239  'to' => [
240  ParamValidator::PARAM_TYPE => 'string',
241  ParamValidator::PARAM_REQUIRED => true
242  ],
243  'reason' => '',
244  'movetalk' => false,
245  'movesubpages' => false,
246  'noredirect' => false,
247  ];
248 
249  // Params appear in the docs in the order they are defined,
250  // which is why this is here and not at the bottom.
251  $params += $this->getWatchlistParams();
252 
253  return $params + [
254  'ignorewarnings' => false,
255  'tags' => [
256  ParamValidator::PARAM_TYPE => 'tags',
257  ParamValidator::PARAM_ISMULTI => true,
258  ],
259  ];
260  }
261 
262  public function needsToken() {
263  return 'csrf';
264  }
265 
266  protected function getExamplesMessages() {
267  return [
268  'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
269  'reason=Misspelled%20title&movetalk=&noredirect='
270  => 'apihelp-move-example-move',
271  ];
272  }
273 
274  public function getHelpUrls() {
275  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Move';
276  }
277 }
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:63
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition: ApiBase.php:1516
getErrorFormatter()
Definition: ApiBase.php:679
requireOnlyOneParameter( $params,... $required)
Die if 0 or more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:947
getResult()
Get the result object.
Definition: ApiBase.php:668
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:808
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:529
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1571
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1364
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:64
API Module to move pages.
Definition: ApiMove.php:35
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiMove.php:262
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiMove.php:229
__construct(ApiMain $mainModule, $moduleName, MovePageFactory $movePageFactory, RepoGroup $repoGroup, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup)
Definition: ApiMove.php:42
moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags=[])
Definition: ApiMove.php:198
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiMove.php:63
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiMove.php:225
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiMove.php:274
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiMove.php:266
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiMove.php:233
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.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:58
Represents a title within MediaWiki.
Definition: Title.php:76
Provides access to user options.
Prioritized list of file repositories.
Definition: RepoGroup.php:30
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