MediaWiki REL1_35
ApiMove.php
Go to the documentation of this file.
1<?php
24
29class ApiMove extends ApiBase {
30
32
33 public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
34 parent::__construct( $mainModule, $moduleName, $modulePrefix );
35
36 $this->watchlistExpiryEnabled = $this->getConfig()->get( 'WatchlistExpiry' );
37 $this->watchlistMaxDuration = $this->getConfig()->get( 'WatchlistExpiryMaxDuration' );
38 }
39
40 public function execute() {
42
43 $user = $this->getUser();
44 $params = $this->extractRequestParams();
45
46 $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
47
48 if ( isset( $params['from'] ) ) {
49 $fromTitle = Title::newFromText( $params['from'] );
50 if ( !$fromTitle || $fromTitle->isExternal() ) {
51 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['from'] ) ] );
52 }
53 } elseif ( isset( $params['fromid'] ) ) {
54 $fromTitle = Title::newFromID( $params['fromid'] );
55 if ( !$fromTitle ) {
56 $this->dieWithError( [ 'apierror-nosuchpageid', $params['fromid'] ] );
57 }
58 }
59
60 if ( !$fromTitle->exists() ) {
61 $this->dieWithError( 'apierror-missingtitle' );
62 }
63 $fromTalk = $fromTitle->getTalkPage();
64
65 $toTitle = Title::newFromText( $params['to'] );
66 if ( !$toTitle || $toTitle->isExternal() ) {
67 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['to'] ) ] );
68 }
69 $toTalk = $toTitle->getTalkPageIfDefined();
70
71 $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
72 if ( $toTitle->getNamespace() == NS_FILE
73 && !$repoGroup->getLocalRepo()->findFile( $toTitle )
74 && $repoGroup->findFile( $toTitle )
75 ) {
76 if ( !$params['ignorewarnings'] &&
77 $this->getPermissionManager()->userHasRight( $user, 'reupload-shared' ) ) {
78 $this->dieWithError( 'apierror-fileexists-sharedrepo-perm' );
79 } elseif ( !$this->getPermissionManager()->userHasRight( $user, 'reupload-shared' ) ) {
80 $this->dieWithError( 'apierror-cantoverwrite-sharedfile' );
81 }
82 }
83
84 // Rate limit
85 if ( $user->pingLimiter( 'move' ) ) {
86 $this->dieWithError( 'apierror-ratelimited' );
87 }
88
89 // Check if the user is allowed to add the specified changetags
90 if ( $params['tags'] ) {
91 $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
92 if ( !$ableToTag->isOK() ) {
93 $this->dieStatus( $ableToTag );
94 }
95 }
96
97 // Move the page
98 $toTitleExists = $toTitle->exists();
99 $status = $this->movePage( $fromTitle, $toTitle, $params['reason'], !$params['noredirect'],
100 $params['tags'] ?: [] );
101 if ( !$status->isOK() ) {
102 $user->spreadAnyEditBlock();
103 $this->dieStatus( $status );
104 }
105
106 $r = [
107 'from' => $fromTitle->getPrefixedText(),
108 'to' => $toTitle->getPrefixedText(),
109 'reason' => $params['reason']
110 ];
111
112 // NOTE: we assume that if the old title exists, it's because it was re-created as
113 // a redirect to the new title. This is not safe, but what we did before was
114 // even worse: we just determined whether a redirect should have been created,
115 // and reported that it was created if it should have, without any checks.
116 $r['redirectcreated'] = $fromTitle->exists();
117
118 $r['moveoverredirect'] = $toTitleExists;
119
120 // Move the talk page
121 if ( $params['movetalk'] && $toTalk && $fromTalk->exists() && !$fromTitle->isTalkPage() ) {
122 $toTalkExists = $toTalk->exists();
123 $status = $this->movePage(
124 $fromTalk,
125 $toTalk,
126 $params['reason'],
127 !$params['noredirect'],
128 $params['tags'] ?: []
129 );
130 if ( $status->isOK() ) {
131 $r['talkfrom'] = $fromTalk->getPrefixedText();
132 $r['talkto'] = $toTalk->getPrefixedText();
133 $r['talkmoveoverredirect'] = $toTalkExists;
134 } else {
135 // We're not going to dieWithError() on failure, since we already changed something
136 $r['talkmove-errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
137 }
138 }
139
140 $result = $this->getResult();
141
142 // Move subpages
143 if ( $params['movesubpages'] ) {
144 $r['subpages'] = $this->moveSubpages(
145 $fromTitle,
146 $toTitle,
147 $params['reason'],
148 $params['noredirect'],
149 $params['tags'] ?: []
150 );
151 ApiResult::setIndexedTagName( $r['subpages'], 'subpage' );
152
153 if ( $params['movetalk'] ) {
154 $r['subpages-talk'] = $this->moveSubpages(
155 $fromTalk,
156 $toTalk,
157 $params['reason'],
158 $params['noredirect'],
159 $params['tags'] ?: []
160 );
161 ApiResult::setIndexedTagName( $r['subpages-talk'], 'subpage' );
162 }
163 }
164
165 $watch = 'preferences';
166 if ( isset( $params['watchlist'] ) ) {
167 $watch = $params['watchlist'];
168 }
169 $watchlistExpiry = $this->getExpiryFromParams( $params );
170
171 // Watch pages
172 $this->setWatch( $watch, $fromTitle, $user, 'watchmoves', $watchlistExpiry );
173 $this->setWatch( $watch, $toTitle, $user, 'watchmoves', $watchlistExpiry );
174
175 $result->addValue( null, $this->getModuleName(), $r );
176 }
177
186 protected function movePage( Title $from, Title $to, $reason, $createRedirect, $changeTags ) {
187 $mp = MediaWikiServices::getInstance()->getMovePageFactory()->newMovePage( $from, $to );
188 $valid = $mp->isValidMove();
189 if ( !$valid->isOK() ) {
190 return $valid;
191 }
192
193 $user = $this->getUser();
194 $permStatus = $mp->checkPermissions( $user, $reason );
195 if ( !$permStatus->isOK() ) {
196 return $permStatus;
197 }
198
199 // Check suppressredirect permission
200 if ( !$this->getPermissionManager()->userHasRight( $user, 'suppressredirect' ) ) {
201 $createRedirect = true;
202 }
203
204 return $mp->move( $user, $reason, $createRedirect, $changeTags );
205 }
206
215 public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags = [] ) {
216 $retval = [];
217
218 $mp = new MovePage( $fromTitle, $toTitle );
219 $result =
220 $mp->moveSubpagesIfAllowed( $this->getUser(), $reason, !$noredirect, $changeTags );
221 if ( !$result->isOK() ) {
222 // This means the whole thing failed
223 return [ 'errors' => $this->getErrorFormatter()->arrayFromStatus( $result ) ];
224 }
225
226 // At least some pages could be moved
227 // Report each of them separately
228 foreach ( $result->getValue() as $oldTitle => $status ) {
230 $r = [ 'from' => $oldTitle ];
231 if ( $status->isOK() ) {
232 $r['to'] = $status->getValue();
233 } else {
234 $r['errors'] = $this->getErrorFormatter()->arrayFromStatus( $status );
235 }
236 $retval[] = $r;
237 }
238
239 return $retval;
240 }
241
242 public function mustBePosted() {
243 return true;
244 }
245
246 public function isWriteMode() {
247 return true;
248 }
249
250 public function getAllowedParams() {
251 $params = [
252 'from' => null,
253 'fromid' => [
254 ApiBase::PARAM_TYPE => 'integer'
255 ],
256 'to' => [
257 ApiBase::PARAM_TYPE => 'string',
259 ],
260 'reason' => '',
261 'movetalk' => false,
262 'movesubpages' => false,
263 'noredirect' => false,
264 ];
265
266 // Params appear in the docs in the order they are defined,
267 // which is why this is here and not at the bottom.
268 $params += $this->getWatchlistParams();
269
270 return $params + [
271 'ignorewarnings' => false,
272 'tags' => [
273 ApiBase::PARAM_TYPE => 'tags',
275 ],
276 ];
277 }
278
279 public function needsToken() {
280 return 'csrf';
281 }
282
283 protected function getExamplesMessages() {
284 return [
285 'action=move&from=Badtitle&to=Goodtitle&token=123ABC&' .
286 'reason=Misspelled%20title&movetalk=&noredirect='
287 => 'apihelp-move-example-move',
288 ];
289 }
290
291 public function getHelpUrls() {
292 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Move';
293 }
294}
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.
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:52
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1437
const PARAM_REQUIRED
Definition ApiBase.php:102
const PARAM_TYPE
Definition ApiBase.php:78
getErrorFormatter()
Get the error formatter Stable to override.
Definition ApiBase.php:635
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:692
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:909
getResult()
Get the result object.
Definition ApiBase.php:620
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:772
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:1495
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition ApiBase.php:1294
const PARAM_ISMULTI
Definition ApiBase.php:74
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:47
API Module to move pages.
Definition ApiMove.php:29
needsToken()
Returns the token type this module requires in order to execute.
Definition ApiMove.php:279
isWriteMode()
Indicates whether this module requires write mode.
Definition ApiMove.php:246
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Stable to call.
Definition ApiMove.php:33
moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags=[])
Definition ApiMove.php:215
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition ApiMove.php:40
mustBePosted()
Indicates whether this module must be called with a POST request Stable to override.
Definition ApiMove.php:242
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiMove.php:291
movePage(Title $from, Title $to, $reason, $createRedirect, $changeTags)
Definition ApiMove.php:186
getExamplesMessages()
Returns usage examples for this module.
Definition ApiMove.php:283
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiMove.php:250
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...
getUser()
Stable to override.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Handles the backend logic of moving a page from one title to another.
Definition MovePage.php:42
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Represents a title within MediaWiki.
Definition Title.php:42
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
const NS_FILE
Definition Defines.php:76
return true
Definition router.php:92