MediaWiki REL1_35
SpecialMovepage.php
Go to the documentation of this file.
1<?php
26
34 protected $oldTitle = null;
35
37 protected $newTitle;
38
40 protected $reason;
41
42 // Checks
43
45 protected $moveTalk;
46
48 protected $deleteAndMove;
49
51 protected $moveSubpages;
52
54 protected $fixRedirects;
55
57 protected $leaveRedirect;
58
60 protected $moveOverShared;
61
62 private $watch = false;
63
65 private $permManager;
66
67 public function __construct() {
68 parent::__construct( 'Movepage' );
69 $this->permManager = MediaWikiServices::getInstance()->getPermissionManager();
70 }
71
72 public function doesWrites() {
73 return true;
74 }
75
76 public function execute( $par ) {
78
79 $this->checkReadOnly();
80
81 $this->setHeaders();
82 $this->outputHeader();
83
84 $request = $this->getRequest();
85 $target = $par ?? $request->getVal( 'target' );
86
87 // Yes, the use of getVal() and getText() is wanted, see T22365
88
89 $oldTitleText = $request->getVal( 'wpOldTitle', $target );
90 $this->oldTitle = Title::newFromText( $oldTitleText );
91
92 if ( !$this->oldTitle ) {
93 // Either oldTitle wasn't passed, or newFromText returned null
94 throw new ErrorPageError( 'notargettitle', 'notargettext' );
95 }
96 $this->getOutput()->addBacklinkSubtitle( $this->oldTitle );
97
98 if ( !$this->oldTitle->exists() ) {
99 throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
100 }
101
102 $newTitleTextMain = $request->getText( 'wpNewTitleMain' );
103 $newTitleTextNs = $request->getInt( 'wpNewTitleNs', $this->oldTitle->getNamespace() );
104 // Backwards compatibility for forms submitting here from other sources
105 // which is more common than it should be..
106 $newTitleText_bc = $request->getText( 'wpNewTitle' );
107 $this->newTitle = strlen( $newTitleText_bc ) > 0
108 ? Title::newFromText( $newTitleText_bc )
109 : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
110
111 $user = $this->getUser();
112
113 # Check rights
114 $permErrors = $this->permManager->getPermissionErrors( 'move', $user, $this->oldTitle );
115 if ( count( $permErrors ) ) {
116 // Auto-block user's IP if the account was "hard" blocked
117 DeferredUpdates::addCallableUpdate( function () use ( $user ) {
118 $user->spreadAnyEditBlock();
119 } );
120 throw new PermissionsError( 'move', $permErrors );
121 }
122
123 $def = !$request->wasPosted();
124
125 $this->reason = $request->getText( 'wpReason' );
126 $this->moveTalk = $request->getBool( 'wpMovetalk', $def );
127 $this->fixRedirects = $request->getBool( 'wpFixRedirects', $def );
128 $this->leaveRedirect = $request->getBool( 'wpLeaveRedirect', $def );
129 // T222953: Tick the "move subpages" box by default
130 $this->moveSubpages = $request->getBool( 'wpMovesubpages', $def );
131 $this->deleteAndMove = $request->getBool( 'wpDeleteAndMove' );
132 $this->moveOverShared = $request->getBool( 'wpMoveOverSharedFile' );
133 $this->watch = $request->getCheck( 'wpWatch' ) && $user->isLoggedIn();
134
135 if ( $request->getVal( 'action' ) == 'submit' && $request->wasPosted()
136 && $user->matchEditToken( $request->getVal( 'wpEditToken' ) )
137 ) {
138 $this->doSubmit();
139 } else {
140 $this->showForm( [] );
141 }
142 }
143
152 protected function showForm( $err, $isPermError = false ) {
153 $this->getSkin()->setRelevantTitle( $this->oldTitle );
154
155 $out = $this->getOutput();
156 $out->setPageTitle( $this->msg( 'move-page', $this->oldTitle->getPrefixedText() ) );
157 $out->addModuleStyles( 'mediawiki.special' );
158 $out->addModules( 'mediawiki.misc-authed-ooui' );
159 $this->addHelpLink( 'Help:Moving a page' );
160
161 $handlerSupportsRedirects = MediaWikiServices::getInstance()
162 ->getContentHandlerFactory()
163 ->getContentHandler( $this->oldTitle->getContentModel() )
164 ->supportsRedirects();
165
166 if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
167 $out->addWikiMsg( 'movepagetext' );
168 } else {
169 $out->addWikiMsg( $handlerSupportsRedirects ?
170 'movepagetext-noredirectfixer' :
171 'movepagetext-noredirectsupport' );
172 }
173
174 if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
175 $out->wrapWikiMsg(
176 "<div class=\"warningbox mw-moveuserpage-warning\">\n$1\n</div>",
177 'moveuserpage-warning'
178 );
179 } elseif ( $this->oldTitle->getNamespace() == NS_CATEGORY ) {
180 $out->wrapWikiMsg(
181 "<div class=\"warningbox mw-movecategorypage-warning\">\n$1\n</div>",
182 'movecategorypage-warning'
183 );
184 }
185
186 $deleteAndMove = false;
187 $moveOverShared = false;
188
189 $user = $this->getUser();
190
191 $newTitle = $this->newTitle;
192
193 if ( !$newTitle ) {
194 # Show the current title as a default
195 # when the form is first opened.
196 $newTitle = $this->oldTitle;
197 } elseif ( !count( $err ) ) {
198 # If a title was supplied, probably from the move log revert
199 # link, check for validity. We can then show some diagnostic
200 # information and save a click.
201 $mp = new MovePage( $this->oldTitle, $newTitle );
202 $status = $mp->isValidMove();
203 $status->merge( $mp->checkPermissions( $user, null ) );
204 if ( $status->getErrors() ) {
205 $err = $status->getErrorsArray();
206 }
207 }
208
209 if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'articleexists'
210 && $this->permManager->quickUserCan( 'delete', $user, $newTitle )
211 ) {
212 $out->wrapWikiMsg(
213 "<div class='warningbox'>\n$1\n</div>\n",
214 [ 'delete_and_move_text', $newTitle->getPrefixedText() ]
215 );
216 $deleteAndMove = true;
217 $err = [];
218 }
219
220 if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'file-exists-sharedrepo'
221 && $this->permManager->userHasRight( $user, 'reupload-shared' )
222 ) {
223 $out->wrapWikiMsg(
224 "<div class='warningbox'>\n$1\n</div>\n",
225 [
226 'move-over-sharedrepo',
228 ]
229 );
230 $moveOverShared = true;
231 $err = [];
232 }
233
234 $oldTalk = $this->oldTitle->getTalkPage();
235 $oldTitleSubpages = $this->oldTitle->hasSubpages();
236 $oldTitleTalkSubpages = $this->oldTitle->getTalkPage()->hasSubpages();
237
238 $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
239 !count( $this->permManager->getPermissionErrors(
240 'move-subpages',
241 $user,
242 $this->oldTitle
243 ) );
244
245 # We also want to be able to move assoc. subpage talk-pages even if base page
246 # has no associated talk page, so || with $oldTitleTalkSubpages.
247 $considerTalk = !$this->oldTitle->isTalkPage() &&
248 ( $oldTalk->exists()
249 || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
250
252 if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
253 $hasRedirects = $dbr->selectField( 'redirect', '1',
254 [
255 'rd_namespace' => $this->oldTitle->getNamespace(),
256 'rd_title' => $this->oldTitle->getDBkey(),
257 ], __METHOD__ );
258 } else {
259 $hasRedirects = false;
260 }
261
262 if ( count( $err ) ) {
263 '@phan-var array[] $err';
264 if ( $isPermError ) {
265 $action_desc = $this->msg( 'action-move' )->plain();
266 $errMsgHtml = $this->msg( 'permissionserrorstext-withaction',
267 count( $err ), $action_desc )->parseAsBlock();
268 } else {
269 $errMsgHtml = $this->msg( 'cannotmove', count( $err ) )->parseAsBlock();
270 }
271
272 if ( count( $err ) == 1 ) {
273 $errMsg = $err[0];
274 $errMsgName = array_shift( $errMsg );
275
276 if ( $errMsgName == 'hookaborted' ) {
277 $errMsgHtml .= "<p>{$errMsg[0]}</p>\n";
278 } else {
279 $errMsgHtml .= $this->msg( $errMsgName, $errMsg )->parseAsBlock();
280 }
281 } else {
282 $errStr = [];
283
284 foreach ( $err as $errMsg ) {
285 if ( $errMsg[0] == 'hookaborted' ) {
286 $errStr[] = $errMsg[1];
287 } else {
288 $errMsgName = array_shift( $errMsg );
289 $errStr[] = $this->msg( $errMsgName, $errMsg )->parse();
290 }
291 }
292
293 $errMsgHtml .= '<ul><li>' . implode( "</li>\n<li>", $errStr ) . "</li></ul>\n";
294 }
295 $out->addHTML( Html::errorBox( $errMsgHtml ) );
296 }
297
298 if ( $this->oldTitle->isProtected( 'move' ) ) {
299 # Is the title semi-protected?
300 if ( $this->oldTitle->isSemiProtected( 'move' ) ) {
301 $noticeMsg = 'semiprotectedpagemovewarning';
302 } else {
303 # Then it must be protected based on static groups (regular)
304 $noticeMsg = 'protectedpagemovewarning';
305 }
306 $out->addHTML( "<div class='warningbox mw-warning-with-logexcerpt'>\n" );
307 $out->addWikiMsg( $noticeMsg );
308 LogEventsList::showLogExtract(
309 $out,
310 'protect',
311 $this->oldTitle,
312 '',
313 [ 'lim' => 1 ]
314 );
315 $out->addHTML( "</div>\n" );
316 }
317
318 // Length limit for wpReason and wpNewTitleMain is enforced in the
319 // mediawiki.special.movePage module
320
321 $immovableNamespaces = [];
322 $namespaceInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
323 foreach ( array_keys( $this->getLanguage()->getNamespaces() ) as $nsId ) {
324 if ( !$namespaceInfo->isMovable( $nsId ) ) {
325 $immovableNamespaces[] = $nsId;
326 }
327 }
328
329 $out->enableOOUI();
330 $fields = [];
331
332 $fields[] = new OOUI\FieldLayout(
333 new MediaWiki\Widget\ComplexTitleInputWidget( [
334 'id' => 'wpNewTitle',
335 'namespace' => [
336 'id' => 'wpNewTitleNs',
337 'name' => 'wpNewTitleNs',
338 'value' => $newTitle->getNamespace(),
339 'exclude' => $immovableNamespaces,
340 ],
341 'title' => [
342 'id' => 'wpNewTitleMain',
343 'name' => 'wpNewTitleMain',
344 'value' => $newTitle->getText(),
345 // Inappropriate, since we're expecting the user to input a non-existent page's title
346 'suggestions' => false,
347 ],
348 'infusable' => true,
349 ] ),
350 [
351 'label' => $this->msg( 'newtitle' )->text(),
352 'align' => 'top',
353 ]
354 );
355
356 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
357 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
358 // Unicode codepoints.
359 $fields[] = new OOUI\FieldLayout(
360 new OOUI\TextInputWidget( [
361 'name' => 'wpReason',
362 'id' => 'wpReason',
363 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
364 'infusable' => true,
365 'value' => $this->reason,
366 ] ),
367 [
368 'label' => $this->msg( 'movereason' )->text(),
369 'align' => 'top',
370 ]
371 );
372
373 if ( $considerTalk ) {
374 $fields[] = new OOUI\FieldLayout(
375 new OOUI\CheckboxInputWidget( [
376 'name' => 'wpMovetalk',
377 'id' => 'wpMovetalk',
378 'value' => '1',
379 'selected' => $this->moveTalk,
380 ] ),
381 [
382 'label' => $this->msg( 'movetalk' )->text(),
383 'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ),
384 'helpInline' => true,
385 'align' => 'inline',
386 'id' => 'wpMovetalk-field',
387 ]
388 );
389 }
390
391 if ( $this->permManager->userHasRight( $user, 'suppressredirect' )
392 ) {
393 if ( $handlerSupportsRedirects ) {
394 $isChecked = $this->leaveRedirect;
395 $isDisabled = false;
396 } else {
397 $isChecked = false;
398 $isDisabled = true;
399 }
400 $fields[] = new OOUI\FieldLayout(
401 new OOUI\CheckboxInputWidget( [
402 'name' => 'wpLeaveRedirect',
403 'id' => 'wpLeaveRedirect',
404 'value' => '1',
405 'selected' => $isChecked,
406 'disabled' => $isDisabled,
407 ] ),
408 [
409 'label' => $this->msg( 'move-leave-redirect' )->text(),
410 'align' => 'inline',
411 ]
412 );
413 }
414
415 if ( $hasRedirects ) {
416 $fields[] = new OOUI\FieldLayout(
417 new OOUI\CheckboxInputWidget( [
418 'name' => 'wpFixRedirects',
419 'id' => 'wpFixRedirects',
420 'value' => '1',
421 'selected' => $this->fixRedirects,
422 ] ),
423 [
424 'label' => $this->msg( 'fix-double-redirects' )->text(),
425 'align' => 'inline',
426 ]
427 );
428 }
429
430 if ( $canMoveSubpage ) {
431 $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
432 $fields[] = new OOUI\FieldLayout(
433 new OOUI\CheckboxInputWidget( [
434 'name' => 'wpMovesubpages',
435 'id' => 'wpMovesubpages',
436 'value' => '1',
437 'selected' => $this->moveSubpages,
438 ] ),
439 [
440 'label' => new OOUI\HtmlSnippet(
441 $this->msg(
442 ( $this->oldTitle->hasSubpages()
443 ? 'move-subpages'
444 : 'move-talk-subpages' )
445 )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
446 ),
447 'align' => 'inline',
448 ]
449 );
450 }
451
452 # Don't allow watching if user is not logged in
453 if ( $user->isLoggedIn() ) {
454 $watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
455 || $user->isWatched( $this->oldTitle ) );
456 $fields[] = new OOUI\FieldLayout(
457 new OOUI\CheckboxInputWidget( [
458 'name' => 'wpWatch',
459 'id' => 'watch', # ew
460 'value' => '1',
461 'selected' => $watchChecked,
462 ] ),
463 [
464 'label' => $this->msg( 'move-watch' )->text(),
465 'align' => 'inline',
466 ]
467 );
468 }
469
470 $hiddenFields = '';
471 if ( $moveOverShared ) {
472 $hiddenFields .= Html::hidden( 'wpMoveOverSharedFile', '1' );
473 }
474
475 if ( $deleteAndMove ) {
476 $fields[] = new OOUI\FieldLayout(
477 new OOUI\CheckboxInputWidget( [
478 'name' => 'wpDeleteAndMove',
479 'id' => 'wpDeleteAndMove',
480 'value' => '1',
481 ] ),
482 [
483 'label' => $this->msg( 'delete_and_move_confirm' )->text(),
484 'align' => 'inline',
485 ]
486 );
487 }
488
489 $fields[] = new OOUI\FieldLayout(
490 new OOUI\ButtonInputWidget( [
491 'name' => 'wpMove',
492 'value' => $this->msg( 'movepagebtn' )->text(),
493 'label' => $this->msg( 'movepagebtn' )->text(),
494 'flags' => [ 'primary', 'progressive' ],
495 'type' => 'submit',
496 ] ),
497 [
498 'align' => 'top',
499 ]
500 );
501
502 $fieldset = new OOUI\FieldsetLayout( [
503 'label' => $this->msg( 'move-page-legend' )->text(),
504 'id' => 'mw-movepage-table',
505 'items' => $fields,
506 ] );
507
508 $form = new OOUI\FormLayout( [
509 'method' => 'post',
510 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
511 'id' => 'movepage',
512 ] );
513 $form->appendContent(
514 $fieldset,
515 new OOUI\HtmlSnippet(
516 $hiddenFields .
517 Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
518 Html::hidden( 'wpEditToken', $user->getEditToken() )
519 )
520 );
521
522 $out->addHTML(
523 new OOUI\PanelLayout( [
524 'classes' => [ 'movepage-wrapper' ],
525 'expanded' => false,
526 'padded' => true,
527 'framed' => true,
528 'content' => $form,
529 ] )
530 );
531
532 $this->showLogFragment( $this->oldTitle );
533 $this->showSubpages( $this->oldTitle );
534 }
535
536 private function doSubmit() {
537 $user = $this->getUser();
538
539 if ( $user->pingLimiter( 'move' ) ) {
540 throw new ThrottledError;
541 }
542
543 $ot = $this->oldTitle;
544 $nt = $this->newTitle;
545
546 # don't allow moving to pages with # in
547 if ( !$nt || $nt->hasFragment() ) {
548 $this->showForm( [ [ 'badtitletext' ] ] );
549
550 return;
551 }
552
553 $services = MediaWikiServices::getInstance();
554
555 # Show a warning if the target file exists on a shared repo
556 $repoGroup = $services->getRepoGroup();
557 if ( $nt->getNamespace() == NS_FILE
558 && !( $this->moveOverShared && $this->permManager->userHasRight( $user, 'reupload-shared' ) )
559 && !$repoGroup->getLocalRepo()->findFile( $nt )
560 && $repoGroup->findFile( $nt )
561 ) {
562 $this->showForm( [ [ 'file-exists-sharedrepo' ] ] );
563
564 return;
565 }
566
567 # Delete to make way if requested
568 if ( $this->deleteAndMove ) {
569 $permErrors = $this->permManager->getPermissionErrors( 'delete', $user, $nt );
570 if ( count( $permErrors ) ) {
571 # Only show the first error
572 $this->showForm( $permErrors, true );
573
574 return;
575 }
576
577 $page = WikiPage::factory( $nt );
578
579 // Small safety margin to guard against concurrent edits
580 if ( $page->isBatchedDelete( 5 ) ) {
581 $this->showForm( [ [ 'movepage-delete-first' ] ] );
582
583 return;
584 }
585
586 $reason = $this->msg( 'delete_and_move_reason', $ot )->inContentLanguage()->text();
587
588 // Delete an associated image if there is
589 if ( $nt->getNamespace() == NS_FILE ) {
590 $file = $repoGroup->getLocalRepo()->newFile( $nt );
591 $file->load( File::READ_LATEST );
592 if ( $file->exists() ) {
593 $file->deleteFile( $reason, $user, false );
594 }
595 }
596
597 $deleteStatus = $page->doDeleteArticleReal(
598 $reason,
599 $user,
600 /* suppress */ false
601 );
602 if ( !$deleteStatus->isGood() ) {
603 $this->showForm( $deleteStatus->getErrorsArray() );
604
605 return;
606 }
607 }
608
609 $handler = MediaWikiServices::getInstance()
610 ->getContentHandlerFactory()
611 ->getContentHandler( $ot->getContentModel() );
612
613 if ( !$handler->supportsRedirects() ) {
614 $createRedirect = false;
615 } elseif ( $this->permManager->userHasRight( $user, 'suppressredirect' ) ) {
616 $createRedirect = $this->leaveRedirect;
617 } else {
618 $createRedirect = true;
619 }
620
621 # Do the actual move.
622 $mp = new MovePage( $ot, $nt );
623
624 # check whether the requested actions are permitted / possible
625 $userPermitted = $mp->checkPermissions( $user, $this->reason )->isOK();
626 if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
627 $this->moveTalk = false;
628 }
629 if ( $this->moveSubpages ) {
630 $this->moveSubpages = $this->permManager->userCan( 'move-subpages', $user, $ot );
631 }
632
633 $status = $mp->moveIfAllowed( $user, $this->reason, $createRedirect );
634 if ( !$status->isOK() ) {
635 $this->showForm( $status->getErrorsArray(), !$userPermitted );
636 return;
637 }
638
639 if ( $this->getConfig()->get( 'FixDoubleRedirects' ) && $this->fixRedirects ) {
640 DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
641 }
642
643 $out = $this->getOutput();
644 $out->setPageTitle( $this->msg( 'pagemovedsub' ) );
645
647 $oldLink = $linkRenderer->makeLink(
648 $ot,
649 null,
650 [ 'id' => 'movepage-oldlink' ],
651 [ 'redirect' => 'no' ]
652 );
653 $newLink = $linkRenderer->makeKnownLink(
654 $nt,
655 null,
656 [ 'id' => 'movepage-newlink' ]
657 );
658 $oldText = $ot->getPrefixedText();
659 $newText = $nt->getPrefixedText();
660
661 if ( $ot->exists() ) {
662 // NOTE: we assume that if the old title exists, it's because it was re-created as
663 // a redirect to the new title. This is not safe, but what we did before was
664 // even worse: we just determined whether a redirect should have been created,
665 // and reported that it was created if it should have, without any checks.
666 // Also note that isRedirect() is unreliable because of T39209.
667 $msgName = 'movepage-moved-redirect';
668 } else {
669 $msgName = 'movepage-moved-noredirect';
670 }
671
672 $out->addHTML( $this->msg( 'movepage-moved' )->rawParams( $oldLink,
673 $newLink )->params( $oldText, $newText )->parseAsBlock() );
674 $out->addWikiMsg( $msgName );
675
676 $this->getHookRunner()->onSpecialMovepageAfterMove( $this, $ot, $nt );
677
678 /*
679 * Now we move extra pages we've been asked to move: subpages and talk
680 * pages.
681 *
682 * First, make a list of id's. This might be marginally less efficient
683 * than a more direct method, but this is not a highly performance-cri-
684 * tical code path and readable code is more important here.
685 *
686 * If the target namespace doesn't allow subpages, moving with subpages
687 * would mean that you couldn't move them back in one operation, which
688 * is bad.
689 * @todo FIXME: A specific error message should be given in this case.
690 */
691
692 // @todo FIXME: Use MovePage::moveSubpages() here
693 $nsInfo = $services->getNamespaceInfo();
695 if ( $this->moveSubpages && (
696 $nsInfo->hasSubpages( $nt->getNamespace() ) || (
697 $this->moveTalk
698 && $nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
699 )
700 ) ) {
701 $conds = [
702 'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
703 . ' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
704 ];
705 $conds['page_namespace'] = [];
706 if ( $nsInfo->hasSubpages( $nt->getNamespace() ) ) {
707 $conds['page_namespace'][] = $ot->getNamespace();
708 }
709 if ( $this->moveTalk &&
710 $nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
711 ) {
712 $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace();
713 }
714 } elseif ( $this->moveTalk ) {
715 $conds = [
716 'page_namespace' => $ot->getTalkPage()->getNamespace(),
717 'page_title' => $ot->getDBkey()
718 ];
719 } else {
720 # Skip the query
721 $conds = null;
722 }
723
724 $extraPages = [];
725 if ( $conds !== null ) {
726 $extraPages = TitleArray::newFromResult(
727 $dbr->select( 'page',
728 [ 'page_id', 'page_namespace', 'page_title' ],
729 $conds,
730 __METHOD__
731 )
732 );
733 }
734
735 $extraOutput = [];
736 $count = 1;
737 foreach ( $extraPages as $oldSubpage ) {
738 if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) {
739 # Already did this one.
740 continue;
741 }
742
743 $newPageName = preg_replace(
744 '#^' . preg_quote( $ot->getDBkey(), '#' ) . '#',
745 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
746 $oldSubpage->getDBkey()
747 );
748
749 if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) {
750 // Moving a subpage from a subject namespace to a talk namespace or vice-versa
751 $newNs = $nt->getNamespace();
752 } elseif ( $oldSubpage->isTalkPage() ) {
753 $newNs = $nt->getTalkPage()->getNamespace();
754 } else {
755 $newNs = $nt->getSubjectPage()->getNamespace();
756 }
757
758 # T16385: we need makeTitleSafe because the new page names may
759 # be longer than 255 characters.
760 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
761 if ( !$newSubpage ) {
762 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
763 $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink )
764 ->params( Title::makeName( $newNs, $newPageName ) )->escaped();
765 continue;
766 }
767
768 $mp = new MovePage( $oldSubpage, $newSubpage );
769 # This was copy-pasted from Renameuser, bleh.
770 if ( $newSubpage->exists() && !$mp->isValidMove()->isOK() ) {
771 $link = $linkRenderer->makeKnownLink( $newSubpage );
772 $extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped();
773 } else {
774 $status = $mp->moveIfAllowed( $user, $this->reason, $createRedirect );
775
776 if ( $status->isOK() ) {
777 if ( $this->fixRedirects ) {
778 DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage );
779 }
780 $oldLink = $linkRenderer->makeLink(
781 $oldSubpage,
782 null,
783 [],
784 [ 'redirect' => 'no' ]
785 );
786
787 $newLink = $linkRenderer->makeKnownLink( $newSubpage );
788 $extraOutput[] = $this->msg( 'movepage-page-moved' )
789 ->rawParams( $oldLink, $newLink )->escaped();
790 ++$count;
791
792 $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
793 if ( $count >= $maximumMovedPages ) {
794 $extraOutput[] = $this->msg( 'movepage-max-pages' )
795 ->numParams( $maximumMovedPages )->escaped();
796 break;
797 }
798 } else {
799 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
800 $newLink = $linkRenderer->makeLink( $newSubpage );
801 $extraOutput[] = $this->msg( 'movepage-page-unmoved' )
802 ->rawParams( $oldLink, $newLink )->escaped();
803 }
804 }
805 }
806
807 if ( $extraOutput !== [] ) {
808 $out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" );
809 }
810
811 # Deal with watches (we don't watch subpages)
812 WatchAction::doWatchOrUnwatch( $this->watch, $ot, $user );
813 WatchAction::doWatchOrUnwatch( $this->watch, $nt, $user );
814 }
815
816 private function showLogFragment( $title ) {
817 $moveLogPage = new LogPage( 'move' );
818 $out = $this->getOutput();
819 $out->addHTML( Xml::element( 'h2', null, $moveLogPage->getName()->text() ) );
820 LogEventsList::showLogExtract( $out, 'move', $title );
821 }
822
829 private function showSubpages( $title ) {
830 $nsHasSubpages = MediaWikiServices::getInstance()->getNamespaceInfo()->
831 hasSubpages( $title->getNamespace() );
832 $subpages = $title->getSubpages();
833 $count = $subpages instanceof TitleArray ? $subpages->count() : 0;
834
835 $titleIsTalk = $title->isTalkPage();
836 $subpagesTalk = $title->getTalkPage()->getSubpages();
837 $countTalk = $subpagesTalk instanceof TitleArray ? $subpagesTalk->count() : 0;
838 $totalCount = $count + $countTalk;
839
840 if ( !$nsHasSubpages && $countTalk == 0 ) {
841 return;
842 }
843
844 $this->getOutput()->wrapWikiMsg(
845 '== $1 ==',
846 [ 'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ]
847 );
848
849 if ( $nsHasSubpages ) {
850 $this->showSubpagesList( $subpages, $count, 'movesubpagetext', true );
851 }
852
853 if ( !$titleIsTalk && $countTalk > 0 ) {
854 $this->showSubpagesList( $subpagesTalk, $countTalk, 'movesubpagetalktext' );
855 }
856 }
857
858 private function showSubpagesList( $subpages, $pagecount, $wikiMsg, $noSubpageMsg = false ) {
859 $out = $this->getOutput();
860
861 # No subpages.
862 if ( $pagecount == 0 && $noSubpageMsg ) {
863 $out->addWikiMsg( 'movenosubpage' );
864 return;
865 }
866
867 $out->addWikiMsg( $wikiMsg, $this->getLanguage()->formatNum( $pagecount ) );
868 $out->addHTML( "<ul>\n" );
869
870 $linkBatch = new LinkBatch( $subpages );
871 $linkBatch->setCaller( __METHOD__ );
872 $linkBatch->execute();
874
875 foreach ( $subpages as $subpage ) {
876 $link = $linkRenderer->makeLink( $subpage );
877 $out->addHTML( "<li>$link</li>\n" );
878 }
879 $out->addHTML( "</ul>\n" );
880 }
881
890 public function prefixSearchSubpages( $search, $limit, $offset ) {
891 return $this->prefixSearchString( $search, $limit, $offset );
892 }
893
894 protected function getGroupName() {
895 return 'pagetools';
896 }
897}
getUser()
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static fixRedirects( $reason, $redirTitle, $destTitle=false)
Insert jobs into the job queue to fix redirects to the given title.
An error page which can definitely be safely rendered using the OutputPage.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
Class to simplify the use of log pages.
Definition LogPage.php:37
MediaWikiServices is the service locator for the application scope of MediaWiki.
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
A special page that allows users to change page titles.
PermissionManager $permManager
execute( $par)
Default execute method Checks user permissions.
showSubpages( $title)
Show subpages of the page being moved.
doesWrites()
Indicates whether this special page may perform database writes.
showSubpagesList( $subpages, $pagecount, $wikiMsg, $noSubpageMsg=false)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
showForm( $err, $isPermError=false)
Show the form.
showLogFragment( $title)
string $reason
Text input.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
Handles the backend logic of moving a page from one title to another.
Definition MovePage.php:42
Show an error when a user tries to do something they do not have the necessary permissions for.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!...
getOutput()
Get the OutputPage being used for this instance.
getSkin()
Shortcut to get the skin being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getPageTitle( $subpage=false)
Get a self-referential title object.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
MediaWiki Linker LinkRenderer null $linkRenderer
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
Show an error when the user hits a rate limit.
The TitleArray class only exists to provide the newFromResult method at pre- sent.
Represents a title within MediaWiki.
Definition Title.php:42
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1041
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:1014
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1859
Shortcut to construct a special page which is unlisted by default.
static doWatchOrUnwatch( $watch, Title $title, User $user, string $expiry=null)
Watch or unwatch a page.
const NS_USER
Definition Defines.php:72
const NS_FILE
Definition Defines.php:76
const NS_CATEGORY
Definition Defines.php:84
A helper class for throttling authentication attempts.
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:29
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42