MediaWiki  master
ApiMove.php
Go to the documentation of this file.
1 <?php
26 
31 class ApiMove extends ApiBase {
32 
34 
37 
39  private $repoGroup;
40 
41  public function __construct(
42  ApiMain $mainModule,
43  $moduleName,
48  ) {
49  parent::__construct( $mainModule, $moduleName );
50 
51  $this->movePageFactory = $movePageFactory;
52  $this->repoGroup = $repoGroup;
53 
54  // Variables needed in ApiWatchlistTrait trait
55  $this->watchlistExpiryEnabled = $this->getConfig()->get( 'WatchlistExpiry' );
56  $this->watchlistMaxDuration = $this->getConfig()->get( 'WatchlistExpiryMaxDuration' );
57  $this->watchlistManager = $watchlistManager;
58  $this->userOptionsLookup = $userOptionsLookup;
59  }
60 
61  public function execute() {
63 
64  $user = $this->getUser();
65  $params = $this->extractRequestParams();
66 
67  $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
68 
69  if ( isset( $params['from'] ) ) {
70  $fromTitle = Title::newFromText( $params['from'] );
71  if ( !$fromTitle || $fromTitle->isExternal() ) {
72  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['from'] ) ] );
73  }
74  } elseif ( isset( $params['fromid'] ) ) {
75  $fromTitle = Title::newFromID( $params['fromid'] );
76  if ( !$fromTitle ) {
77  $this->dieWithError( [ 'apierror-nosuchpageid', $params['fromid'] ] );
78  }
79  }
80 
81  if ( !$fromTitle->exists() ) {
82  $this->dieWithError( 'apierror-missingtitle' );
83  }
84  $fromTalk = $fromTitle->getTalkPage();
85 
86  $toTitle = Title::newFromText( $params['to'] );
87  if ( !$toTitle || $toTitle->isExternal() ) {
88  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
89  }
90  $toTalk = $toTitle->getTalkPageIfDefined();
91 
92  if ( $toTitle->getNamespace() === NS_FILE
93  && !$this->repoGroup->getLocalRepo()->findFile( $toTitle )
94  && $this->repoGroup->findFile( $toTitle )
95  ) {
96  if ( !$params['ignorewarnings'] &&
97  $this->getAuthority()->isAllowed( 'reupload-shared' ) ) {
98  $this->dieWithError( 'apierror-fileexists-sharedrepo-perm' );
99  } elseif ( !$this->getAuthority()->isAllowed( 'reupload-shared' ) ) {
100  $this->dieWithError( 'apierror-cantoverwrite-sharedfile' );
101  }
102  }
103 
104  // Rate limit
105  if ( $user->pingLimiter( 'move' ) ) {
106  $this->dieWithError( 'apierror-ratelimited' );
107  }
108 
109  // Check if the user is allowed to add the specified changetags
110  if ( $params['tags'] ) {
111  $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $this->getAuthority() );
112  if ( !$ableToTag->isOK() ) {
113  $this->dieStatus( $ableToTag );
114  }
115  }
116 
117  // Move the page
118  $toTitleExists = $toTitle->exists();
119  $status = $this->movePage( $fromTitle, $toTitle, $params['reason'], !$params['noredirect'],
120  $params['tags'] ?: [] );
121  if ( !$status->isOK() ) {
122  $user->spreadAnyEditBlock();
123  $this->dieStatus( $status );
124  }
125 
126  $r = [
127  'from' => $fromTitle->getPrefixedText(),
128  'to' => $toTitle->getPrefixedText(),
129  'reason' => $params['reason']
130  ];
131 
132  // NOTE: we assume that if the old title exists, it's because it was re-created as
133  // a redirect to the new title. This is not safe, but what we did before was
134  // even worse: we just determined whether a redirect should have been created,
135  // and reported that it was created if it should have, without any checks.
136  $r['redirectcreated'] = $fromTitle->exists();
137 
138  $r['moveoverredirect'] = $toTitleExists;
139 
140  // Move the talk page
141  if ( $params['movetalk'] && $toTalk && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
142  $toTalkExists = $toTalk->exists();
143  $status = $this->movePage(
144  $fromTalk,
145  $toTalk,
146  $params['reason'],
147  !$params['noredirect'],
148  $params['tags'] ?: []
149  );
150  if ( $status->isOK() ) {
151  $r['talkfrom'] = $fromTalk->getPrefixedText();
152  $r['talkto'] = $toTalk->getPrefixedText();
153  $r['talkmoveoverredirect'] = $toTalkExists;
154  } else {
155  // We're not going to dieWithError() on failure, since we already changed something
156  $r['talkmove-errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
157  }
158  }
159 
160  $result = $this->getResult();
161 
162  // Move subpages
163  if ( $params['movesubpages'] ) {
164  $r['subpages'] = $this->moveSubpages(
165  $fromTitle,
166  $toTitle,
167  $params['reason'],
168  $params['noredirect'],
169  $params['tags'] ?: []
170  );
171  ApiResult::setIndexedTagName( $r['subpages'], 'subpage' );
172 
173  if ( $params['movetalk'] ) {
174  $r['subpages-talk'] = $this->moveSubpages(
175  $fromTalk,
176  $toTalk,
177  $params['reason'],
178  $params['noredirect'],
179  $params['tags'] ?: []
180  );
181  ApiResult::setIndexedTagName( $r['subpages-talk'], 'subpage' );
182  }
183  }
184 
185  $watch = 'preferences';
186  if ( isset( $params['watchlist'] ) ) {
187  $watch = $params['watchlist'];
188  }
189  $watchlistExpiry = $this->getExpiryFromParams( $params );
190 
191  // Watch pages
192  $this->setWatch( $watch, $fromTitle, $user, 'watchmoves', $watchlistExpiry );
193  $this->setWatch( $watch, $toTitle, $user, 'watchmoves', $watchlistExpiry );
194 
195  $result->addValue( null, $this->getModuleName(), $r );
196  }
197 
206  protected function movePage( Title $from, Title $to, $reason, $createRedirect, $changeTags ) {
207  $mp = $this->movePageFactory->newMovePage( $from, $to );
208  $valid = $mp->isValidMove();
209  if ( !$valid->isOK() ) {
210  return $valid;
211  }
212 
213  $permStatus = $mp->authorizeMove( $this->getAuthority(), $reason );
214  if ( !$permStatus->isOK() ) {
215  return Status::wrap( $permStatus );
216  }
217 
218  // Check suppressredirect permission
219  if ( !$this->getAuthority()->isAllowed( 'suppressredirect' ) ) {
220  $createRedirect = true;
221  }
222 
223  return $mp->move( $this->getUser(), $reason, $createRedirect, $changeTags );
224  }
225 
234  public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags = [] ) {
235  $retval = [];
236 
237  $mp = $this->movePageFactory->newMovePage( $fromTitle, $toTitle );
238  $result =
239  $mp->moveSubpagesIfAllowed( $this->getAuthority(), $reason, !$noredirect, $changeTags );
240  if ( !$result->isOK() ) {
241  // This means the whole thing failed
242  return [ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $result ) ];
243  }
244 
245  // At least some pages could be moved
246  // Report each of them separately
247  foreach ( $result->getValue() as $oldTitle => $status ) {
249  $r = [ 'from' => $oldTitle ];
250  if ( $status->isOK() ) {
251  $r['to'] = $status->getValue();
252  } else {
253  $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
254  }
255  $retval[] = $r;
256  }
257 
258  return $retval;
259  }
260 
261  public function mustBePosted() {
262  return true;
263  }
264 
265  public function isWriteMode() {
266  return true;
267  }
268 
269  public function getAllowedParams() {
270  $params = [
271  'from' => null,
272  'fromid' => [
273  ApiBase::PARAM_TYPE => 'integer'
274  ],
275  'to' => [
276  ApiBase::PARAM_TYPE => 'string',
278  ],
279  'reason' => '',
280  'movetalk' => false,
281  'movesubpages' => false,
282  'noredirect' => false,
283  ];
284 
285  // Params appear in the docs in the order they are defined,
286  // which is why this is here and not at the bottom.
287  $params += $this->getWatchlistParams();
288 
289  return $params + [
290  'ignorewarnings' => false,
291  'tags' => [
292  ApiBase::PARAM_TYPE => 'tags',
293  ApiBase::PARAM_ISMULTI => true,
294  ],
295  ];
296  }
297 
298  public function needsToken() {
299  return 'csrf';
300  }
301 
302  protected function getExamplesMessages() {
303  return [
304  'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
305  'reason=Misspelled%20title&movetalk=&noredirect='
306  => 'apihelp-move-example-move',
307  ];
308  }
309 
310  public function getHelpUrls() {
311  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Move';
312  }
313 }
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:49
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:72
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:383
getExpiryFromParams
getExpiryFromParams(array $params)
Get formatted expiry from the given parameters, or null if no expiry was provided.
Definition: ApiWatchlistTrait.php:164
ApiBase\PARAM_REQUIRED
const PARAM_REQUIRED
Definition: ApiBase.php:78
ApiMove\needsToken
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiMove.php:298
ApiMove\$movePageFactory
MovePageFactory $movePageFactory
Definition: ApiMove.php:36
ApiMove\moveSubpages
moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags=[])
Definition: ApiMove.php:234
ApiMove\__construct
__construct(ApiMain $mainModule, $moduleName, MovePageFactory $movePageFactory, RepoGroup $repoGroup, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup)
Definition: ApiMove.php:41
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
Definition: ApiBase.php:72
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:571
ContextSource\getUser
getUser()
Definition: ContextSource.php:136
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:55
ApiMove\movePage
movePage(Title $from, Title $to, $reason, $createRedirect, $changeTags)
Definition: ApiMove.php:206
ApiMove\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiMove.php:269
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
MediaWiki\Watchlist\WatchlistManager
WatchlistManager service.
Definition: WatchlistManager.php:52
Status\wrap
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:62
ApiMove\isWriteMode
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiMove.php:265
setWatch
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.
Definition: ApiWatchlistTrait.php:96
ApiMove\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiMove.php:302
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:707
ApiMove
API Module to move pages.
Definition: ApiMove.php:31
ChangeTags\canAddTagsAccompanyingChange
static canAddTagsAccompanyingChange(array $tags, Authority $performer=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:625
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:603
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:844
ContextSource\getAuthority
getAuthority()
Definition: ContextSource.php:144
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:1459
ApiMove\$repoGroup
RepoGroup $repoGroup
Definition: ApiMove.php:39
ApiMove\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiMove.php:310
MediaWiki\User\UserOptionsLookup
Provides access to user options.
Definition: UserOptionsLookup.php:29
Page\MovePageFactory
Definition: MovePageFactory.php:30
ApiWatchlistTrait
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
Definition: ApiWatchlistTrait.php:21
$watchlistManager
WatchlistManager $watchlistManager
Definition: ApiWatchlistTrait.php:30
$userOptionsLookup
UserOptionsLookup $userOptionsLookup
Definition: ApiWatchlistTrait.php:33
Title
Represents a title within MediaWiki.
Definition: Title.php:48
getWatchlistParams
getWatchlistParams(array $watchOptions=[])
Get additional allow params specific to watchlisting.
Definition: ApiWatchlistTrait.php:59
ApiMove\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiMove.php:61
ApiBase\dieStatus
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1442
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:440
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
Definition: ApiBase.php:71
RepoGroup
Prioritized list of file repositories.
Definition: RepoGroup.php:32
NS_FILE
const NS_FILE
Definition: Defines.php:70
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:529
ApiMove\mustBePosted
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiMove.php:261
ApiBase\getErrorFormatter
getErrorFormatter()
Definition: ApiBase.php:582