89 private bool $moveOverProtection;
92 private $watch =
false;
127 parent::__construct(
'Movepage' );
128 $this->movePageFactory = $movePageFactory;
129 $this->permManager = $permManager;
130 $this->userOptionsLookup = $userOptionsLookup;
131 $this->dbProvider = $dbProvider;
132 $this->contentHandlerFactory = $contentHandlerFactory;
133 $this->nsInfo = $nsInfo;
134 $this->linkBatchFactory = $linkBatchFactory;
135 $this->repoGroup = $repoGroup;
136 $this->wikiPageFactory = $wikiPageFactory;
137 $this->searchEngineFactory = $searchEngineFactory;
138 $this->watchlistManager = $watchlistManager;
139 $this->watchedItemStore = $watchedItemStore;
140 $this->restrictionStore = $restrictionStore;
141 $this->titleFactory = $titleFactory;
142 $this->deletePageFactory = $deletePageFactory;
160 $target = $par ?? $request->getText(
'target' );
161 $oldTitleText = $request->getText(
'wpOldTitle', $target );
162 $this->oldTitle = Title::newFromText( $oldTitleText );
164 if ( !$this->oldTitle ) {
168 $this->
getOutput()->addBacklinkSubtitle( $this->oldTitle );
170 $this->
getOutput()->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
172 if ( !$this->oldTitle->exists() ) {
176 $newTitleTextMain = $request->getText(
'wpNewTitleMain' );
177 $newTitleTextNs = $request->getInt(
'wpNewTitleNs', $this->oldTitle->getNamespace() );
180 $newTitleText_bc = $request->getText(
'wpNewTitle' );
181 $this->newTitle = strlen( $newTitleText_bc ) > 0
182 ? Title::newFromText( $newTitleText_bc )
183 : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
186 $isSubmit = $request->getRawVal(
'action' ) ===
'submit' && $request->wasPosted();
188 $reasonList = $request->getText(
'wpReasonList',
'other' );
189 $reason = $request->getText(
'wpReason' );
190 if ( $reasonList ===
'other' ) {
193 $this->reason = $reasonList . $this->
msg(
'colon-separator' )->inContentLanguage()->text() .
$reason;
195 $this->reason = $reasonList;
200 $this->moveTalk = $request->getBool(
'wpMovetalk', $def );
201 $this->fixRedirects = $request->getBool(
'wpFixRedirects', $def );
202 $this->leaveRedirect = $request->getBool(
'wpLeaveRedirect', $def );
204 $this->moveSubpages = $request->getBool(
'wpMovesubpages', $def );
205 $this->deleteAndMove = $request->getBool(
'wpDeleteAndMove' );
206 $this->moveOverShared = $request->getBool(
'wpMoveOverSharedFile' );
207 $this->moveOverProtection = $request->getBool(
'wpMoveOverProtection' );
208 $this->watch = $request->getCheck(
'wpWatch' ) && $user->isRegistered();
212 if ( $isSubmit && $user->matchEditToken( $request->getVal(
'wpEditToken' ) ) ) {
214 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
215 PermissionManager::RIGOR_SECURE );
217 $user->scheduleSpreadBlock();
218 if ( !$permStatus->isGood() ) {
224 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
225 PermissionManager::RIGOR_FULL );
226 if ( !$permStatus->isGood() ) {
227 $user->scheduleSpreadBlock();
242 $this->
getSkin()->setRelevantTitle( $this->oldTitle );
245 $out->setPageTitleMsg( $this->
msg(
'move-page' )->plaintextParams( $this->oldTitle->getPrefixedText() ) );
246 $out->addModuleStyles( [
248 'mediawiki.interface.helpers.styles'
250 $out->addModules(
'mediawiki.misc-authed-ooui' );
253 $handler = $this->contentHandlerFactory
254 ->getContentHandler( $this->oldTitle->getContentModel() );
255 $createRedirect = $handler->supportsRedirects() && !(
263 $out->addWikiMsg(
'movepagetext' );
265 $out->addWikiMsg( $createRedirect ?
266 'movepagetext-noredirectfixer' :
267 'movepagetext-noredirectsupport' );
270 if ( $this->oldTitle->getNamespace() ===
NS_USER && !$this->oldTitle->isSubpage() ) {
273 $out->msg(
'moveuserpage-warning' )->parse(),
274 'mw-moveuserpage-warning'
278 $this->moveTalk = $this->
getRequest()->getBool(
"wpMovetalk",
false );
279 } elseif ( $this->oldTitle->getNamespace() ===
NS_CATEGORY ) {
282 $out->msg(
'movecategorypage-warning' )->parse(),
283 'mw-movecategorypage-warning'
296 # Show the current title as a default
297 # when the form is first opened.
299 } elseif ( !$status ) {
300 # If a title was supplied, probably from the move log revert
301 # link, check for validity. We can then show some diagnostic
302 # information and save a click.
303 $mp = $this->movePageFactory->newMovePage( $this->oldTitle,
$newTitle );
304 $status = $mp->isValidMove();
305 $status->merge( $mp->probablyCanMove( $this->getAuthority() ) );
306 if ( $this->moveTalk ) {
308 if ( $oldTalk && $newTalk && $oldTalk->exists() ) {
309 $mpTalk = $this->movePageFactory->newMovePage( $oldTalk, $newTalk );
310 $talkStatus = $mpTalk->isValidMove();
311 $talkStatus->merge( $mpTalk->probablyCanMove( $this->getAuthority() ) );
319 if ( !$talkStatus ) {
323 $talkStatus = $this->permManager->getPermissionStatus(
'move', $user, $oldTalk,
324 PermissionManager::RIGOR_QUICK );
332 $oldTalk = $this->oldTitle->getTalkPageIfDefined();
333 $oldTitleSubpages = $this->oldTitle->hasSubpages();
334 $oldTitleTalkSubpages = $this->oldTitle->getTalkPageIfDefined()->hasSubpages();
336 $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
337 $this->permManager->quickUserCan(
342 # We also want to be able to move assoc. subpage talk-pages even if base page
343 # has no associated talk page, so || with $oldTitleTalkSubpages.
344 $considerTalk = !$this->oldTitle->isTalkPage() &&
346 || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
349 $queryBuilder = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
352 ->where( [
'rd_namespace' => $this->oldTitle->getNamespace() ] )
353 ->andWhere( [
'rd_title' => $this->oldTitle->getDBkey() ] )
354 ->andWhere( [
'rd_interwiki' =>
'' ] );
356 $hasRedirects = (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
358 $hasRedirects =
false;
362 $talkOK = $talkStatus->isOK();
363 $mainOK = $status->isOK();
364 $talkIsArticle = $talkIsRedirect = $mainIsArticle = $mainIsRedirect =
false;
365 if ( count( $status->getMessages() ) == 1 ) {
366 $mainIsArticle = $status->hasMessage(
'articleexists' )
367 && $this->permManager->quickUserCan(
'delete', $user,
$newTitle );
368 $mainIsRedirect = $status->hasMessage(
'redirectexists' ) && (
370 $this->permManager->quickUserCan(
'delete-redirect', $user,
$newTitle ) ||
371 $this->permManager->quickUserCan(
'delete', $user,
$newTitle ) );
372 if ( $status->hasMessage(
'file-exists-sharedrepo' )
373 && $this->permManager->userHasRight( $user,
'reupload-shared' )
384 if ( count( $talkStatus->getMessages() ) == 1 ) {
385 $talkIsArticle = $talkStatus->hasMessage(
'articleexists' )
386 && $this->permManager->quickUserCan(
'delete', $user,
$newTitle );
387 $talkIsRedirect = $talkStatus->hasMessage(
'redirectexists' ) && (
389 $this->permManager->quickUserCan(
'delete-redirect', $user,
$newTitle ) ||
390 $this->permManager->quickUserCan(
'delete', $user,
$newTitle ) );
396 if ( ( $talkIsArticle && $mainIsArticle ) ||
397 ( $talkIsArticle && $mainIsRedirect ) ||
398 ( $talkIsRedirect && $mainIsArticle )
400 $warning = $out->msg(
'delete_and_move_text_2',
402 $newTalkTitle->getPrefixedText()
406 } elseif ( $mainIsRedirect && $talkIsRedirect ) {
407 $warning = $out->msg(
'delete_redirect_and_move_text_2',
409 $newTalkTitle->getPrefixedText()
414 } elseif ( $mainIsArticle && $talkOK ) {
418 } elseif ( $mainIsRedirect && $talkOK ) {
422 } elseif ( $talkIsArticle && $mainOK ) {
423 $warning = $out->msg(
'delete_and_move_text', $newTalkTitle->getPrefixedText() );
425 } elseif ( $talkIsRedirect && $mainOK ) {
426 $warning = $out->msg(
'delete_redirect_and_move_text', $newTalkTitle->getPrefixedText() );
430 $out->addHTML( Html::warningBox( $warning->parse() ) );
432 $messages = $status->getMessages();
434 if ( $status instanceof PermissionStatus ) {
435 $action_desc = $this->
msg(
'action-move' )->plain();
436 $errMsgHtml = $this->
msg(
'permissionserrorstext-withaction',
437 count( $messages ), $action_desc )->parseAsBlock();
439 $errMsgHtml = $this->
msg(
'cannotmove', count( $messages ) )->parseAsBlock();
442 if ( count( $messages ) == 1 ) {
443 $errMsgHtml .= $this->
msg( $messages[0] )->parseAsBlock();
447 foreach ( $messages as $msg ) {
448 $errStr[] = $this->
msg( $msg )->parse();
451 $errMsgHtml .=
'<ul><li>' . implode(
"</li>\n<li>", $errStr ) .
"</li></ul>\n";
453 $out->addHTML( Html::errorBox( $errMsgHtml ) );
455 $talkMessages = $talkStatus->getMessages();
456 if ( $talkMessages ) {
458 $errMsgHtml = $this->
msg(
'cannotmovetalk', count( $talkMessages ) )->parseAsBlock();
460 if ( count( $talkMessages ) == 1 ) {
461 $errMsgHtml .= $this->
msg( $talkMessages[0] )->parseAsBlock();
465 foreach ( $talkMessages as $msg ) {
466 $errStr[] = $this->
msg( $msg )->parse();
469 $errMsgHtml .=
'<ul><li>' . implode(
"</li>\n<li>", $errStr ) .
"</li></ul>\n";
471 $errMsgHtml .= $this->
msg(
'movetalk-unselect' )->parse();
472 $out->addHTML( Html::errorBox( $errMsgHtml ) );
476 if ( $this->restrictionStore->isProtected( $this->oldTitle,
'move' ) ) {
477 # Is the title semi-protected?
478 if ( $this->restrictionStore->isSemiProtected( $this->oldTitle,
'move' ) ) {
479 $noticeMsg =
'semiprotectedpagemovewarning';
481 # Then it must be protected based on static groups (regular)
482 $noticeMsg =
'protectedpagemovewarning';
484 LogEventsList::showLogExtract(
489 [
'lim' => 1,
'msgKey' => $noticeMsg ]
494 if ( $talkOK && $oldTalk && $oldTalk->exists() && $this->restrictionStore->isProtected( $oldTalk,
'move' ) ) {
495 # Is the title semi-protected?
496 if ( $this->restrictionStore->isSemiProtected( $oldTalk,
'move' ) ) {
497 $noticeMsg =
'semiprotectedtalkpagemovewarning';
499 # Then it must be protected based on static groups (regular)
500 $noticeMsg =
'protectedtalkpagemovewarning';
502 LogEventsList::showLogExtract(
507 [
'lim' => 1,
'msgKey' => $noticeMsg ]
514 $immovableNamespaces = [];
515 foreach ( $this->
getLanguage()->getNamespaces() as $nsId => $_ ) {
516 if ( !$this->nsInfo->isMovable( $nsId ) ) {
517 $immovableNamespaces[] = $nsId;
521 $moveOverProtection =
false;
522 if ( $this->newTitle ) {
523 if ( $this->restrictionStore->isProtected( $this->newTitle,
'create' ) ) {
524 # Is the title semi-protected?
525 if ( $this->restrictionStore->isSemiProtected( $this->newTitle,
'create' ) ) {
526 $noticeMsg =
'semiprotectedpagemovecreatewarning';
528 # Then it must be protected based on static groups (regular)
529 $noticeMsg =
'protectedpagemovecreatewarning';
531 LogEventsList::showLogExtract(
536 [
'lim' => 1,
'msgKey' => $noticeMsg ]
538 $moveOverProtection =
true;
541 if ( $oldTalk && $oldTalk->exists() && $talkOK &&
542 $newTalk && $this->restrictionStore->isProtected( $newTalk,
'create' )
544 # Is the title semi-protected?
545 if ( $this->restrictionStore->isSemiProtected( $newTalk,
'create' ) ) {
546 $noticeMsg =
'semiprotectedpagemovetalkcreatewarning';
548 # Then it must be protected based on static groups (regular)
549 $noticeMsg =
'protectedpagemovetalkcreatewarning';
551 LogEventsList::showLogExtract(
556 [
'lim' => 1,
'msgKey' => $noticeMsg ]
558 $moveOverProtection =
true;
565 $fields[] =
new FieldLayout(
566 new ComplexTitleInputWidget( [
567 'id' =>
'wpNewTitle',
569 'id' =>
'wpNewTitleNs',
570 'name' =>
'wpNewTitleNs',
572 'exclude' => $immovableNamespaces,
575 'id' =>
'wpNewTitleMain',
576 'name' =>
'wpNewTitleMain',
579 'suggestions' =>
false,
584 'label' => $this->msg(
'newtitle' )->text(),
589 $options = Html::listDropdownOptions(
590 $this->
msg(
'movepage-reason-dropdown' )
591 ->page( $this->oldTitle )
592 ->inContentLanguage()
594 [
'other' => $this->
msg(
'movereasonotherlist' )->text() ]
596 $options = Html::listDropdownOptionsOoui( $options );
598 $fields[] =
new FieldLayout(
599 new DropdownInputWidget( [
600 'name' =>
'wpReasonList',
601 'inputId' =>
'wpReasonList',
603 'value' => $this->
getRequest()->getText(
'wpReasonList',
'other' ),
604 'options' => $options,
607 'label' => $this->
msg(
'movereason' )->text(),
615 $fields[] =
new FieldLayout(
616 new TextInputWidget( [
617 'name' =>
'wpReason',
619 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
621 'value' => $this->
getRequest()->getText(
'wpReason' ),
624 'label' => $this->
msg(
'moveotherreason' )->text(),
629 if ( $considerTalk ) {
630 $fields[] =
new FieldLayout(
631 new CheckboxInputWidget( [
632 'name' =>
'wpMovetalk',
633 'id' =>
'wpMovetalk',
638 'selected' => $this->moveTalk,
641 'label' => $this->
msg(
'movetalk' )->text(),
642 'help' =>
new HtmlSnippet( $this->
msg(
'movepagetalktext' )->parseAsBlock() ),
643 'helpInline' =>
true,
645 'id' =>
'wpMovetalk-field',
650 if ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
651 if ( $createRedirect ) {
658 $fields[] =
new FieldLayout(
659 new CheckboxInputWidget( [
660 'name' =>
'wpLeaveRedirect',
661 'id' =>
'wpLeaveRedirect',
663 'selected' => $isChecked,
664 'disabled' => $isDisabled,
667 'label' => $this->
msg(
'move-leave-redirect' )->text(),
673 if ( $hasRedirects ) {
674 $fields[] =
new FieldLayout(
675 new CheckboxInputWidget( [
676 'name' =>
'wpFixRedirects',
677 'id' =>
'wpFixRedirects',
679 'selected' => $this->fixRedirects,
682 'label' => $this->
msg(
'fix-double-redirects' )->text(),
688 if ( $canMoveSubpage ) {
690 $fields[] =
new FieldLayout(
691 new CheckboxInputWidget( [
692 'name' =>
'wpMovesubpages',
693 'id' =>
'wpMovesubpages',
695 'selected' => $this->moveSubpages,
698 'label' =>
new HtmlSnippet(
700 ( $this->oldTitle->hasSubpages()
702 :
'move-talk-subpages' )
703 )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
710 # Don't allow watching if user is not logged in
711 if ( $user->isRegistered() ) {
712 $watchChecked = ( $this->watch || $this->userOptionsLookup->getBoolOption( $user,
'watchmoves' )
713 || $this->watchlistManager->isWatched( $user, $this->oldTitle ) );
714 $fields[] =
new FieldLayout(
715 new CheckboxInputWidget( [
717 'id' =>
'watch', # ew
720 'selected' => $watchChecked,
723 'label' => $this->
msg(
'move-watch' )->text(),
728 # Add a dropdown for watchlist expiry times in the form, T261230
730 $expiryOptions = WatchAction::getExpiryOptions(
732 $this->watchedItemStore->getWatchedItem( $user, $this->oldTitle )
734 # Reformat the options to match what DropdownInputWidget wants.
736 foreach ( $expiryOptions[
'options'] as $label => $value ) {
737 $options[] = [
'data' => $value,
'label' => $label ];
740 $fields[] =
new FieldLayout(
741 new DropdownInputWidget( [
742 'name' =>
'wpWatchlistExpiry',
743 'id' =>
'wpWatchlistExpiry',
745 'options' => $options,
748 'label' => $this->
msg(
'confirm-watch-label' )->text(),
749 'id' =>
'wpWatchlistExpiryLabel',
759 $hiddenFields .= Html::hidden(
'wpMoveOverSharedFile',
'1' );
766 $msg = $this->
msg(
'delete_and_move_confirm_2',
768 $deleteAndMove[0]->getPrefixedText(),
774 $msg = $this->
msg(
'delete_and_move_confirm', $deleteAndMove[0]->getPrefixedText() )->text();
776 $fields[] =
new FieldLayout(
777 new CheckboxInputWidget( [
778 'name' =>
'wpDeleteAndMove',
779 'id' =>
'wpDeleteAndMove',
788 if ( $moveOverProtection ) {
789 $fields[] =
new FieldLayout(
790 new CheckboxInputWidget( [
791 'name' =>
'wpMoveOverProtection',
792 'id' =>
'wpMoveOverProtection',
796 'label' => $this->
msg(
'move_over_protection_confirm' )->text(),
802 $fields[] =
new FieldLayout(
803 new ButtonInputWidget( [
805 'value' => $this->
msg(
'movepagebtn' )->text(),
806 'label' => $this->
msg(
'movepagebtn' )->text(),
807 'flags' => [
'primary',
'progressive' ],
809 'accessKey' => Linker::accesskey(
'move' ),
810 'title' => Linker::titleAttrib(
'move' ),
817 $fieldset =
new FieldsetLayout( [
818 'label' => $this->
msg(
'move-page-legend' )->text(),
819 'id' =>
'mw-movepage-table',
823 $form =
new FormLayout( [
825 'action' => $this->
getPageTitle()->getLocalURL(
'action=submit' ),
828 $form->appendContent(
832 Html::hidden(
'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
833 Html::hidden(
'wpEditToken', $user->getEditToken() )
839 'classes' => [
'movepage-wrapper' ],
846 if ( $this->
getAuthority()->isAllowed(
'editinterface' ) ) {
848 $this->
msg(
'movepage-reason-dropdown' )->inContentLanguage()->getTitle(),
849 $this->
msg(
'movepage-edit-reasonlist' )->text(),
851 [
'action' =>
'edit' ]
853 $out->addHTML( Html::rawElement(
'p', [
'class' =>
'mw-movepage-editreasons' ], $link ) );
856 $this->showLogFragment( $this->oldTitle );
857 $this->showSubpages( $this->oldTitle );
861 if ( !$title->exists() ) {
864 $redir2 = $title->isSingleRevRedirect();
866 $permStatus = $this->permManager->getPermissionStatus(
867 $redir2 ?
'delete-redirect' :
'delete',
870 if ( !$permStatus->isGood() ) {
872 if ( !$this->permManager->userCan(
'delete', $user, $title ) ) {
886 $page = $this->wikiPageFactory->newFromTitle( $title );
887 $delPage = $this->deletePageFactory->newDeletePage( $page, $user );
890 if ( $delPage->isBatchedDelete( 5 ) ) {
897 if ( $title->getNamespace() ===
NS_FILE ) {
898 $file = $this->repoGroup->getLocalRepo()->newFile( $title );
899 $file->load( IDBAccessObject::READ_LATEST );
900 if ( $file->exists() ) {
901 $file->deleteFile(
$reason, $user,
false );
905 $deletionLog = $redir2 ?
'delete_redir2' :
'delete';
906 $deleteStatus = $delPage
907 ->setLogSubtype( $deletionLog )
909 ->forceImmediate(
true )
912 return $deleteStatus;
915 private function doSubmit() {
918 if ( $user->pingLimiter(
'move' ) ) {
919 throw new ThrottledError;
922 $ot = $this->oldTitle;
923 $nt = $this->newTitle;
925 # don't allow moving to pages with # in
926 if ( !$nt || $nt->hasFragment() ) {
927 $this->showForm( StatusValue::newFatal(
'badtitletext' ) );
932 if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
933 $this->moveTalk =
false;
936 $oldTalk = $ot->getTalkPageIfDefined();
937 $newTalk = $nt->getTalkPageIfDefined();
939 # Show a warning if the target file exists on a shared repo
940 if ( $nt->getNamespace() ===
NS_FILE
941 && !( $this->moveOverShared && $this->permManager->userHasRight( $user,
'reupload-shared' ) )
942 && !$this->repoGroup->getLocalRepo()->findFile( $nt )
943 && $this->repoGroup->findFile( $nt )
945 $this->showForm( StatusValue::newFatal(
'file-exists-sharedrepo' ) );
950 # Show a warning if procted (showForm handles the warning )
951 if ( !$this->moveOverProtection ) {
952 if ( $this->restrictionStore->isProtected( $nt,
'create' ) ) {
956 if ( $this->moveTalk && $this->restrictionStore->isProtected( $newTalk,
'create' ) ) {
962 $handler = $this->contentHandlerFactory->getContentHandler( $ot->getContentModel() );
964 if ( !$handler->supportsRedirects() || (
970 $createRedirect =
false;
971 } elseif ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
972 $createRedirect = $this->leaveRedirect;
974 $createRedirect =
true;
978 $mp = $this->movePageFactory->newMovePage( $ot, $nt );
979 $permStatusMain = $mp->authorizeMove( $this->
getAuthority(), $this->reason );
980 $permStatusMain->merge( $mp->isValidMove() );
982 $onlyMovingTalkSubpages =
false;
983 if ( $this->moveTalk ) {
984 $mpTalk = $this->movePageFactory->newMovePage( $oldTalk, $newTalk );
985 $permStatusTalk = $mpTalk->authorizeMove( $this->
getAuthority(), $this->reason );
986 $permStatusTalk->merge( $mpTalk->isValidMove() );
989 if ( !$permStatusTalk->isOK() &&
990 !$permStatusTalk->hasMessagesExcept(
'movepage-source-doesnt-exist' )
992 $permStatusTalk->setOK(
true );
993 $onlyMovingTalkSubpages =
true;
1000 if ( $this->deleteAndMove ) {
1003 if ( $permStatusMain->hasMessagesExcept(
'redirectexists',
'articleexists' ) ||
1004 $permStatusTalk->hasMessagesExcept(
'redirectexists',
'articleexists' ) ) {
1005 $this->showForm( $permStatusMain, $permStatusTalk );
1014 $deleteStatus = $this->vacateTitle( $nt, $user, $ot );
1015 if ( !$deleteStatus->isGood() ) {
1016 $this->showForm( $deleteStatus );
1019 if ( $this->moveTalk ) {
1020 $deleteStatus = $this->vacateTitle( $newTalk, $user, $oldTalk );
1021 if ( !$deleteStatus->isGood() ) {
1024 $this->showForm( StatusValue::newGood(), $deleteStatus );
1028 } elseif ( !$permStatusMain->isOK() || !$permStatusTalk->isOK() ) {
1030 $this->showForm( $permStatusMain, $permStatusTalk );
1036 $mainStatus = $mp->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1037 if ( !$mainStatus->isOK() ) {
1038 $this->showForm( $mainStatus );
1043 if ( $fixRedirects ) {
1052 if ( $this->moveTalk && !$onlyMovingTalkSubpages ) {
1053 $talkStatus = $mpTalk->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1057 '@phan-var Status<string> $talkStatus';
1058 $talkStatus->value = $newTalk->getPrefixedText();
1059 $moveStatuses[$oldTalk->getPrefixedText()] = $talkStatus;
1063 if ( $this->moveSubpages ) {
1064 if ( $this->permManager->userCan(
'move-subpages', $user, $ot ) ) {
1065 $mp->setMaximumMovedPages( $maximumMovedPages - count( $moveStatuses ) );
1066 $subpageStatus = $mp->moveSubpagesIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1067 $moveStatuses = array_merge( $moveStatuses, $subpageStatus->value );
1069 if ( $this->moveTalk && $maximumMovedPages > count( $moveStatuses ) &&
1070 $this->permManager->userCan(
'move-subpages', $user, $oldTalk ) &&
1071 ( $onlyMovingTalkSubpages || $talkStatus->isOK() )
1073 $mpTalk->setMaximumMovedPages( $maximumMovedPages - count( $moveStatuses ) );
1074 $talkSubStatus = $mpTalk->moveSubpagesIfAllowed(
1077 $moveStatuses = array_merge( $moveStatuses, $talkSubStatus->value );
1084 $out->setPageTitleMsg( $this->
msg(
'pagemovedsub' ) );
1087 $oldLink = $linkRenderer->makeLink(
1090 [
'id' =>
'movepage-oldlink' ],
1091 [
'redirect' =>
'no' ]
1093 $newLink = $linkRenderer->makeKnownLink(
1096 [
'id' =>
'movepage-newlink' ]
1098 $oldText = $ot->getPrefixedText();
1099 $newText = $nt->getPrefixedText();
1101 $out->addHTML( $this->
msg(
'movepage-moved' )->rawParams( $oldLink,
1102 $newLink )->params( $oldText, $newText )->parseAsBlock() );
1103 $out->addWikiMsg( isset( $mainStatus->getValue()[
'redirectRevision'] ) ?
1104 'movepage-moved-redirect' :
1105 'movepage-moved-noredirect' );
1107 $this->getHookRunner()->onSpecialMovepageAfterMove( $this, $ot, $nt );
1110 foreach ( $moveStatuses as $oldSubpage => $subpageStatus ) {
1111 if ( $subpageStatus->hasMessage(
'movepage-max-pages' ) ) {
1112 $extraOutput[] = $this->
msg(
'movepage-max-pages' )
1113 ->numParams( $maximumMovedPages )->escaped();
1116 $oldSubpage = Title::newFromText( $oldSubpage );
1117 $newSubpage = Title::newFromText( $subpageStatus->value );
1118 if ( $subpageStatus->isGood() ) {
1119 if ( $fixRedirects ) {
1122 $oldLink = $linkRenderer->makeLink( $oldSubpage,
null, [], [
'redirect' =>
"no" ] );
1123 $newLink = $linkRenderer->makeKnownLink( $newSubpage );
1125 $extraOutput[] = $this->
msg(
'movepage-page-moved' )->rawParams(
1128 } elseif ( $subpageStatus->hasMessage(
'articleexists' )
1129 || $subpageStatus->hasMessage(
'redirectexists' )
1131 $link = $linkRenderer->makeKnownLink( $newSubpage );
1132 $extraOutput[] = $this->
msg(
'movepage-page-exists' )->rawParams( $link )->escaped();
1134 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
1135 if ( $newSubpage ) {
1136 $newLink = $linkRenderer->makeLink( $newSubpage );
1139 $newLink = htmlspecialchars( $subpageStatus->value );
1141 $extraOutput[] = $this->
msg(
'movepage-page-unmoved' )
1142 ->rawParams( $oldLink, $newLink )->escaped();
1146 if ( $extraOutput !== [] ) {
1147 $out->addHTML(
"<ul>\n<li>" . implode(
"</li>\n<li>", $extraOutput ) .
"</li>\n</ul>" );
1150 # Deal with watches (we don't watch subpages)
1151 # Get text from expiry selection dropdown, T261230
1152 $expiry = $this->
getRequest()->getText(
'wpWatchlistExpiry' );
1154 $expiry = ExpiryDef::normalizeExpiry( $expiry, TS::ISO_8601 );
1159 $this->watchlistManager->setWatch(
1166 $this->watchlistManager->setWatch(
1174 private function showLogFragment( Title $title ) {
1175 $moveLogPage =
new LogPage(
'move' );
1177 $out->addHTML(
Html::element(
'h2', [], $moveLogPage->getName()->text() ) );
1178 LogEventsList::showLogExtract( $out,
'move', $title );
1187 private function showSubpages( $title ) {
1189 $nsHasSubpages = $this->nsInfo->hasSubpages( $title->getNamespace() );
1190 $subpages = $title->getSubpages( $maximumMovedPages + 1 );
1191 $count = $subpages instanceof TitleArrayFromResult ? $subpages->count() : 0;
1193 $titleIsTalk = $title->isTalkPage();
1194 $subpagesTalk = $title->getTalkPage()->getSubpages( $maximumMovedPages + 1 );
1195 $countTalk = $subpagesTalk instanceof TitleArrayFromResult ? $subpagesTalk->count() : 0;
1196 $totalCount = $count + $countTalk;
1198 if ( !$nsHasSubpages && $countTalk == 0 ) {
1204 [
'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ]
1207 if ( $nsHasSubpages ) {
1208 $this->showSubpagesList(
1209 $subpages, $count,
'movesubpagetext',
'movesubpagetext-truncated',
true
1213 if ( !$titleIsTalk && $countTalk > 0 ) {
1214 $this->showSubpagesList(
1215 $subpagesTalk, $countTalk,
'movesubpagetalktext',
'movesubpagetalktext-truncated'
1220 private function showSubpagesList(
1221 TitleArrayFromResult $subpages,
int $pagecount,
string $msg,
string $truncatedMsg,
bool $noSubpageMsg =
false
1226 if ( $pagecount == 0 && $noSubpageMsg ) {
1227 $out->addWikiMsg(
'movenosubpage' );
1233 if ( $pagecount > $maximumMovedPages ) {
1234 $subpages = $this->truncateSubpagesList( $subpages );
1235 $out->addWikiMsg( $truncatedMsg, $this->getLanguage()->formatNum( $maximumMovedPages ) );
1237 $out->addWikiMsg( $msg, $this->getLanguage()->formatNum( $pagecount ) );
1239 $out->addHTML(
"<ul>\n" );
1241 $this->linkBatchFactory->newLinkBatch( $subpages )
1242 ->setCaller( __METHOD__ )
1246 foreach ( $subpages as $subpage ) {
1247 $link = $linkRenderer->makeLink( $subpage );
1248 $out->addHTML(
"<li>$link</li>\n" );
1250 $out->addHTML(
"</ul>\n" );
1253 private function truncateSubpagesList( iterable $subpages ): array {
1255 foreach ( $subpages as $subpage ) {
1256 $returnArray[] = $subpage;
1261 return $returnArray;
1273 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );