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