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