MediaWiki REL1_37
ApiMove.php
Go to the documentation of this file.
1<?php
26
31class 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',
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}
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:55
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1436
const PARAM_REQUIRED
Definition ApiBase.php:105
const PARAM_TYPE
Definition ApiBase.php:81
getErrorFormatter()
Definition ApiBase.php:639
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:901
getResult()
Get the result object.
Definition ApiBase.php:628
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:764
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:497
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:1293
const PARAM_ISMULTI
Definition ApiBase.php:77
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:49
API Module to move pages.
Definition ApiMove.php:31
needsToken()
Returns the token type this module requires in order to execute.
Definition ApiMove.php:298
isWriteMode()
Indicates whether this module requires write mode.
Definition ApiMove.php:265
RepoGroup $repoGroup
Definition ApiMove.php:39
__construct(ApiMain $mainModule, $moduleName, MovePageFactory $movePageFactory, RepoGroup $repoGroup, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup)
Definition ApiMove.php:41
moveSubpages( $fromTitle, $toTitle, $reason, $noredirect, $changeTags=[])
Definition ApiMove.php:234
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition ApiMove.php:61
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition ApiMove.php:261
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiMove.php:310
MovePageFactory $movePageFactory
Definition ApiMove.php:36
movePage(Title $from, Title $to, $reason, $createRedirect, $changeTags)
Definition ApiMove.php:206
getExamplesMessages()
Returns usage examples for this module.
Definition ApiMove.php:302
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiMove.php:269
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...
Provides access to user options.
Prioritized list of file repositories.
Definition RepoGroup.php:33
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:48
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
return true
Definition router.php:92