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