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