92 private bool $moveOverProtection;
95 private $watch =
false;
116 parent::__construct(
'Movepage' );
134 $target = $par ??
$request->getText(
'target' );
135 $oldTitleText =
$request->getText(
'wpOldTitle', $target );
136 $this->oldTitle = Title::newFromText( $oldTitleText );
138 if ( !$this->oldTitle ) {
142 $this->
getOutput()->addBacklinkSubtitle( $this->oldTitle );
144 $this->
getOutput()->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
146 if ( !$this->oldTitle->exists() ) {
150 $newTitleTextMain =
$request->getText(
'wpNewTitleMain' );
151 $newTitleTextNs =
$request->getInt(
'wpNewTitleNs', $this->oldTitle->getNamespace() );
154 $newTitleText_bc =
$request->getText(
'wpNewTitle' );
155 $this->newTitle = strlen( $newTitleText_bc ) > 0
156 ? Title::newFromText( $newTitleText_bc )
157 : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
160 $isSubmit =
$request->getRawVal(
'action' ) ===
'submit' &&
$request->wasPosted();
162 $reasonList =
$request->getText(
'wpReasonList',
'other' );
164 if ( $reasonList ===
'other' ) {
167 $this->reason = $reasonList . $this->
msg(
'colon-separator' )->inContentLanguage()->text() .
$reason;
169 $this->reason = $reasonList;
174 $this->moveTalk =
$request->getBool(
'wpMovetalk', $def );
175 $this->fixRedirects =
$request->getBool(
'wpFixRedirects', $def );
176 $this->leaveRedirect =
$request->getBool(
'wpLeaveRedirect', $def );
178 $this->moveSubpages =
$request->getBool(
'wpMovesubpages', $def );
179 $this->deleteAndMove =
$request->getBool(
'wpDeleteAndMove' );
180 $this->moveOverShared =
$request->getBool(
'wpMoveOverSharedFile' );
181 $this->moveOverProtection =
$request->getBool(
'wpMoveOverProtection' );
182 $this->watch =
$request->getCheck(
'wpWatch' ) && $user->isRegistered();
186 if ( $isSubmit && $user->matchEditToken(
$request->getVal(
'wpEditToken' ) ) ) {
188 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
189 PermissionManager::RIGOR_SECURE );
191 $user->scheduleSpreadBlock();
192 if ( !$permStatus->isGood() ) {
198 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
199 PermissionManager::RIGOR_FULL );
200 if ( !$permStatus->isGood() ) {
201 $user->scheduleSpreadBlock();
208 private function getRedirectTarget(
PageIdentity $title ): string {
209 $target = $this->redirectLookup->getRedirectTarget( $title );
215 return $this->titleFormatter->getPrefixedText( $target );
226 $this->getSkin()->setRelevantTitle( $this->oldTitle );
228 $out = $this->getOutput();
229 $out->setPageTitleMsg( $this->msg(
'move-page' )->plaintextParams( $this->oldTitle->getPrefixedText() ) );
230 $out->addModuleStyles( [
232 'mediawiki.interface.helpers.styles'
234 $out->addModules(
'mediawiki.misc-authed-ooui' );
235 $this->addHelpLink(
'Help:Moving a page' );
237 $handler = $this->contentHandlerFactory
238 ->getContentHandler( $this->oldTitle->getContentModel() );
239 $createRedirect = $handler->supportsRedirects() && !(
247 $out->addWikiMsg(
'movepagetext' );
249 $out->addWikiMsg( $createRedirect ?
250 'movepagetext-noredirectfixer' :
251 'movepagetext-noredirectsupport' );
254 if ( $this->oldTitle->getNamespace() ===
NS_USER ) {
255 if ( !$this->oldTitle->isSubpage() ) {
258 $out->msg(
'moveuserpage-warning' )->parse(),
259 'mw-moveuserpage-warning'
264 $this->moveTalk = $this->
getRequest()->getBool(
"wpMovetalk",
false );
268 $out->msg(
'moveusersubpage-warning' )->parse(),
269 'mw-moveusersubpage-warning'
273 } elseif ( $this->oldTitle->getNamespace() ===
NS_CATEGORY ) {
276 $out->msg(
'movecategorypage-warning' )->parse(),
277 'mw-movecategorypage-warning'
283 $moveOverShared =
false;
286 $newTitle = $this->newTitle;
290 # Show the current title as a default
291 # when the form is first opened.
292 $newTitle = $this->oldTitle;
293 } elseif ( !$status ) {
294 # If a title was supplied, probably from the move log revert
295 # link, check for validity. We can then show some diagnostic
296 # information and save a click.
297 $mp = $this->movePageFactory->newMovePage( $this->oldTitle, $newTitle );
298 $status = $mp->isValidMove();
299 $status->merge( $mp->probablyCanMove( $this->getAuthority() ) );
300 if ( $this->moveTalk ) {
302 if ( $oldTalk && $newTalk && $oldTalk->exists() ) {
303 $mpTalk = $this->movePageFactory->newMovePage( $oldTalk, $newTalk );
304 $talkStatus = $mpTalk->isValidMove();
305 $talkStatus->merge( $mpTalk->probablyCanMove( $this->getAuthority() ) );
313 if ( !$talkStatus ) {
317 $talkStatus = $this->permManager->getPermissionStatus(
'move', $user, $oldTalk,
318 PermissionManager::RIGOR_QUICK );
326 $oldTalk = $this->oldTitle->getTalkPageIfDefined();
327 $oldTitleSubpages = $this->oldTitle->hasSubpages();
328 $oldTitleTalkSubpages = $this->oldTitle->getTalkPageIfDefined()->hasSubpages();
330 $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
331 $this->permManager->quickUserCan(
336 # We also want to be able to move assoc. subpage talk-pages even if base page
337 # has no associated talk page, so || with $oldTitleTalkSubpages.
338 $considerTalk = !$this->oldTitle->isTalkPage() &&
340 || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
343 $queryBuilder = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
346 ->where( [
'rd_namespace' => $this->oldTitle->getNamespace() ] )
347 ->andWhere( [
'rd_title' => $this->oldTitle->getDBkey() ] )
348 ->andWhere( [
'rd_interwiki' =>
'' ] );
350 $hasRedirects = (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
352 $hasRedirects =
false;
356 $talkOK = $talkStatus->isOK();
357 $mainOK = $status->isOK();
358 $talkIsArticle = $talkIsRedirect = $mainIsArticle = $mainIsRedirect =
false;
359 if ( count( $status->getMessages() ) == 1 ) {
360 $mainIsArticle = $status->hasMessage(
'articleexists' )
361 && $this->permManager->quickUserCan(
'delete', $user, $newTitle );
362 $mainIsRedirect = $status->hasMessage(
'redirectexists' ) && (
364 $this->permManager->quickUserCan(
'delete-redirect', $user, $newTitle ) ||
365 $this->permManager->quickUserCan(
'delete', $user, $newTitle ) );
366 if ( $status->hasMessage(
'file-exists-sharedrepo' )
367 && $this->permManager->userHasRight( $user,
'reupload-shared' )
371 $out->msg(
'move-over-sharedrepo', $newTitle->
getPrefixedText() )->parse()
374 $moveOverShared =
true;
378 if ( count( $talkStatus->getMessages() ) == 1 ) {
379 $talkIsArticle = $talkStatus->hasMessage(
'articleexists' )
380 && $this->permManager->quickUserCan(
'delete', $user, $newTitle );
381 $talkIsRedirect = $talkStatus->hasMessage(
'redirectexists' ) && (
383 $this->permManager->quickUserCan(
'delete-redirect', $user, $newTitle ) ||
384 $this->permManager->quickUserCan(
'delete', $user, $newTitle ) );
390 if ( ( $talkIsArticle && $mainIsArticle ) ||
391 ( $talkIsArticle && $mainIsRedirect ) ||
392 ( $talkIsRedirect && $mainIsArticle )
394 $warning = $out->msg(
'delete_and_move_text_2',
396 $newTalkTitle->getPrefixedText()
398 $deleteAndMove = [ $newTitle, $newTalkTitle ];
400 } elseif ( $mainIsRedirect && $talkIsRedirect ) {
401 $warning = $out->msg(
'delete_redirect_and_move_text_2',
403 $newTalkTitle->getPrefixedText(),
404 $this->getRedirectTarget( $newTitle ),
405 $this->getRedirectTarget( $newTalkTitle ),
407 $deleteAndMove = [ $newTitle, $newTalkTitle ];
410 } elseif ( $mainIsArticle && $talkOK ) {
411 $warning = $out->msg(
'delete_and_move_text', $newTitle->
getPrefixedText() );
412 $deleteAndMove = [ $newTitle ];
414 } elseif ( $mainIsRedirect && $talkOK ) {
415 $warning = $out->msg(
'delete_redirect_and_move_text',
417 $this->getRedirectTarget( $newTitle ),
419 $deleteAndMove = [ $newTitle ];
421 } elseif ( $talkIsArticle && $mainOK ) {
422 $warning = $out->msg(
'delete_and_move_text', $newTalkTitle->getPrefixedText() );
423 $deleteAndMove = [ $newTalkTitle ];
424 } elseif ( $talkIsRedirect && $mainOK ) {
425 $warning = $out->msg(
'delete_redirect_and_move_text',
426 $newTalkTitle->getPrefixedText(),
427 $this->getRedirectTarget( $newTalkTitle ),
429 $deleteAndMove = [ $newTalkTitle ];
432 $out->addHTML( Html::warningBox( $warning->parse() ) );
434 $messages = $status->getMessages();
436 if ( $status instanceof PermissionStatus ) {
437 $action_desc = $this->
msg(
'action-move' )->plain();
438 $errMsgHtml = $this->
msg(
'permissionserrorstext-withaction',
439 count( $messages ), $action_desc )->parseAsBlock();
441 $errMsgHtml = $this->
msg(
'cannotmove', count( $messages ) )->parseAsBlock();
444 if ( count( $messages ) == 1 ) {
445 $errMsgHtml .= $this->
msg( $messages[0] )->parseAsBlock();
449 foreach ( $messages as $msg ) {
450 $errStr[] = $this->
msg( $msg )->parse();
455 $errStr = array_unique( $errStr );
457 $errMsgHtml .=
'<ul><li>' . implode(
"</li>\n<li>", $errStr ) .
"</li></ul>\n";
459 $out->addHTML( Html::errorBox( $errMsgHtml ) );
461 $talkMessages = $talkStatus->getMessages();
462 if ( $talkMessages ) {
464 $errMsgHtml = $this->
msg(
'cannotmovetalk', count( $talkMessages ) )->parseAsBlock();
466 if ( count( $talkMessages ) == 1 ) {
467 $errMsgHtml .= $this->
msg( $talkMessages[0] )->parseAsBlock();
471 foreach ( $talkMessages as $msg ) {
472 $errStr[] = $this->
msg( $msg )->parse();
475 $errMsgHtml .=
'<ul><li>' . implode(
"</li>\n<li>", $errStr ) .
"</li></ul>\n";
477 $errMsgHtml .= $this->
msg(
'movetalk-unselect' )->parse();
478 $out->addHTML( Html::errorBox( $errMsgHtml ) );
482 if ( $this->restrictionStore->isProtected( $this->oldTitle,
'move' ) ) {
483 # Is the title semi-protected?
484 if ( $this->restrictionStore->isSemiProtected( $this->oldTitle,
'move' ) ) {
485 $noticeMsg =
'semiprotectedpagemovewarning';
487 # Then it must be protected based on static groups (regular)
488 $noticeMsg =
'protectedpagemovewarning';
490 LogEventsList::showLogExtract(
495 [
'lim' => 1,
'msgKey' => $noticeMsg ]
500 if ( $talkOK && $oldTalk && $oldTalk->exists() && $this->restrictionStore->isProtected( $oldTalk,
'move' ) ) {
501 # Is the title semi-protected?
502 if ( $this->restrictionStore->isSemiProtected( $oldTalk,
'move' ) ) {
503 $noticeMsg =
'semiprotectedtalkpagemovewarning';
505 # Then it must be protected based on static groups (regular)
506 $noticeMsg =
'protectedtalkpagemovewarning';
508 LogEventsList::showLogExtract(
513 [
'lim' => 1,
'msgKey' => $noticeMsg ]
520 $immovableNamespaces = [];
521 foreach ( $this->
getLanguage()->getNamespaces() as $nsId => $_ ) {
522 if ( !$this->nsInfo->isMovable( $nsId ) ) {
523 $immovableNamespaces[] = $nsId;
527 $moveOverProtectionMain =
false;
528 $moveOverProtectionTalk =
false;
529 if ( $this->newTitle && $mainOK ) {
532 if ( $this->restrictionStore->isProtected( $this->newTitle,
'create' ) ) {
533 # Is the title semi-protected?
534 if ( $this->restrictionStore->isSemiProtected( $this->newTitle,
'create' ) ) {
535 $noticeMsg =
'semiprotectedpagemovecreatewarning';
537 # Then it must be protected based on static groups (regular)
538 $noticeMsg =
'protectedpagemovecreatewarning';
540 LogEventsList::showLogExtract(
545 [
'lim' => 1,
'msgKey' => $noticeMsg ]
547 $moveOverProtectionMain =
true;
550 if ( $oldTalk && $oldTalk->exists() && $talkOK &&
551 $newTalk && $this->restrictionStore->isProtected( $newTalk,
'create' )
553 # Is the title semi-protected?
554 if ( $this->restrictionStore->isSemiProtected( $newTalk,
'create' ) ) {
555 $noticeMsg =
'semiprotectedpagemovetalkcreatewarning';
557 # Then it must be protected based on static groups (regular)
558 $noticeMsg =
'protectedpagemovetalkcreatewarning';
560 LogEventsList::showLogExtract(
565 [
'lim' => 1,
'msgKey' => $noticeMsg ]
567 $moveOverProtectionTalk =
true;
574 $fields[] =
new FieldLayout(
575 new ComplexTitleInputWidget( [
576 'id' =>
'wpNewTitle',
578 'id' =>
'wpNewTitleNs',
579 'name' =>
'wpNewTitleNs',
581 'exclude' => $immovableNamespaces,
584 'id' =>
'wpNewTitleMain',
585 'name' =>
'wpNewTitleMain',
586 'value' => $newTitle->
getText(),
588 'suggestions' =>
false,
593 'label' => $this->msg(
'newtitle' )->text(),
598 $options = Html::listDropdownOptions(
599 $this->
msg(
'movepage-reason-dropdown' )
600 ->page( $this->oldTitle )
601 ->inContentLanguage()
603 [
'other' => $this->
msg(
'movereasonotherlist' )->text() ]
605 $options = Html::listDropdownOptionsOoui( $options );
607 $fields[] =
new FieldLayout(
608 new DropdownInputWidget( [
609 'name' =>
'wpReasonList',
610 'inputId' =>
'wpReasonList',
612 'value' => $this->
getRequest()->getText(
'wpReasonList',
'other' ),
613 'options' => $options,
616 'label' => $this->
msg(
'movereason' )->text(),
624 $fields[] =
new FieldLayout(
625 new TextInputWidget( [
626 'name' =>
'wpReason',
628 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
630 'value' => $this->
getRequest()->getText(
'wpReason' ),
633 'label' => $this->
msg(
'moveotherreason' )->text(),
638 if ( $considerTalk ) {
639 $fields[] =
new FieldLayout(
640 new CheckboxInputWidget( [
641 'name' =>
'wpMovetalk',
642 'id' =>
'wpMovetalk',
647 'selected' => $this->moveTalk,
650 'label' => $this->
msg(
'movetalk' )->text(),
651 'help' =>
new HtmlSnippet( $this->
msg(
'movepagetalktext' )->parseAsBlock() ),
652 'helpInline' =>
true,
654 'id' =>
'wpMovetalk-field',
659 if ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
660 if ( $createRedirect ) {
661 $isChecked = $this->leaveRedirect;
667 $fields[] =
new FieldLayout(
668 new CheckboxInputWidget( [
669 'name' =>
'wpLeaveRedirect',
670 'id' =>
'wpLeaveRedirect',
672 'selected' => $isChecked,
673 'disabled' => $isDisabled,
676 'label' => $this->
msg(
'move-leave-redirect' )->text(),
682 if ( $hasRedirects ) {
683 $fields[] =
new FieldLayout(
684 new CheckboxInputWidget( [
685 'name' =>
'wpFixRedirects',
686 'id' =>
'wpFixRedirects',
688 'selected' => $this->fixRedirects,
691 'label' => $this->
msg(
'fix-double-redirects' )->text(),
697 if ( $canMoveSubpage ) {
699 $fields[] =
new FieldLayout(
700 new CheckboxInputWidget( [
701 'name' =>
'wpMovesubpages',
702 'id' =>
'wpMovesubpages',
704 'selected' => $this->moveSubpages,
707 'label' =>
new HtmlSnippet(
709 ( $this->oldTitle->hasSubpages()
711 :
'move-talk-subpages' )
712 )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
719 # Don't allow watching if user is not logged in
720 if ( $user->isRegistered() ) {
721 $watchChecked = ( $this->watch || $this->userOptionsLookup->getBoolOption( $user,
'watchmoves' )
722 || $this->watchlistManager->isWatched( $user, $this->oldTitle ) );
723 $fields[] =
new FieldLayout(
724 new CheckboxInputWidget( [
726 'id' =>
'watch', # ew
729 'selected' => $watchChecked,
732 'label' => $this->
msg(
'move-watch' )->text(),
737 # Add a dropdown for watchlist expiry times in the form, T261230
739 $expiryOptions = WatchAction::getExpiryOptions(
741 $this->watchedItemStore->getWatchedItem( $user, $this->oldTitle )
743 # Reformat the options to match what DropdownInputWidget wants.
745 foreach ( $expiryOptions[
'options'] as $label => $value ) {
746 $options[] = [
'data' => $value,
'label' => $label ];
749 $fields[] =
new FieldLayout(
750 new DropdownInputWidget( [
751 'name' =>
'wpWatchlistExpiry',
752 'id' =>
'wpWatchlistExpiry',
754 'options' => $options,
757 'label' => $this->
msg(
'confirm-watch-label' )->text(),
758 'id' =>
'wpWatchlistExpiryLabel',
767 if ( $moveOverShared ) {
768 $hiddenFields .= Html::hidden(
'wpMoveOverSharedFile',
'1' );
771 if ( $deleteAndMove ) {
774 if ( count( $deleteAndMove ) == 2 ) {
775 $msg = $this->
msg(
'delete_and_move_confirm_2',
776 $deleteAndMove[0]->getPrefixedText(),
777 $deleteAndMove[1]->getPrefixedText()
781 $msg = $this->
msg(
'delete_and_move_confirm', $deleteAndMove[0]->getPrefixedText() )->text();
783 $fields[] =
new FieldLayout(
784 new CheckboxInputWidget( [
785 'name' =>
'wpDeleteAndMove',
786 'id' =>
'wpDeleteAndMove',
795 if ( $moveOverProtectionMain || $moveOverProtectionTalk ) {
796 if ( $moveOverProtectionMain && $moveOverProtectionTalk ) {
797 $moveOverProtectionMsg = $this->
msg(
798 'move_over_protection_confirm_both',
799 $this->newTitle->getPrefixedText(),
800 $newTalkTitle->getPrefixedText()
802 } elseif ( $moveOverProtectionMain ) {
803 $moveOverProtectionMsg = $this->
msg(
804 'move_over_protection_confirm',
805 $this->newTitle->getPrefixedText()
808 $moveOverProtectionMsg = $this->
msg(
809 'move_over_protection_confirm_talk',
810 $newTalkTitle->getPrefixedText()
813 $fields[] =
new FieldLayout(
814 new CheckboxInputWidget( [
815 'name' =>
'wpMoveOverProtection',
816 'id' =>
'wpMoveOverProtection',
820 'label' => $moveOverProtectionMsg,
826 $fields[] =
new FieldLayout(
827 new ButtonInputWidget( [
829 'value' => $this->
msg(
'movepagebtn' )->text(),
830 'label' => $this->
msg(
'movepagebtn' )->text(),
831 'flags' => [
'primary',
'progressive' ],
833 'accessKey' => Linker::accesskey(
'move' ),
834 'title' => Linker::titleAttrib(
'move' ),
841 $fieldset =
new FieldsetLayout( [
842 'label' => $this->
msg(
'move-page-legend' )->text(),
843 'id' =>
'mw-movepage-table',
847 $form =
new FormLayout( [
849 'action' => $this->getPageTitle()->getLocalURL(
'action=submit' ),
852 $form->appendContent(
856 Html::hidden(
'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
857 Html::hidden(
'wpEditToken', $user->getEditToken() )
863 'classes' => [
'movepage-wrapper' ],
870 if ( $this->
getAuthority()->isAllowed(
'editinterface' ) ) {
872 $this->
msg(
'movepage-reason-dropdown' )->inContentLanguage()->
getTitle(),
873 $this->
msg(
'movepage-edit-reasonlist' )->text(),
875 [
'action' =>
'edit' ]
877 $out->addHTML( Html::rawElement(
'p', [
'class' =>
'mw-movepage-editreasons' ], $link ) );
880 $this->showLogFragment( $this->oldTitle );
881 $this->showSubpages( $this->oldTitle );
884 private function vacateTitle( Title $title, User $user, Title $oldTitle ):
StatusValue {
885 if ( !$title->exists() ) {
888 $redir2 = $title->isSingleRevRedirect();
890 $permStatus = $this->permManager->getPermissionStatus(
891 $redir2 ?
'delete-redirect' :
'delete',
894 if ( !$permStatus->isGood() ) {
896 if ( !$this->permManager->userCan(
'delete', $user, $title ) ) {
910 $page = $this->wikiPageFactory->newFromTitle( $title );
911 $delPage = $this->deletePageFactory->newDeletePage( $page, $user );
914 if ( $delPage->isBatchedDelete( 5 ) ) {
918 $reason = $this->
msg(
'delete_and_move_reason', $oldTitle->
getPrefixedText() )->inContentLanguage()->text();
922 $file = $this->repoGroup->getLocalRepo()->newFile( $title );
923 $file->load( IDBAccessObject::READ_LATEST );
924 if ( $file->exists() ) {
925 $file->deleteFile( $reason, $user,
false );
929 $deletionLog = $redir2 ?
'delete_redir2' :
'delete';
930 $deleteStatus = $delPage
931 ->setLogSubtype( $deletionLog )
933 ->forceImmediate(
true )
934 ->deleteUnsafe( $reason );
936 return $deleteStatus;
939 private function doSubmit() {
942 if ( $user->pingLimiter(
'move' ) ) {
943 throw new ThrottledError;
946 $ot = $this->oldTitle;
947 $nt = $this->newTitle;
949 # don't allow moving to pages with # in
950 if ( !$nt || $nt->hasFragment() ) {
951 $this->showForm( StatusValue::newFatal(
'badtitletext' ) );
956 $oldTalk = $ot->getTalkPageIfDefined();
957 $newTalk = $nt->getTalkPageIfDefined();
959 if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
960 $this->moveTalk =
false;
963 # Show a warning if the target file exists on a shared repo
964 if ( $nt->getNamespace() ===
NS_FILE
965 && !( $this->moveOverShared && $this->permManager->userHasRight( $user,
'reupload-shared' ) )
966 && !$this->repoGroup->getLocalRepo()->findFile( $nt )
967 && $this->repoGroup->findFile( $nt )
969 $this->showForm( StatusValue::newFatal(
'file-exists-sharedrepo' ) );
974 # Show a warning if protected (showForm handles the warning )
975 if ( !$this->moveOverProtection ) {
976 if ( $this->restrictionStore->isProtected( $nt,
'create' ) ) {
980 if ( $this->moveTalk && $newTalk && $this->restrictionStore->isProtected( $newTalk,
'create' ) ) {
986 $handler = $this->contentHandlerFactory->getContentHandler( $ot->getContentModel() );
988 if ( !$handler->supportsRedirects() || (
994 $createRedirect =
false;
995 } elseif ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
996 $createRedirect = $this->leaveRedirect;
998 $createRedirect =
true;
1002 $mp = $this->movePageFactory->newMovePage( $ot, $nt );
1003 $permStatusMain = $mp->authorizeMove( $this->
getAuthority(), $this->reason );
1004 $permStatusMain->merge( $mp->isValidMove() );
1006 $onlyMovingTalkSubpages =
false;
1007 if ( $this->moveTalk && $oldTalk && $newTalk ) {
1008 $mpTalk = $this->movePageFactory->newMovePage( $oldTalk, $newTalk );
1009 $permStatusTalk = $mpTalk->authorizeMove( $this->
getAuthority(), $this->reason );
1010 $permStatusTalk->merge( $mpTalk->isValidMove() );
1013 if ( !$permStatusTalk->isOK() &&
1014 !$permStatusTalk->hasMessagesExcept(
'movepage-source-doesnt-exist' )
1016 $permStatusTalk->setOK(
true );
1017 $onlyMovingTalkSubpages =
true;
1024 if ( $this->deleteAndMove ) {
1027 if ( $permStatusMain->hasMessagesExcept(
'redirectexists',
'articleexists' ) ||
1028 ( !$onlyMovingTalkSubpages &&
1029 $permStatusTalk->hasMessagesExcept(
'redirectexists',
'articleexists' ) )
1031 $this->showForm( $permStatusMain, $permStatusTalk );
1040 $deleteStatus = $this->vacateTitle( $nt, $user, $ot );
1041 if ( !$deleteStatus->isGood() ) {
1042 $this->showForm( $deleteStatus );
1045 if ( $this->moveTalk && $oldTalk && $newTalk ) {
1046 $deleteStatus = $this->vacateTitle( $newTalk, $user, $oldTalk );
1047 if ( !$deleteStatus->isGood() ) {
1050 $this->showForm( StatusValue::newGood(), $deleteStatus );
1054 } elseif ( !$permStatusMain->isOK() || !$permStatusTalk->isOK() ) {
1056 $this->showForm( $permStatusMain, $permStatusTalk );
1062 $mainStatus = $mp->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1063 if ( !$mainStatus->isOK() ) {
1064 $this->showForm( $mainStatus );
1068 $fixRedirects = $this->fixRedirects && $this->
getConfig()->get( MainConfigNames::FixDoubleRedirects );
1069 if ( $fixRedirects ) {
1070 DoubleRedirectJob::fixRedirects(
'move', $ot );
1074 $maximumMovedPages = $this->
getConfig()->get( MainConfigNames::MaximumMovedPages );
1078 if ( $mpTalk && !$onlyMovingTalkSubpages ) {
1079 $talkStatus = $mpTalk->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1083 '@phan-var Status<string> $talkStatus';
1084 $talkStatus->value = $newTalk->getPrefixedText();
1085 $moveStatuses[$oldTalk->getPrefixedText()] = $talkStatus;
1089 if ( $this->moveSubpages ) {
1090 if ( $this->permManager->userCan(
'move-subpages', $user, $ot ) ) {
1091 $mp->setMaximumMovedPages( $maximumMovedPages - count( $moveStatuses ) );
1092 $subpageStatus = $mp->moveSubpagesIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1093 $moveStatuses = array_merge( $moveStatuses, $subpageStatus->value );
1095 if ( $mpTalk && $oldTalk && $maximumMovedPages > count( $moveStatuses ) &&
1096 $this->permManager->userCan(
'move-subpages', $user, $oldTalk ) &&
1097 ( $onlyMovingTalkSubpages || $talkStatus->isOK() )
1099 $mpTalk->setMaximumMovedPages( $maximumMovedPages - count( $moveStatuses ) );
1100 $talkSubStatus = $mpTalk->moveSubpagesIfAllowed(
1103 $moveStatuses = array_merge( $moveStatuses, $talkSubStatus->value );
1110 $out->setPageTitleMsg( $this->
msg(
'pagemovedsub' ) );
1113 $oldLink = $linkRenderer->makeLink(
1116 [
'id' =>
'movepage-oldlink' ],
1117 [
'redirect' =>
'no' ]
1119 $newLink = $linkRenderer->makeKnownLink(
1122 [
'id' =>
'movepage-newlink' ]
1124 $oldText = $ot->getPrefixedText();
1125 $newText = $nt->getPrefixedText();
1127 $out->addHTML( $this->
msg(
'movepage-moved' )->rawParams( $oldLink,
1128 $newLink )->params( $oldText, $newText )->parseAsBlock() );
1129 $out->addWikiMsg( isset( $mainStatus->getValue()[
'redirectRevision'] ) ?
1130 'movepage-moved-redirect' :
1131 'movepage-moved-noredirect' );
1133 $this->
getHookRunner()->onSpecialMovepageAfterMove( $this, $ot, $nt );
1136 foreach ( $moveStatuses as $oldSubpage => $subpageStatus ) {
1137 if ( $subpageStatus->hasMessage(
'movepage-max-pages' ) ) {
1138 $extraOutput[] = $this->
msg(
'movepage-max-pages' )
1139 ->numParams( $maximumMovedPages )->escaped();
1142 $oldSubpage = Title::newFromText( $oldSubpage );
1143 $newSubpage = Title::newFromText( $subpageStatus->value );
1144 if ( $subpageStatus->isGood() ) {
1145 if ( $fixRedirects ) {
1146 DoubleRedirectJob::fixRedirects(
'move', $oldSubpage );
1148 $oldLink = $linkRenderer->makeLink( $oldSubpage,
null, [], [
'redirect' =>
"no" ] );
1149 $newLink = $linkRenderer->makeKnownLink( $newSubpage );
1151 $extraOutput[] = $this->
msg(
'movepage-page-moved' )->rawParams(
1154 } elseif ( $subpageStatus->hasMessage(
'articleexists' )
1155 || $subpageStatus->hasMessage(
'redirectexists' )
1157 $link = $linkRenderer->makeKnownLink( $newSubpage );
1158 $extraOutput[] = $this->
msg(
'movepage-page-exists' )->rawParams( $link )->escaped();
1160 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
1161 if ( $newSubpage ) {
1162 $newLink = $linkRenderer->makeLink( $newSubpage );
1165 $newLink = htmlspecialchars( $subpageStatus->value );
1167 $extraOutput[] = $this->
msg(
'movepage-page-unmoved' )
1168 ->rawParams( $oldLink, $newLink )->escaped();
1172 if ( $extraOutput !== [] ) {
1173 $out->addHTML(
"<ul>\n<li>" . implode(
"</li>\n<li>", $extraOutput ) .
"</li>\n</ul>" );
1176 # Deal with watches (we don't watch subpages)
1177 # Get text from expiry selection dropdown, T261230
1178 $expiry = $this->
getRequest()->getText(
'wpWatchlistExpiry' );
1179 if ( $this->
getConfig()->
get( MainConfigNames::WatchlistExpiry ) && $expiry !==
'' ) {
1180 $expiry = ExpiryDef::normalizeExpiry( $expiry, TS::ISO_8601 );
1185 $this->watchlistManager->setWatch(
1192 $this->watchlistManager->setWatch(
1200 private function showLogFragment( Title $title ) {
1201 $moveLogPage =
new LogPage(
'move' );
1203 $out->addHTML( Html::element(
'h2', [], $moveLogPage->getName()->text() ) );
1204 LogEventsList::showLogExtract( $out,
'move', $title );
1213 private function showSubpages( $title ) {
1214 $maximumMovedPages = $this->
getConfig()->get( MainConfigNames::MaximumMovedPages );
1215 $nsHasSubpages = $this->nsInfo->hasSubpages( $title->getNamespace() );
1216 $subpages = $title->getSubpages( $maximumMovedPages + 1 );
1217 $count = $subpages instanceof TitleArrayFromResult ? $subpages->count() : 0;
1219 $titleIsTalk = $title->isTalkPage();
1220 $subpagesTalk = $title->getTalkPage()->getSubpages( $maximumMovedPages + 1 );
1221 $countTalk = $subpagesTalk instanceof TitleArrayFromResult ? $subpagesTalk->count() : 0;
1222 $totalCount = $count + $countTalk;
1224 if ( !$nsHasSubpages && $countTalk == 0 ) {
1230 [
'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ]
1233 if ( $nsHasSubpages ) {
1234 $this->showSubpagesList(
1235 $subpages, $count,
'movesubpagetext',
'movesubpagetext-truncated',
true
1239 if ( !$titleIsTalk && $countTalk > 0 ) {
1240 $this->showSubpagesList(
1241 $subpagesTalk, $countTalk,
'movesubpagetalktext',
'movesubpagetalktext-truncated'
1246 private function showSubpagesList(
1247 TitleArrayFromResult $subpages,
int $pagecount,
string $msg,
string $truncatedMsg,
bool $noSubpageMsg =
false
1252 if ( $pagecount == 0 && $noSubpageMsg ) {
1253 $out->addWikiMsg(
'movenosubpage' );
1257 $maximumMovedPages = $this->
getConfig()->get( MainConfigNames::MaximumMovedPages );
1259 if ( $pagecount > $maximumMovedPages ) {
1260 $subpages = $this->truncateSubpagesList( $subpages );
1261 $out->addWikiMsg( $truncatedMsg, $this->
getLanguage()->formatNum( $maximumMovedPages ) );
1263 $out->addWikiMsg( $msg, $this->
getLanguage()->formatNum( $pagecount ) );
1265 $out->addHTML(
"<ul>\n" );
1267 $this->linkBatchFactory->newLinkBatch( $subpages )
1268 ->setCaller( __METHOD__ )
1272 foreach ( $subpages as $subpage ) {
1273 $link = $linkRenderer->makeLink( $subpage );
1274 $out->addHTML(
"<li>$link</li>\n" );
1276 $out->addHTML(
"</ul>\n" );
1279 private function truncateSubpagesList( iterable $subpages ): array {
1281 foreach ( $subpages as $subpage ) {
1282 $returnArray[] = $subpage;
1283 if ( count( $returnArray ) >= $this->
getConfig()->
get( MainConfigNames::MaximumMovedPages ) ) {
1287 return $returnArray;
1299 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );