Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
49.37% |
311 / 630 |
|
27.27% |
3 / 11 |
CRAP | |
0.00% |
0 / 1 |
SpecialMovePage | |
49.44% |
311 / 629 |
|
27.27% |
3 / 11 |
2383.53 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
1 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
75.51% |
37 / 49 |
|
0.00% |
0 / 1 |
14.12 | |||
showForm | |
65.15% |
215 / 330 |
|
0.00% |
0 / 1 |
130.70 | |||
doSubmit | |
0.00% |
0 / 182 |
|
0.00% |
0 / 1 |
2862 | |||
showLogFragment | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
showSubpages | |
81.82% |
18 / 22 |
|
0.00% |
0 / 1 |
9.49 | |||
showSubpagesList | |
88.89% |
16 / 18 |
|
0.00% |
0 / 1 |
5.03 | |||
truncateSubpagesList | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
prefixSearchSubpages | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Specials; |
22 | |
23 | use DoubleRedirectJob; |
24 | use ErrorPageError; |
25 | use LogEventsList; |
26 | use LogPage; |
27 | use MediaWiki\Cache\LinkBatchFactory; |
28 | use MediaWiki\CommentStore\CommentStore; |
29 | use MediaWiki\Content\IContentHandlerFactory; |
30 | use MediaWiki\Deferred\DeferredUpdates; |
31 | use MediaWiki\Html\Html; |
32 | use MediaWiki\MainConfigNames; |
33 | use MediaWiki\Page\DeletePageFactory; |
34 | use MediaWiki\Page\MovePageFactory; |
35 | use MediaWiki\Page\WikiPageFactory; |
36 | use MediaWiki\Permissions\PermissionManager; |
37 | use MediaWiki\Permissions\PermissionStatus; |
38 | use MediaWiki\Permissions\RestrictionStore; |
39 | use MediaWiki\SpecialPage\UnlistedSpecialPage; |
40 | use MediaWiki\Title\NamespaceInfo; |
41 | use MediaWiki\Title\Title; |
42 | use MediaWiki\Title\TitleArrayFromResult; |
43 | use MediaWiki\Title\TitleFactory; |
44 | use MediaWiki\User\Options\UserOptionsLookup; |
45 | use MediaWiki\Watchlist\WatchlistManager; |
46 | use MediaWiki\Widget\ComplexTitleInputWidget; |
47 | use MediaWiki\Xml\Xml; |
48 | use OOUI\ButtonInputWidget; |
49 | use OOUI\CheckboxInputWidget; |
50 | use OOUI\DropdownInputWidget; |
51 | use OOUI\FieldLayout; |
52 | use OOUI\FieldsetLayout; |
53 | use OOUI\FormLayout; |
54 | use OOUI\HtmlSnippet; |
55 | use OOUI\PanelLayout; |
56 | use OOUI\TextInputWidget; |
57 | use PermissionsError; |
58 | use RepoGroup; |
59 | use SearchEngineFactory; |
60 | use StatusValue; |
61 | use StringUtils; |
62 | use ThrottledError; |
63 | use Wikimedia\Rdbms\IConnectionProvider; |
64 | use Wikimedia\Rdbms\IDBAccessObject; |
65 | use Wikimedia\Rdbms\IExpression; |
66 | use Wikimedia\Rdbms\LikeValue; |
67 | |
68 | /** |
69 | * Implement Special:Movepage for changing page titles |
70 | * |
71 | * @ingroup SpecialPage |
72 | */ |
73 | class SpecialMovePage extends UnlistedSpecialPage { |
74 | /** @var Title */ |
75 | protected $oldTitle = null; |
76 | |
77 | /** @var Title */ |
78 | protected $newTitle; |
79 | |
80 | /** @var string Text input */ |
81 | protected $reason; |
82 | |
83 | /** @var bool */ |
84 | protected $moveTalk; |
85 | |
86 | /** @var bool */ |
87 | protected $deleteAndMove; |
88 | |
89 | /** @var bool */ |
90 | protected $moveSubpages; |
91 | |
92 | /** @var bool */ |
93 | protected $fixRedirects; |
94 | |
95 | /** @var bool */ |
96 | protected $leaveRedirect; |
97 | |
98 | /** @var bool */ |
99 | protected $moveOverShared; |
100 | |
101 | /** @var bool */ |
102 | private $watch = false; |
103 | |
104 | private MovePageFactory $movePageFactory; |
105 | private PermissionManager $permManager; |
106 | private UserOptionsLookup $userOptionsLookup; |
107 | private IConnectionProvider $dbProvider; |
108 | private IContentHandlerFactory $contentHandlerFactory; |
109 | private NamespaceInfo $nsInfo; |
110 | private LinkBatchFactory $linkBatchFactory; |
111 | private RepoGroup $repoGroup; |
112 | private WikiPageFactory $wikiPageFactory; |
113 | private SearchEngineFactory $searchEngineFactory; |
114 | private WatchlistManager $watchlistManager; |
115 | private RestrictionStore $restrictionStore; |
116 | private TitleFactory $titleFactory; |
117 | private DeletePageFactory $deletePageFactory; |
118 | |
119 | /** |
120 | * @param MovePageFactory $movePageFactory |
121 | * @param PermissionManager $permManager |
122 | * @param UserOptionsLookup $userOptionsLookup |
123 | * @param IConnectionProvider $dbProvider |
124 | * @param IContentHandlerFactory $contentHandlerFactory |
125 | * @param NamespaceInfo $nsInfo |
126 | * @param LinkBatchFactory $linkBatchFactory |
127 | * @param RepoGroup $repoGroup |
128 | * @param WikiPageFactory $wikiPageFactory |
129 | * @param SearchEngineFactory $searchEngineFactory |
130 | * @param WatchlistManager $watchlistManager |
131 | * @param RestrictionStore $restrictionStore |
132 | * @param TitleFactory $titleFactory |
133 | * @param DeletePageFactory $deletePageFactory |
134 | */ |
135 | public function __construct( |
136 | MovePageFactory $movePageFactory, |
137 | PermissionManager $permManager, |
138 | UserOptionsLookup $userOptionsLookup, |
139 | IConnectionProvider $dbProvider, |
140 | IContentHandlerFactory $contentHandlerFactory, |
141 | NamespaceInfo $nsInfo, |
142 | LinkBatchFactory $linkBatchFactory, |
143 | RepoGroup $repoGroup, |
144 | WikiPageFactory $wikiPageFactory, |
145 | SearchEngineFactory $searchEngineFactory, |
146 | WatchlistManager $watchlistManager, |
147 | RestrictionStore $restrictionStore, |
148 | TitleFactory $titleFactory, |
149 | DeletePageFactory $deletePageFactory |
150 | ) { |
151 | parent::__construct( 'Movepage' ); |
152 | $this->movePageFactory = $movePageFactory; |
153 | $this->permManager = $permManager; |
154 | $this->userOptionsLookup = $userOptionsLookup; |
155 | $this->dbProvider = $dbProvider; |
156 | $this->contentHandlerFactory = $contentHandlerFactory; |
157 | $this->nsInfo = $nsInfo; |
158 | $this->linkBatchFactory = $linkBatchFactory; |
159 | $this->repoGroup = $repoGroup; |
160 | $this->wikiPageFactory = $wikiPageFactory; |
161 | $this->searchEngineFactory = $searchEngineFactory; |
162 | $this->watchlistManager = $watchlistManager; |
163 | $this->restrictionStore = $restrictionStore; |
164 | $this->titleFactory = $titleFactory; |
165 | $this->deletePageFactory = $deletePageFactory; |
166 | } |
167 | |
168 | public function doesWrites() { |
169 | return true; |
170 | } |
171 | |
172 | public function execute( $par ) { |
173 | $this->useTransactionalTimeLimit(); |
174 | $this->checkReadOnly(); |
175 | $this->setHeaders(); |
176 | $this->outputHeader(); |
177 | |
178 | $request = $this->getRequest(); |
179 | |
180 | // Beware: The use of WebRequest::getText() is wanted! See T22365 |
181 | $target = $par ?? $request->getText( 'target' ); |
182 | $oldTitleText = $request->getText( 'wpOldTitle', $target ); |
183 | $this->oldTitle = Title::newFromText( $oldTitleText ); |
184 | |
185 | if ( !$this->oldTitle ) { |
186 | // Either oldTitle wasn't passed, or newFromText returned null |
187 | throw new ErrorPageError( 'notargettitle', 'notargettext' ); |
188 | } |
189 | $this->getOutput()->addBacklinkSubtitle( $this->oldTitle ); |
190 | |
191 | if ( !$this->oldTitle->exists() ) { |
192 | throw new ErrorPageError( 'nopagetitle', 'nopagetext' ); |
193 | } |
194 | |
195 | $newTitleTextMain = $request->getText( 'wpNewTitleMain' ); |
196 | $newTitleTextNs = $request->getInt( 'wpNewTitleNs', $this->oldTitle->getNamespace() ); |
197 | // Backwards compatibility for forms submitting here from other sources |
198 | // which is more common than it should be. |
199 | $newTitleText_bc = $request->getText( 'wpNewTitle' ); |
200 | $this->newTitle = strlen( $newTitleText_bc ) > 0 |
201 | ? Title::newFromText( $newTitleText_bc ) |
202 | : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain ); |
203 | |
204 | $user = $this->getUser(); |
205 | $isSubmit = $request->getRawVal( 'action' ) === 'submit' && $request->wasPosted(); |
206 | |
207 | $reasonList = $request->getText( 'wpReasonList', 'other' ); |
208 | $reason = $request->getText( 'wpReason' ); |
209 | if ( $reasonList === 'other' ) { |
210 | $this->reason = $reason; |
211 | } elseif ( $reason !== '' ) { |
212 | $this->reason = $reasonList . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $reason; |
213 | } else { |
214 | $this->reason = $reasonList; |
215 | } |
216 | // Default to checked, but don't fill in true during submission (browsers only submit checked values) |
217 | // TODO: Use HTMLForm to take care of this. |
218 | $def = !$isSubmit; |
219 | $this->moveTalk = $request->getBool( 'wpMovetalk', $def ); |
220 | $this->fixRedirects = $request->getBool( 'wpFixRedirects', $def ); |
221 | $this->leaveRedirect = $request->getBool( 'wpLeaveRedirect', $def ); |
222 | // T222953: Tick the "move subpages" box by default |
223 | $this->moveSubpages = $request->getBool( 'wpMovesubpages', $def ); |
224 | $this->deleteAndMove = $request->getBool( 'wpDeleteAndMove' ); |
225 | $this->moveOverShared = $request->getBool( 'wpMoveOverSharedFile' ); |
226 | $this->watch = $request->getCheck( 'wpWatch' ) && $user->isRegistered(); |
227 | |
228 | // Similar to other SpecialPage/Action classes, when tokens fail (likely due to reset or expiry), |
229 | // do not show an error but show the form again for easy re-submit. |
230 | if ( $isSubmit && $user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) { |
231 | // Check rights |
232 | $permStatus = $this->permManager->getPermissionStatus( 'move', $user, $this->oldTitle, |
233 | PermissionManager::RIGOR_SECURE ); |
234 | // If the account is "hard" blocked, auto-block IP |
235 | DeferredUpdates::addCallableUpdate( [ $user, 'spreadAnyEditBlock' ] ); |
236 | if ( !$permStatus->isGood() ) { |
237 | throw new PermissionsError( 'move', $permStatus ); |
238 | } |
239 | $this->doSubmit(); |
240 | } else { |
241 | // Avoid primary DB connection on form view (T283265) |
242 | $permStatus = $this->permManager->getPermissionStatus( 'move', $user, $this->oldTitle, |
243 | PermissionManager::RIGOR_FULL ); |
244 | if ( !$permStatus->isGood() ) { |
245 | DeferredUpdates::addCallableUpdate( [ $user, 'spreadAnyEditBlock' ] ); |
246 | throw new PermissionsError( 'move', $permStatus ); |
247 | } |
248 | $this->showForm(); |
249 | } |
250 | } |
251 | |
252 | /** |
253 | * Show the form |
254 | * |
255 | * @param ?StatusValue $status Form submission status. |
256 | * If it is a PermissionStatus, a special message will be shown. |
257 | */ |
258 | private function showForm( ?StatusValue $status = null ) { |
259 | $this->getSkin()->setRelevantTitle( $this->oldTitle ); |
260 | |
261 | $out = $this->getOutput(); |
262 | $out->setPageTitleMsg( $this->msg( 'move-page' )->plaintextParams( $this->oldTitle->getPrefixedText() ) ); |
263 | $out->addModuleStyles( [ |
264 | 'mediawiki.special', |
265 | 'mediawiki.interface.helpers.styles' |
266 | ] ); |
267 | $out->addModules( 'mediawiki.misc-authed-ooui' ); |
268 | $this->addHelpLink( 'Help:Moving a page' ); |
269 | |
270 | $handler = $this->contentHandlerFactory |
271 | ->getContentHandler( $this->oldTitle->getContentModel() ); |
272 | $createRedirect = $handler->supportsRedirects() && !( |
273 | // Do not create redirects for wikitext message overrides (T376399). |
274 | // Maybe one day they will have a custom content model and this special case won't be needed. |
275 | $this->oldTitle->getNamespace() === NS_MEDIAWIKI && |
276 | $this->oldTitle->getContentModel() === CONTENT_MODEL_WIKITEXT |
277 | ); |
278 | |
279 | if ( $this->getConfig()->get( MainConfigNames::FixDoubleRedirects ) ) { |
280 | $out->addWikiMsg( 'movepagetext' ); |
281 | } else { |
282 | $out->addWikiMsg( $createRedirect ? |
283 | 'movepagetext-noredirectfixer' : |
284 | 'movepagetext-noredirectsupport' ); |
285 | } |
286 | |
287 | if ( $this->oldTitle->getNamespace() === NS_USER && !$this->oldTitle->isSubpage() ) { |
288 | $out->addHTML( |
289 | Html::warningBox( |
290 | $out->msg( 'moveuserpage-warning' )->parse(), |
291 | 'mw-moveuserpage-warning' |
292 | ) |
293 | ); |
294 | } elseif ( $this->oldTitle->getNamespace() === NS_CATEGORY ) { |
295 | $out->addHTML( |
296 | Html::warningBox( |
297 | $out->msg( 'movecategorypage-warning' )->parse(), |
298 | 'mw-movecategorypage-warning' |
299 | ) |
300 | ); |
301 | } |
302 | |
303 | $deleteAndMove = false; |
304 | $moveOverShared = false; |
305 | |
306 | $user = $this->getUser(); |
307 | $newTitle = $this->newTitle; |
308 | |
309 | if ( !$newTitle ) { |
310 | # Show the current title as a default |
311 | # when the form is first opened. |
312 | $newTitle = $this->oldTitle; |
313 | } elseif ( !$status ) { |
314 | # If a title was supplied, probably from the move log revert |
315 | # link, check for validity. We can then show some diagnostic |
316 | # information and save a click. |
317 | $mp = $this->movePageFactory->newMovePage( $this->oldTitle, $newTitle ); |
318 | $status = $mp->isValidMove(); |
319 | $status->merge( $mp->probablyCanMove( $this->getAuthority() ) ); |
320 | } |
321 | if ( !$status ) { |
322 | $status = StatusValue::newGood(); |
323 | } |
324 | |
325 | if ( count( $status->getMessages() ) == 1 ) { |
326 | if ( $status->hasMessage( 'articleexists' ) |
327 | && $this->permManager->quickUserCan( 'delete', $user, $newTitle ) |
328 | ) { |
329 | $out->addHTML( |
330 | Html::warningBox( |
331 | $out->msg( 'delete_and_move_text', $newTitle->getPrefixedText() )->parse() |
332 | ) |
333 | ); |
334 | $deleteAndMove = true; |
335 | $status = StatusValue::newGood(); |
336 | } elseif ( $status->hasMessage( 'redirectexists' ) && ( |
337 | // Any user that can delete normally can also delete a redirect here |
338 | $this->permManager->quickUserCan( 'delete-redirect', $user, $newTitle ) || |
339 | $this->permManager->quickUserCan( 'delete', $user, $newTitle ) ) |
340 | ) { |
341 | $out->addHTML( |
342 | Html::warningBox( |
343 | $out->msg( 'delete_redirect_and_move_text', $newTitle->getPrefixedText() )->parse() |
344 | ) |
345 | ); |
346 | $deleteAndMove = true; |
347 | $status = StatusValue::newGood(); |
348 | } elseif ( $status->hasMessage( 'file-exists-sharedrepo' ) |
349 | && $this->permManager->userHasRight( $user, 'reupload-shared' ) |
350 | ) { |
351 | $out->addHTML( |
352 | Html::warningBox( |
353 | $out->msg( 'move-over-sharedrepo', $newTitle->getPrefixedText() )->parse() |
354 | ) |
355 | ); |
356 | $moveOverShared = true; |
357 | $status = StatusValue::newGood(); |
358 | } |
359 | } |
360 | |
361 | $oldTalk = $this->oldTitle->getTalkPageIfDefined(); |
362 | $oldTitleSubpages = $this->oldTitle->hasSubpages(); |
363 | $oldTitleTalkSubpages = $this->oldTitle->getTalkPageIfDefined()->hasSubpages(); |
364 | |
365 | $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) && |
366 | $this->permManager->quickUserCan( |
367 | 'move-subpages', |
368 | $user, |
369 | $this->oldTitle |
370 | ); |
371 | |
372 | # We also want to be able to move assoc. subpage talk-pages even if base page |
373 | # has no associated talk page, so || with $oldTitleTalkSubpages. |
374 | $considerTalk = !$this->oldTitle->isTalkPage() && |
375 | ( $oldTalk->exists() |
376 | || ( $oldTitleTalkSubpages && $canMoveSubpage ) ); |
377 | |
378 | if ( $this->getConfig()->get( MainConfigNames::FixDoubleRedirects ) ) { |
379 | $queryBuilder = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder() |
380 | ->select( '1' ) |
381 | ->from( 'redirect' ) |
382 | ->where( [ 'rd_namespace' => $this->oldTitle->getNamespace() ] ) |
383 | ->andWhere( [ 'rd_title' => $this->oldTitle->getDBkey() ] ) |
384 | ->andWhere( [ 'rd_interwiki' => '' ] ); |
385 | |
386 | $hasRedirects = (bool)$queryBuilder->caller( __METHOD__ )->fetchField(); |
387 | } else { |
388 | $hasRedirects = false; |
389 | } |
390 | |
391 | $messages = $status->getMessages(); |
392 | if ( $messages ) { |
393 | if ( $status instanceof PermissionStatus ) { |
394 | $action_desc = $this->msg( 'action-move' )->plain(); |
395 | $errMsgHtml = $this->msg( 'permissionserrorstext-withaction', |
396 | count( $messages ), $action_desc )->parseAsBlock(); |
397 | } else { |
398 | $errMsgHtml = $this->msg( 'cannotmove', count( $messages ) )->parseAsBlock(); |
399 | } |
400 | |
401 | if ( count( $messages ) == 1 ) { |
402 | $errMsgHtml .= $this->msg( $messages[0] )->parseAsBlock(); |
403 | } else { |
404 | $errStr = []; |
405 | |
406 | foreach ( $messages as $msg ) { |
407 | $errStr[] = $this->msg( $msg )->parse(); |
408 | } |
409 | |
410 | $errMsgHtml .= '<ul><li>' . implode( "</li>\n<li>", $errStr ) . "</li></ul>\n"; |
411 | } |
412 | $out->addHTML( Html::errorBox( $errMsgHtml ) ); |
413 | } |
414 | |
415 | if ( $this->restrictionStore->isProtected( $this->oldTitle, 'move' ) ) { |
416 | # Is the title semi-protected? |
417 | if ( $this->restrictionStore->isSemiProtected( $this->oldTitle, 'move' ) ) { |
418 | $noticeMsg = 'semiprotectedpagemovewarning'; |
419 | } else { |
420 | # Then it must be protected based on static groups (regular) |
421 | $noticeMsg = 'protectedpagemovewarning'; |
422 | } |
423 | LogEventsList::showLogExtract( |
424 | $out, |
425 | 'protect', |
426 | $this->oldTitle, |
427 | '', |
428 | [ 'lim' => 1, 'msgKey' => $noticeMsg ] |
429 | ); |
430 | } |
431 | |
432 | // Length limit for wpReason and wpNewTitleMain is enforced in the |
433 | // mediawiki.special.movePage module |
434 | |
435 | $immovableNamespaces = []; |
436 | foreach ( $this->getLanguage()->getNamespaces() as $nsId => $_ ) { |
437 | if ( !$this->nsInfo->isMovable( $nsId ) ) { |
438 | $immovableNamespaces[] = $nsId; |
439 | } |
440 | } |
441 | |
442 | $out->enableOOUI(); |
443 | $fields = []; |
444 | |
445 | $fields[] = new FieldLayout( |
446 | new ComplexTitleInputWidget( [ |
447 | 'id' => 'wpNewTitle', |
448 | 'namespace' => [ |
449 | 'id' => 'wpNewTitleNs', |
450 | 'name' => 'wpNewTitleNs', |
451 | 'value' => $newTitle->getNamespace(), |
452 | 'exclude' => $immovableNamespaces, |
453 | ], |
454 | 'title' => [ |
455 | 'id' => 'wpNewTitleMain', |
456 | 'name' => 'wpNewTitleMain', |
457 | 'value' => $newTitle->getText(), |
458 | // Inappropriate, since we're expecting the user to input a non-existent page's title |
459 | 'suggestions' => false, |
460 | ], |
461 | 'infusable' => true, |
462 | ] ), |
463 | [ |
464 | 'label' => $this->msg( 'newtitle' )->text(), |
465 | 'align' => 'top', |
466 | ] |
467 | ); |
468 | |
469 | $options = Html::listDropdownOptions( |
470 | $this->msg( 'movepage-reason-dropdown' ) |
471 | ->page( $this->oldTitle ) |
472 | ->inContentLanguage() |
473 | ->text(), |
474 | [ 'other' => $this->msg( 'movereasonotherlist' )->text() ] |
475 | ); |
476 | $options = Html::listDropdownOptionsOoui( $options ); |
477 | |
478 | $fields[] = new FieldLayout( |
479 | new DropdownInputWidget( [ |
480 | 'name' => 'wpReasonList', |
481 | 'inputId' => 'wpReasonList', |
482 | 'infusable' => true, |
483 | 'value' => $this->getRequest()->getText( 'wpReasonList', 'other' ), |
484 | 'options' => $options, |
485 | ] ), |
486 | [ |
487 | 'label' => $this->msg( 'movereason' )->text(), |
488 | 'align' => 'top', |
489 | ] |
490 | ); |
491 | |
492 | // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP |
493 | // (e.g. emojis) count for two each. This limit is overridden in JS to instead count |
494 | // Unicode codepoints. |
495 | $fields[] = new FieldLayout( |
496 | new TextInputWidget( [ |
497 | 'name' => 'wpReason', |
498 | 'id' => 'wpReason', |
499 | 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT, |
500 | 'infusable' => true, |
501 | 'value' => $this->getRequest()->getText( 'wpReason' ), |
502 | ] ), |
503 | [ |
504 | 'label' => $this->msg( 'moveotherreason' )->text(), |
505 | 'align' => 'top', |
506 | ] |
507 | ); |
508 | |
509 | if ( $considerTalk ) { |
510 | $fields[] = new FieldLayout( |
511 | new CheckboxInputWidget( [ |
512 | 'name' => 'wpMovetalk', |
513 | 'id' => 'wpMovetalk', |
514 | 'value' => '1', |
515 | 'selected' => $this->moveTalk, |
516 | ] ), |
517 | [ |
518 | 'label' => $this->msg( 'movetalk' )->text(), |
519 | 'help' => new HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ), |
520 | 'helpInline' => true, |
521 | 'align' => 'inline', |
522 | 'id' => 'wpMovetalk-field', |
523 | ] |
524 | ); |
525 | } |
526 | |
527 | if ( $this->permManager->userHasRight( $user, 'suppressredirect' ) ) { |
528 | if ( $createRedirect ) { |
529 | $isChecked = $this->leaveRedirect; |
530 | $isDisabled = false; |
531 | } else { |
532 | $isChecked = false; |
533 | $isDisabled = true; |
534 | } |
535 | $fields[] = new FieldLayout( |
536 | new CheckboxInputWidget( [ |
537 | 'name' => 'wpLeaveRedirect', |
538 | 'id' => 'wpLeaveRedirect', |
539 | 'value' => '1', |
540 | 'selected' => $isChecked, |
541 | 'disabled' => $isDisabled, |
542 | ] ), |
543 | [ |
544 | 'label' => $this->msg( 'move-leave-redirect' )->text(), |
545 | 'align' => 'inline', |
546 | ] |
547 | ); |
548 | } |
549 | |
550 | if ( $hasRedirects ) { |
551 | $fields[] = new FieldLayout( |
552 | new CheckboxInputWidget( [ |
553 | 'name' => 'wpFixRedirects', |
554 | 'id' => 'wpFixRedirects', |
555 | 'value' => '1', |
556 | 'selected' => $this->fixRedirects, |
557 | ] ), |
558 | [ |
559 | 'label' => $this->msg( 'fix-double-redirects' )->text(), |
560 | 'align' => 'inline', |
561 | ] |
562 | ); |
563 | } |
564 | |
565 | if ( $canMoveSubpage ) { |
566 | $maximumMovedPages = $this->getConfig()->get( MainConfigNames::MaximumMovedPages ); |
567 | $fields[] = new FieldLayout( |
568 | new CheckboxInputWidget( [ |
569 | 'name' => 'wpMovesubpages', |
570 | 'id' => 'wpMovesubpages', |
571 | 'value' => '1', |
572 | 'selected' => $this->moveSubpages, |
573 | ] ), |
574 | [ |
575 | 'label' => new HtmlSnippet( |
576 | $this->msg( |
577 | ( $this->oldTitle->hasSubpages() |
578 | ? 'move-subpages' |
579 | : 'move-talk-subpages' ) |
580 | )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse() |
581 | ), |
582 | 'align' => 'inline', |
583 | ] |
584 | ); |
585 | } |
586 | |
587 | # Don't allow watching if user is not logged in |
588 | if ( $user->isRegistered() ) { |
589 | $watchChecked = ( $this->watch || $this->userOptionsLookup->getBoolOption( $user, 'watchmoves' ) |
590 | || $this->watchlistManager->isWatched( $user, $this->oldTitle ) ); |
591 | $fields[] = new FieldLayout( |
592 | new CheckboxInputWidget( [ |
593 | 'name' => 'wpWatch', |
594 | 'id' => 'watch', # ew |
595 | 'value' => '1', |
596 | 'selected' => $watchChecked, |
597 | ] ), |
598 | [ |
599 | 'label' => $this->msg( 'move-watch' )->text(), |
600 | 'align' => 'inline', |
601 | ] |
602 | ); |
603 | } |
604 | |
605 | $hiddenFields = ''; |
606 | if ( $moveOverShared ) { |
607 | $hiddenFields .= Html::hidden( 'wpMoveOverSharedFile', '1' ); |
608 | } |
609 | |
610 | if ( $deleteAndMove ) { |
611 | $fields[] = new FieldLayout( |
612 | new CheckboxInputWidget( [ |
613 | 'name' => 'wpDeleteAndMove', |
614 | 'id' => 'wpDeleteAndMove', |
615 | 'value' => '1', |
616 | ] ), |
617 | [ |
618 | 'label' => $this->msg( 'delete_and_move_confirm', $newTitle->getPrefixedText() )->text(), |
619 | 'align' => 'inline', |
620 | ] |
621 | ); |
622 | } |
623 | |
624 | $fields[] = new FieldLayout( |
625 | new ButtonInputWidget( [ |
626 | 'name' => 'wpMove', |
627 | 'value' => $this->msg( 'movepagebtn' )->text(), |
628 | 'label' => $this->msg( 'movepagebtn' )->text(), |
629 | 'flags' => [ 'primary', 'progressive' ], |
630 | 'type' => 'submit', |
631 | ] ), |
632 | [ |
633 | 'align' => 'top', |
634 | ] |
635 | ); |
636 | |
637 | $fieldset = new FieldsetLayout( [ |
638 | 'label' => $this->msg( 'move-page-legend' )->text(), |
639 | 'id' => 'mw-movepage-table', |
640 | 'items' => $fields, |
641 | ] ); |
642 | |
643 | $form = new FormLayout( [ |
644 | 'method' => 'post', |
645 | 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ), |
646 | 'id' => 'movepage', |
647 | ] ); |
648 | $form->appendContent( |
649 | $fieldset, |
650 | new HtmlSnippet( |
651 | $hiddenFields . |
652 | Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) . |
653 | Html::hidden( 'wpEditToken', $user->getEditToken() ) |
654 | ) |
655 | ); |
656 | |
657 | $out->addHTML( |
658 | new PanelLayout( [ |
659 | 'classes' => [ 'movepage-wrapper' ], |
660 | 'expanded' => false, |
661 | 'padded' => true, |
662 | 'framed' => true, |
663 | 'content' => $form, |
664 | ] ) |
665 | ); |
666 | if ( $this->getAuthority()->isAllowed( 'editinterface' ) ) { |
667 | $link = $this->getLinkRenderer()->makeKnownLink( |
668 | $this->msg( 'movepage-reason-dropdown' )->inContentLanguage()->getTitle(), |
669 | $this->msg( 'movepage-edit-reasonlist' )->text(), |
670 | [], |
671 | [ 'action' => 'edit' ] |
672 | ); |
673 | $out->addHTML( Html::rawElement( 'p', [ 'class' => 'mw-movepage-editreasons' ], $link ) ); |
674 | } |
675 | |
676 | $this->showLogFragment( $this->oldTitle ); |
677 | $this->showSubpages( $this->oldTitle ); |
678 | } |
679 | |
680 | private function doSubmit() { |
681 | $user = $this->getUser(); |
682 | |
683 | if ( $user->pingLimiter( 'move' ) ) { |
684 | throw new ThrottledError; |
685 | } |
686 | |
687 | $ot = $this->oldTitle; |
688 | $nt = $this->newTitle; |
689 | |
690 | # don't allow moving to pages with # in |
691 | if ( !$nt || $nt->hasFragment() ) { |
692 | $this->showForm( StatusValue::newFatal( 'badtitletext' ) ); |
693 | |
694 | return; |
695 | } |
696 | |
697 | # Show a warning if the target file exists on a shared repo |
698 | if ( $nt->getNamespace() === NS_FILE |
699 | && !( $this->moveOverShared && $this->permManager->userHasRight( $user, 'reupload-shared' ) ) |
700 | && !$this->repoGroup->getLocalRepo()->findFile( $nt ) |
701 | && $this->repoGroup->findFile( $nt ) |
702 | ) { |
703 | $this->showForm( StatusValue::newFatal( 'file-exists-sharedrepo' ) ); |
704 | |
705 | return; |
706 | } |
707 | |
708 | # Delete to make way if requested |
709 | if ( $this->deleteAndMove ) { |
710 | $redir2 = $nt->isSingleRevRedirect(); |
711 | |
712 | $permStatus = $this->permManager->getPermissionStatus( |
713 | $redir2 ? 'delete-redirect' : 'delete', |
714 | $user, $nt |
715 | ); |
716 | if ( !$permStatus->isGood() ) { |
717 | if ( $redir2 ) { |
718 | if ( !$this->permManager->userCan( 'delete', $user, $nt ) ) { |
719 | // Cannot delete-redirect, or delete normally |
720 | $this->showForm( $permStatus ); |
721 | return; |
722 | } else { |
723 | // Cannot delete-redirect, but can delete normally, |
724 | // so log as a normal deletion |
725 | $redir2 = false; |
726 | } |
727 | } else { |
728 | // Cannot delete normally |
729 | $this->showForm( $permStatus ); |
730 | return; |
731 | } |
732 | } |
733 | |
734 | $page = $this->wikiPageFactory->newFromTitle( $nt ); |
735 | $delPage = $this->deletePageFactory->newDeletePage( $page, $user ); |
736 | |
737 | // Small safety margin to guard against concurrent edits |
738 | if ( $delPage->isBatchedDelete( 5 ) ) { |
739 | $this->showForm( StatusValue::newFatal( 'movepage-delete-first' ) ); |
740 | |
741 | return; |
742 | } |
743 | |
744 | $reason = $this->msg( 'delete_and_move_reason', $ot )->inContentLanguage()->text(); |
745 | |
746 | // Delete an associated image if there is |
747 | if ( $nt->getNamespace() === NS_FILE ) { |
748 | $file = $this->repoGroup->getLocalRepo()->newFile( $nt ); |
749 | $file->load( IDBAccessObject::READ_LATEST ); |
750 | if ( $file->exists() ) { |
751 | $file->deleteFile( $reason, $user, false ); |
752 | } |
753 | } |
754 | |
755 | $deletionLog = $redir2 ? 'delete_redir2' : 'delete'; |
756 | $deleteStatus = $delPage |
757 | ->setLogSubtype( $deletionLog ) |
758 | // Should be redundant thanks to the isBatchedDelete check above. |
759 | ->forceImmediate( true ) |
760 | ->deleteUnsafe( $reason ); |
761 | |
762 | if ( !$deleteStatus->isGood() ) { |
763 | $this->showForm( $deleteStatus ); |
764 | |
765 | return; |
766 | } |
767 | } |
768 | |
769 | $handler = $this->contentHandlerFactory->getContentHandler( $ot->getContentModel() ); |
770 | |
771 | if ( !$handler->supportsRedirects() || ( |
772 | // Do not create redirects for wikitext message overrides (T376399). |
773 | // Maybe one day they will have a custom content model and this special case won't be needed. |
774 | $ot->getNamespace() === NS_MEDIAWIKI && |
775 | $ot->getContentModel() === CONTENT_MODEL_WIKITEXT |
776 | ) ) { |
777 | $createRedirect = false; |
778 | } elseif ( $this->permManager->userHasRight( $user, 'suppressredirect' ) ) { |
779 | $createRedirect = $this->leaveRedirect; |
780 | } else { |
781 | $createRedirect = true; |
782 | } |
783 | |
784 | # Do the actual move. |
785 | $mp = $this->movePageFactory->newMovePage( $ot, $nt ); |
786 | |
787 | if ( $ot->isTalkPage() || $nt->isTalkPage() ) { |
788 | $this->moveTalk = false; |
789 | } |
790 | if ( $this->moveSubpages ) { |
791 | $this->moveSubpages = $this->permManager->userCan( 'move-subpages', $user, $ot ); |
792 | } |
793 | |
794 | # check whether the requested actions are permitted / possible |
795 | $permStatus = $mp->authorizeMove( $this->getAuthority(), $this->reason ); |
796 | if ( !$permStatus->isOK() ) { |
797 | $this->showForm( $permStatus ); |
798 | return; |
799 | } |
800 | $status = $mp->moveIfAllowed( $this->getAuthority(), $this->reason, $createRedirect ); |
801 | if ( !$status->isOK() ) { |
802 | $this->showForm( $status ); |
803 | return; |
804 | } |
805 | |
806 | if ( $this->getConfig()->get( MainConfigNames::FixDoubleRedirects ) && |
807 | $this->fixRedirects ) { |
808 | DoubleRedirectJob::fixRedirects( 'move', $ot ); |
809 | } |
810 | |
811 | $out = $this->getOutput(); |
812 | $out->setPageTitleMsg( $this->msg( 'pagemovedsub' ) ); |
813 | |
814 | $linkRenderer = $this->getLinkRenderer(); |
815 | $oldLink = $linkRenderer->makeLink( |
816 | $ot, |
817 | null, |
818 | [ 'id' => 'movepage-oldlink' ], |
819 | [ 'redirect' => 'no' ] |
820 | ); |
821 | $newLink = $linkRenderer->makeKnownLink( |
822 | $nt, |
823 | null, |
824 | [ 'id' => 'movepage-newlink' ] |
825 | ); |
826 | $oldText = $ot->getPrefixedText(); |
827 | $newText = $nt->getPrefixedText(); |
828 | |
829 | if ( $status->getValue()['redirectRevision'] !== null ) { |
830 | $msgName = 'movepage-moved-redirect'; |
831 | } else { |
832 | $msgName = 'movepage-moved-noredirect'; |
833 | } |
834 | |
835 | $out->addHTML( $this->msg( 'movepage-moved' )->rawParams( $oldLink, |
836 | $newLink )->params( $oldText, $newText )->parseAsBlock() ); |
837 | $out->addWikiMsg( $msgName ); |
838 | |
839 | $this->getHookRunner()->onSpecialMovepageAfterMove( $this, $ot, $nt ); |
840 | |
841 | /* |
842 | * Now we move extra pages we've been asked to move: subpages and talk |
843 | * pages. |
844 | * |
845 | * First, make a list of id's. This might be marginally less efficient |
846 | * than a more direct method, but this is not a highly performance-cri- |
847 | * tical code path and readable code is more important here. |
848 | * |
849 | * If the target namespace doesn't allow subpages, moving with subpages |
850 | * would mean that you couldn't move them back in one operation, which |
851 | * is bad. |
852 | * @todo FIXME: A specific error message should be given in this case. |
853 | */ |
854 | |
855 | // @todo FIXME: Use MovePage::moveSubpages() here |
856 | $dbr = $this->dbProvider->getReplicaDatabase(); |
857 | if ( $this->moveSubpages && ( |
858 | $this->nsInfo->hasSubpages( $nt->getNamespace() ) || ( |
859 | $this->moveTalk |
860 | && $this->nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() ) |
861 | ) |
862 | ) ) { |
863 | $conds = [ |
864 | $dbr->expr( |
865 | 'page_title', |
866 | IExpression::LIKE, |
867 | new LikeValue( $ot->getDBkey() . '/', $dbr->anyString() ) |
868 | )->or( 'page_title', '=', $ot->getDBkey() ) |
869 | ]; |
870 | $conds['page_namespace'] = []; |
871 | if ( $this->nsInfo->hasSubpages( $nt->getNamespace() ) ) { |
872 | $conds['page_namespace'][] = $ot->getNamespace(); |
873 | } |
874 | if ( $this->moveTalk && |
875 | $this->nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() ) |
876 | ) { |
877 | $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace(); |
878 | } |
879 | } elseif ( $this->moveTalk ) { |
880 | $conds = [ |
881 | 'page_namespace' => $ot->getTalkPage()->getNamespace(), |
882 | 'page_title' => $ot->getDBkey() |
883 | ]; |
884 | } else { |
885 | # Skip the query |
886 | $conds = null; |
887 | } |
888 | |
889 | $extraPages = []; |
890 | if ( $conds !== null ) { |
891 | $extraPages = $this->titleFactory->newTitleArrayFromResult( |
892 | $dbr->newSelectQueryBuilder() |
893 | ->select( [ 'page_id', 'page_namespace', 'page_title' ] ) |
894 | ->from( 'page' ) |
895 | ->where( $conds ) |
896 | ->caller( __METHOD__ )->fetchResultSet() |
897 | ); |
898 | } |
899 | |
900 | $extraOutput = []; |
901 | $count = 1; |
902 | foreach ( $extraPages as $oldSubpage ) { |
903 | if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) { |
904 | # Already did this one. |
905 | continue; |
906 | } |
907 | |
908 | $newPageName = preg_replace( |
909 | '#^' . preg_quote( $ot->getDBkey(), '#' ) . '#', |
910 | StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234 |
911 | $oldSubpage->getDBkey() |
912 | ); |
913 | |
914 | if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) { |
915 | // Moving a subpage from a subject namespace to a talk namespace or vice-versa |
916 | $newNs = $nt->getNamespace(); |
917 | } elseif ( $oldSubpage->isTalkPage() ) { |
918 | $newNs = $nt->getTalkPage()->getNamespace(); |
919 | } else { |
920 | $newNs = $nt->getSubjectPage()->getNamespace(); |
921 | } |
922 | |
923 | # T16385: we need makeTitleSafe because the new page names may |
924 | # be longer than 255 characters. |
925 | $newSubpage = Title::makeTitleSafe( $newNs, $newPageName ); |
926 | if ( !$newSubpage ) { |
927 | $oldLink = $linkRenderer->makeKnownLink( $oldSubpage ); |
928 | $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink ) |
929 | ->params( Title::makeName( $newNs, $newPageName ) )->escaped(); |
930 | continue; |
931 | } |
932 | |
933 | $mp = $this->movePageFactory->newMovePage( $oldSubpage, $newSubpage ); |
934 | # This was copy-pasted from Renameuser, bleh. |
935 | if ( $newSubpage->exists() && !$mp->isValidMove()->isOK() ) { |
936 | $link = $linkRenderer->makeKnownLink( $newSubpage ); |
937 | $extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped(); |
938 | } else { |
939 | $status = $mp->moveIfAllowed( $this->getAuthority(), $this->reason, $createRedirect ); |
940 | |
941 | if ( $status->isOK() ) { |
942 | if ( $this->fixRedirects ) { |
943 | DoubleRedirectJob::fixRedirects( 'move', $oldSubpage ); |
944 | } |
945 | $oldLink = $linkRenderer->makeLink( |
946 | $oldSubpage, |
947 | null, |
948 | [], |
949 | [ 'redirect' => 'no' ] |
950 | ); |
951 | |
952 | $newLink = $linkRenderer->makeKnownLink( $newSubpage ); |
953 | $extraOutput[] = $this->msg( 'movepage-page-moved' ) |
954 | ->rawParams( $oldLink, $newLink )->escaped(); |
955 | ++$count; |
956 | |
957 | $maximumMovedPages = |
958 | $this->getConfig()->get( MainConfigNames::MaximumMovedPages ); |
959 | if ( $count >= $maximumMovedPages ) { |
960 | $extraOutput[] = $this->msg( 'movepage-max-pages' ) |
961 | ->numParams( $maximumMovedPages )->escaped(); |
962 | break; |
963 | } |
964 | } else { |
965 | $oldLink = $linkRenderer->makeKnownLink( $oldSubpage ); |
966 | $newLink = $linkRenderer->makeLink( $newSubpage ); |
967 | $extraOutput[] = $this->msg( 'movepage-page-unmoved' ) |
968 | ->rawParams( $oldLink, $newLink )->escaped(); |
969 | } |
970 | } |
971 | } |
972 | |
973 | if ( $extraOutput !== [] ) { |
974 | $out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" ); |
975 | } |
976 | |
977 | # Deal with watches (we don't watch subpages) |
978 | $this->watchlistManager->setWatch( $this->watch, $this->getAuthority(), $ot ); |
979 | $this->watchlistManager->setWatch( $this->watch, $this->getAuthority(), $nt ); |
980 | } |
981 | |
982 | private function showLogFragment( $title ) { |
983 | $moveLogPage = new LogPage( 'move' ); |
984 | $out = $this->getOutput(); |
985 | $out->addHTML( Xml::element( 'h2', null, $moveLogPage->getName()->text() ) ); |
986 | LogEventsList::showLogExtract( $out, 'move', $title ); |
987 | } |
988 | |
989 | /** |
990 | * Show subpages of the page being moved. Section is not shown if both current |
991 | * namespace does not support subpages and no talk subpages were found. |
992 | * |
993 | * @param Title $title Page being moved. |
994 | */ |
995 | private function showSubpages( $title ) { |
996 | $maximumMovedPages = $this->getConfig()->get( MainConfigNames::MaximumMovedPages ); |
997 | $nsHasSubpages = $this->nsInfo->hasSubpages( $title->getNamespace() ); |
998 | $subpages = $title->getSubpages( $maximumMovedPages + 1 ); |
999 | $count = $subpages instanceof TitleArrayFromResult ? $subpages->count() : 0; |
1000 | |
1001 | $titleIsTalk = $title->isTalkPage(); |
1002 | $subpagesTalk = $title->getTalkPage()->getSubpages( $maximumMovedPages + 1 ); |
1003 | $countTalk = $subpagesTalk instanceof TitleArrayFromResult ? $subpagesTalk->count() : 0; |
1004 | $totalCount = $count + $countTalk; |
1005 | |
1006 | if ( !$nsHasSubpages && $countTalk == 0 ) { |
1007 | return; |
1008 | } |
1009 | |
1010 | $this->getOutput()->wrapWikiMsg( |
1011 | '== $1 ==', |
1012 | [ 'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ] |
1013 | ); |
1014 | |
1015 | if ( $nsHasSubpages ) { |
1016 | $this->showSubpagesList( |
1017 | $subpages, $count, 'movesubpagetext', 'movesubpagetext-truncated', true |
1018 | ); |
1019 | } |
1020 | |
1021 | if ( !$titleIsTalk && $countTalk > 0 ) { |
1022 | $this->showSubpagesList( |
1023 | $subpagesTalk, $countTalk, 'movesubpagetalktext', 'movesubpagetalktext-truncated' |
1024 | ); |
1025 | } |
1026 | } |
1027 | |
1028 | private function showSubpagesList( $subpages, $pagecount, $msg, $truncatedMsg, $noSubpageMsg = false ) { |
1029 | $out = $this->getOutput(); |
1030 | |
1031 | # No subpages. |
1032 | if ( $pagecount == 0 && $noSubpageMsg ) { |
1033 | $out->addWikiMsg( 'movenosubpage' ); |
1034 | return; |
1035 | } |
1036 | |
1037 | $maximumMovedPages = $this->getConfig()->get( MainConfigNames::MaximumMovedPages ); |
1038 | |
1039 | if ( $pagecount > $maximumMovedPages ) { |
1040 | $subpages = $this->truncateSubpagesList( $subpages ); |
1041 | $out->addWikiMsg( $truncatedMsg, $this->getLanguage()->formatNum( $maximumMovedPages ) ); |
1042 | } else { |
1043 | $out->addWikiMsg( $msg, $this->getLanguage()->formatNum( $pagecount ) ); |
1044 | } |
1045 | $out->addHTML( "<ul>\n" ); |
1046 | |
1047 | $linkBatch = $this->linkBatchFactory->newLinkBatch( $subpages ); |
1048 | $linkBatch->setCaller( __METHOD__ ); |
1049 | $linkBatch->execute(); |
1050 | $linkRenderer = $this->getLinkRenderer(); |
1051 | |
1052 | foreach ( $subpages as $subpage ) { |
1053 | $link = $linkRenderer->makeLink( $subpage ); |
1054 | $out->addHTML( "<li>$link</li>\n" ); |
1055 | } |
1056 | $out->addHTML( "</ul>\n" ); |
1057 | } |
1058 | |
1059 | private function truncateSubpagesList( iterable $subpages ): array { |
1060 | $returnArray = []; |
1061 | foreach ( $subpages as $subpage ) { |
1062 | $returnArray[] = $subpage; |
1063 | if ( count( $returnArray ) >= $this->getConfig()->get( MainConfigNames::MaximumMovedPages ) ) { |
1064 | break; |
1065 | } |
1066 | } |
1067 | return $returnArray; |
1068 | } |
1069 | |
1070 | /** |
1071 | * Return an array of subpages beginning with $search that this special page will accept. |
1072 | * |
1073 | * @param string $search Prefix to search for |
1074 | * @param int $limit Maximum number of results to return (usually 10) |
1075 | * @param int $offset Number of results to skip (usually 0) |
1076 | * @return string[] Matching subpages |
1077 | */ |
1078 | public function prefixSearchSubpages( $search, $limit, $offset ) { |
1079 | return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory ); |
1080 | } |
1081 | |
1082 | protected function getGroupName() { |
1083 | return 'pagetools'; |
1084 | } |
1085 | } |
1086 | |
1087 | /** |
1088 | * Retain the old class name for backwards compatibility. |
1089 | * @deprecated since 1.40 |
1090 | */ |
1091 | class_alias( SpecialMovePage::class, 'MovePageForm' ); |