89 private bool $moveOverProtection;
92 private $watch =
false;
111 parent::__construct(
'Movepage' );
129 $target = $par ?? $request->getText(
'target' );
130 $oldTitleText = $request->getText(
'wpOldTitle', $target );
131 $this->oldTitle = Title::newFromText( $oldTitleText );
133 if ( !$this->oldTitle ) {
137 $this->
getOutput()->addBacklinkSubtitle( $this->oldTitle );
139 $this->
getOutput()->addModuleStyles(
'mediawiki.codex.messagebox.styles' );
141 if ( !$this->oldTitle->exists() ) {
145 $newTitleTextMain = $request->getText(
'wpNewTitleMain' );
146 $newTitleTextNs = $request->getInt(
'wpNewTitleNs', $this->oldTitle->getNamespace() );
149 $newTitleText_bc = $request->getText(
'wpNewTitle' );
150 $this->newTitle = strlen( $newTitleText_bc ) > 0
151 ? Title::newFromText( $newTitleText_bc )
152 : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
155 $isSubmit = $request->getRawVal(
'action' ) ===
'submit' && $request->wasPosted();
157 $reasonList = $request->getText(
'wpReasonList',
'other' );
158 $reason = $request->getText(
'wpReason' );
159 if ( $reasonList ===
'other' ) {
162 $this->reason = $reasonList . $this->
msg(
'colon-separator' )->inContentLanguage()->text() .
$reason;
164 $this->reason = $reasonList;
169 $this->moveTalk = $request->getBool(
'wpMovetalk', $def );
170 $this->fixRedirects = $request->getBool(
'wpFixRedirects', $def );
171 $this->leaveRedirect = $request->getBool(
'wpLeaveRedirect', $def );
173 $this->moveSubpages = $request->getBool(
'wpMovesubpages', $def );
174 $this->deleteAndMove = $request->getBool(
'wpDeleteAndMove' );
175 $this->moveOverShared = $request->getBool(
'wpMoveOverSharedFile' );
176 $this->moveOverProtection = $request->getBool(
'wpMoveOverProtection' );
177 $this->watch = $request->getCheck(
'wpWatch' ) && $user->isRegistered();
181 if ( $isSubmit && $user->matchEditToken( $request->getVal(
'wpEditToken' ) ) ) {
183 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
184 PermissionManager::RIGOR_SECURE );
186 $user->scheduleSpreadBlock();
187 if ( !$permStatus->isGood() ) {
193 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
194 PermissionManager::RIGOR_FULL );
195 if ( !$permStatus->isGood() ) {
196 $user->scheduleSpreadBlock();
211 $this->
getSkin()->setRelevantTitle( $this->oldTitle );
214 $out->setPageTitleMsg( $this->
msg(
'move-page' )->plaintextParams( $this->oldTitle->getPrefixedText() ) );
215 $out->addModuleStyles( [
217 'mediawiki.interface.helpers.styles'
219 $out->addModules(
'mediawiki.misc-authed-ooui' );
222 $handler = $this->contentHandlerFactory
223 ->getContentHandler( $this->oldTitle->getContentModel() );
224 $createRedirect = $handler->supportsRedirects() && !(
232 $out->addWikiMsg(
'movepagetext' );
234 $out->addWikiMsg( $createRedirect ?
235 'movepagetext-noredirectfixer' :
236 'movepagetext-noredirectsupport' );
239 if ( $this->oldTitle->getNamespace() ===
NS_USER && !$this->oldTitle->isSubpage() ) {
242 $out->msg(
'moveuserpage-warning' )->parse(),
243 'mw-moveuserpage-warning'
247 $this->moveTalk = $this->
getRequest()->getBool(
"wpMovetalk",
false );
248 } elseif ( $this->oldTitle->getNamespace() ===
NS_CATEGORY ) {
251 $out->msg(
'movecategorypage-warning' )->parse(),
252 'mw-movecategorypage-warning'
265 # Show the current title as a default
266 # when the form is first opened.
268 } elseif ( !$status ) {
269 # If a title was supplied, probably from the move log revert
270 # link, check for validity. We can then show some diagnostic
271 # information and save a click.
272 $mp = $this->movePageFactory->newMovePage( $this->oldTitle,
$newTitle );
273 $status = $mp->isValidMove();
274 $status->merge( $mp->probablyCanMove( $this->getAuthority() ) );
275 if ( $this->moveTalk ) {
277 if ( $oldTalk && $newTalk && $oldTalk->exists() ) {
278 $mpTalk = $this->movePageFactory->newMovePage( $oldTalk, $newTalk );
279 $talkStatus = $mpTalk->isValidMove();
280 $talkStatus->merge( $mpTalk->probablyCanMove( $this->getAuthority() ) );
288 if ( !$talkStatus ) {
292 $talkStatus = $this->permManager->getPermissionStatus(
'move', $user, $oldTalk,
293 PermissionManager::RIGOR_QUICK );
301 $oldTalk = $this->oldTitle->getTalkPageIfDefined();
302 $oldTitleSubpages = $this->oldTitle->hasSubpages();
303 $oldTitleTalkSubpages = $this->oldTitle->getTalkPageIfDefined()->hasSubpages();
305 $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
306 $this->permManager->quickUserCan(
311 # We also want to be able to move assoc. subpage talk-pages even if base page
312 # has no associated talk page, so || with $oldTitleTalkSubpages.
313 $considerTalk = !$this->oldTitle->isTalkPage() &&
315 || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
318 $queryBuilder = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
321 ->where( [
'rd_namespace' => $this->oldTitle->getNamespace() ] )
322 ->andWhere( [
'rd_title' => $this->oldTitle->getDBkey() ] )
323 ->andWhere( [
'rd_interwiki' =>
'' ] );
325 $hasRedirects = (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
327 $hasRedirects =
false;
331 $talkOK = $talkStatus->isOK();
332 $mainOK = $status->isOK();
333 $talkIsArticle = $talkIsRedirect = $mainIsArticle = $mainIsRedirect =
false;
334 if ( count( $status->getMessages() ) == 1 ) {
335 $mainIsArticle = $status->hasMessage(
'articleexists' )
336 && $this->permManager->quickUserCan(
'delete', $user,
$newTitle );
337 $mainIsRedirect = $status->hasMessage(
'redirectexists' ) && (
339 $this->permManager->quickUserCan(
'delete-redirect', $user,
$newTitle ) ||
340 $this->permManager->quickUserCan(
'delete', $user,
$newTitle ) );
341 if ( $status->hasMessage(
'file-exists-sharedrepo' )
342 && $this->permManager->userHasRight( $user,
'reupload-shared' )
353 if ( count( $talkStatus->getMessages() ) == 1 ) {
354 $talkIsArticle = $talkStatus->hasMessage(
'articleexists' )
355 && $this->permManager->quickUserCan(
'delete', $user,
$newTitle );
356 $talkIsRedirect = $talkStatus->hasMessage(
'redirectexists' ) && (
358 $this->permManager->quickUserCan(
'delete-redirect', $user,
$newTitle ) ||
359 $this->permManager->quickUserCan(
'delete', $user,
$newTitle ) );
365 if ( ( $talkIsArticle && $mainIsArticle ) ||
366 ( $talkIsArticle && $mainIsRedirect ) ||
367 ( $talkIsRedirect && $mainIsArticle )
369 $warning = $out->msg(
'delete_and_move_text_2',
371 $newTalkTitle->getPrefixedText()
375 } elseif ( $mainIsRedirect && $talkIsRedirect ) {
376 $warning = $out->msg(
'delete_redirect_and_move_text_2',
378 $newTalkTitle->getPrefixedText()
383 } elseif ( $mainIsArticle && $talkOK ) {
387 } elseif ( $mainIsRedirect && $talkOK ) {
391 } elseif ( $talkIsArticle && $mainOK ) {
392 $warning = $out->msg(
'delete_and_move_text', $newTalkTitle->getPrefixedText() );
394 } elseif ( $talkIsRedirect && $mainOK ) {
395 $warning = $out->msg(
'delete_redirect_and_move_text', $newTalkTitle->getPrefixedText() );
399 $out->addHTML( Html::warningBox( $warning->parse() ) );
401 $messages = $status->getMessages();
403 if ( $status instanceof PermissionStatus ) {
404 $action_desc = $this->
msg(
'action-move' )->plain();
405 $errMsgHtml = $this->
msg(
'permissionserrorstext-withaction',
406 count( $messages ), $action_desc )->parseAsBlock();
408 $errMsgHtml = $this->
msg(
'cannotmove', count( $messages ) )->parseAsBlock();
411 if ( count( $messages ) == 1 ) {
412 $errMsgHtml .= $this->
msg( $messages[0] )->parseAsBlock();
416 foreach ( $messages as $msg ) {
417 $errStr[] = $this->
msg( $msg )->parse();
422 $errStr = array_unique( $errStr );
424 $errMsgHtml .=
'<ul><li>' . implode(
"</li>\n<li>", $errStr ) .
"</li></ul>\n";
426 $out->addHTML( Html::errorBox( $errMsgHtml ) );
428 $talkMessages = $talkStatus->getMessages();
429 if ( $talkMessages ) {
431 $errMsgHtml = $this->
msg(
'cannotmovetalk', count( $talkMessages ) )->parseAsBlock();
433 if ( count( $talkMessages ) == 1 ) {
434 $errMsgHtml .= $this->
msg( $talkMessages[0] )->parseAsBlock();
438 foreach ( $talkMessages as $msg ) {
439 $errStr[] = $this->
msg( $msg )->parse();
442 $errMsgHtml .=
'<ul><li>' . implode(
"</li>\n<li>", $errStr ) .
"</li></ul>\n";
444 $errMsgHtml .= $this->
msg(
'movetalk-unselect' )->parse();
445 $out->addHTML( Html::errorBox( $errMsgHtml ) );
449 if ( $this->restrictionStore->isProtected( $this->oldTitle,
'move' ) ) {
450 # Is the title semi-protected?
451 if ( $this->restrictionStore->isSemiProtected( $this->oldTitle,
'move' ) ) {
452 $noticeMsg =
'semiprotectedpagemovewarning';
454 # Then it must be protected based on static groups (regular)
455 $noticeMsg =
'protectedpagemovewarning';
457 LogEventsList::showLogExtract(
462 [
'lim' => 1,
'msgKey' => $noticeMsg ]
467 if ( $talkOK && $oldTalk && $oldTalk->exists() && $this->restrictionStore->isProtected( $oldTalk,
'move' ) ) {
468 # Is the title semi-protected?
469 if ( $this->restrictionStore->isSemiProtected( $oldTalk,
'move' ) ) {
470 $noticeMsg =
'semiprotectedtalkpagemovewarning';
472 # Then it must be protected based on static groups (regular)
473 $noticeMsg =
'protectedtalkpagemovewarning';
475 LogEventsList::showLogExtract(
480 [
'lim' => 1,
'msgKey' => $noticeMsg ]
487 $immovableNamespaces = [];
488 foreach ( $this->
getLanguage()->getNamespaces() as $nsId => $_ ) {
489 if ( !$this->nsInfo->isMovable( $nsId ) ) {
490 $immovableNamespaces[] = $nsId;
494 $moveOverProtection =
false;
495 if ( $this->newTitle && $mainOK ) {
498 if ( $this->restrictionStore->isProtected( $this->newTitle,
'create' ) ) {
499 # Is the title semi-protected?
500 if ( $this->restrictionStore->isSemiProtected( $this->newTitle,
'create' ) ) {
501 $noticeMsg =
'semiprotectedpagemovecreatewarning';
503 # Then it must be protected based on static groups (regular)
504 $noticeMsg =
'protectedpagemovecreatewarning';
506 LogEventsList::showLogExtract(
511 [
'lim' => 1,
'msgKey' => $noticeMsg ]
513 $moveOverProtection =
true;
516 if ( $oldTalk && $oldTalk->exists() && $talkOK &&
517 $newTalk && $this->restrictionStore->isProtected( $newTalk,
'create' )
519 # Is the title semi-protected?
520 if ( $this->restrictionStore->isSemiProtected( $newTalk,
'create' ) ) {
521 $noticeMsg =
'semiprotectedpagemovetalkcreatewarning';
523 # Then it must be protected based on static groups (regular)
524 $noticeMsg =
'protectedpagemovetalkcreatewarning';
526 LogEventsList::showLogExtract(
531 [
'lim' => 1,
'msgKey' => $noticeMsg ]
533 $moveOverProtection =
true;
540 $fields[] =
new FieldLayout(
541 new ComplexTitleInputWidget( [
542 'id' =>
'wpNewTitle',
544 'id' =>
'wpNewTitleNs',
545 'name' =>
'wpNewTitleNs',
547 'exclude' => $immovableNamespaces,
550 'id' =>
'wpNewTitleMain',
551 'name' =>
'wpNewTitleMain',
554 'suggestions' =>
false,
559 'label' => $this->msg(
'newtitle' )->text(),
564 $options = Html::listDropdownOptions(
565 $this->
msg(
'movepage-reason-dropdown' )
566 ->page( $this->oldTitle )
567 ->inContentLanguage()
569 [
'other' => $this->
msg(
'movereasonotherlist' )->text() ]
571 $options = Html::listDropdownOptionsOoui( $options );
573 $fields[] =
new FieldLayout(
574 new DropdownInputWidget( [
575 'name' =>
'wpReasonList',
576 'inputId' =>
'wpReasonList',
578 'value' => $this->
getRequest()->getText(
'wpReasonList',
'other' ),
579 'options' => $options,
582 'label' => $this->
msg(
'movereason' )->text(),
590 $fields[] =
new FieldLayout(
591 new TextInputWidget( [
592 'name' =>
'wpReason',
594 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
596 'value' => $this->
getRequest()->getText(
'wpReason' ),
599 'label' => $this->
msg(
'moveotherreason' )->text(),
604 if ( $considerTalk ) {
605 $fields[] =
new FieldLayout(
606 new CheckboxInputWidget( [
607 'name' =>
'wpMovetalk',
608 'id' =>
'wpMovetalk',
613 'selected' => $this->moveTalk,
616 'label' => $this->
msg(
'movetalk' )->text(),
617 'help' =>
new HtmlSnippet( $this->
msg(
'movepagetalktext' )->parseAsBlock() ),
618 'helpInline' =>
true,
620 'id' =>
'wpMovetalk-field',
625 if ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
626 if ( $createRedirect ) {
633 $fields[] =
new FieldLayout(
634 new CheckboxInputWidget( [
635 'name' =>
'wpLeaveRedirect',
636 'id' =>
'wpLeaveRedirect',
638 'selected' => $isChecked,
639 'disabled' => $isDisabled,
642 'label' => $this->
msg(
'move-leave-redirect' )->text(),
648 if ( $hasRedirects ) {
649 $fields[] =
new FieldLayout(
650 new CheckboxInputWidget( [
651 'name' =>
'wpFixRedirects',
652 'id' =>
'wpFixRedirects',
654 'selected' => $this->fixRedirects,
657 'label' => $this->
msg(
'fix-double-redirects' )->text(),
663 if ( $canMoveSubpage ) {
665 $fields[] =
new FieldLayout(
666 new CheckboxInputWidget( [
667 'name' =>
'wpMovesubpages',
668 'id' =>
'wpMovesubpages',
670 'selected' => $this->moveSubpages,
673 'label' =>
new HtmlSnippet(
675 ( $this->oldTitle->hasSubpages()
677 :
'move-talk-subpages' )
678 )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
685 # Don't allow watching if user is not logged in
686 if ( $user->isRegistered() ) {
687 $watchChecked = ( $this->watch || $this->userOptionsLookup->getBoolOption( $user,
'watchmoves' )
688 || $this->watchlistManager->isWatched( $user, $this->oldTitle ) );
689 $fields[] =
new FieldLayout(
690 new CheckboxInputWidget( [
692 'id' =>
'watch', # ew
695 'selected' => $watchChecked,
698 'label' => $this->
msg(
'move-watch' )->text(),
703 # Add a dropdown for watchlist expiry times in the form, T261230
705 $expiryOptions = WatchAction::getExpiryOptions(
707 $this->watchedItemStore->getWatchedItem( $user, $this->oldTitle )
709 # Reformat the options to match what DropdownInputWidget wants.
711 foreach ( $expiryOptions[
'options'] as $label => $value ) {
712 $options[] = [
'data' => $value,
'label' => $label ];
715 $fields[] =
new FieldLayout(
716 new DropdownInputWidget( [
717 'name' =>
'wpWatchlistExpiry',
718 'id' =>
'wpWatchlistExpiry',
720 'options' => $options,
723 'label' => $this->
msg(
'confirm-watch-label' )->text(),
724 'id' =>
'wpWatchlistExpiryLabel',
734 $hiddenFields .= Html::hidden(
'wpMoveOverSharedFile',
'1' );
741 $msg = $this->
msg(
'delete_and_move_confirm_2',
742 $deleteAndMove[0]->getPrefixedText(),
747 $msg = $this->
msg(
'delete_and_move_confirm', $deleteAndMove[0]->getPrefixedText() )->text();
749 $fields[] =
new FieldLayout(
750 new CheckboxInputWidget( [
751 'name' =>
'wpDeleteAndMove',
752 'id' =>
'wpDeleteAndMove',
761 if ( $moveOverProtection ) {
762 $fields[] =
new FieldLayout(
763 new CheckboxInputWidget( [
764 'name' =>
'wpMoveOverProtection',
765 'id' =>
'wpMoveOverProtection',
769 'label' => $this->
msg(
'move_over_protection_confirm' )->text(),
775 $fields[] =
new FieldLayout(
776 new ButtonInputWidget( [
778 'value' => $this->
msg(
'movepagebtn' )->text(),
779 'label' => $this->
msg(
'movepagebtn' )->text(),
780 'flags' => [
'primary',
'progressive' ],
782 'accessKey' => Linker::accesskey(
'move' ),
783 'title' => Linker::titleAttrib(
'move' ),
790 $fieldset =
new FieldsetLayout( [
791 'label' => $this->
msg(
'move-page-legend' )->text(),
792 'id' =>
'mw-movepage-table',
796 $form =
new FormLayout( [
798 'action' => $this->
getPageTitle()->getLocalURL(
'action=submit' ),
801 $form->appendContent(
805 Html::hidden(
'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
806 Html::hidden(
'wpEditToken', $user->getEditToken() )
812 'classes' => [
'movepage-wrapper' ],
819 if ( $this->
getAuthority()->isAllowed(
'editinterface' ) ) {
821 $this->
msg(
'movepage-reason-dropdown' )->inContentLanguage()->getTitle(),
822 $this->
msg(
'movepage-edit-reasonlist' )->text(),
824 [
'action' =>
'edit' ]
826 $out->addHTML( Html::rawElement(
'p', [
'class' =>
'mw-movepage-editreasons' ], $link ) );
829 $this->showLogFragment( $this->oldTitle );
830 $this->showSubpages( $this->oldTitle );
834 if ( !$title->exists() ) {
837 $redir2 = $title->isSingleRevRedirect();
839 $permStatus = $this->permManager->getPermissionStatus(
840 $redir2 ?
'delete-redirect' :
'delete',
843 if ( !$permStatus->isGood() ) {
845 if ( !$this->permManager->userCan(
'delete', $user, $title ) ) {
859 $page = $this->wikiPageFactory->newFromTitle( $title );
860 $delPage = $this->deletePageFactory->newDeletePage( $page, $user );
863 if ( $delPage->isBatchedDelete( 5 ) ) {
870 if ( $title->getNamespace() ===
NS_FILE ) {
871 $file = $this->repoGroup->getLocalRepo()->newFile( $title );
872 $file->load( IDBAccessObject::READ_LATEST );
873 if ( $file->exists() ) {
874 $file->deleteFile(
$reason, $user,
false );
878 $deletionLog = $redir2 ?
'delete_redir2' :
'delete';
879 $deleteStatus = $delPage
880 ->setLogSubtype( $deletionLog )
882 ->forceImmediate(
true )
885 return $deleteStatus;
888 private function doSubmit() {
891 if ( $user->pingLimiter(
'move' ) ) {
892 throw new ThrottledError;
895 $ot = $this->oldTitle;
896 $nt = $this->newTitle;
898 # don't allow moving to pages with # in
899 if ( !$nt || $nt->hasFragment() ) {
900 $this->showForm( StatusValue::newFatal(
'badtitletext' ) );
905 $oldTalk = $ot->getTalkPageIfDefined();
906 $newTalk = $nt->getTalkPageIfDefined();
908 if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
909 $this->moveTalk =
false;
912 # Show a warning if the target file exists on a shared repo
913 if ( $nt->getNamespace() ===
NS_FILE
914 && !( $this->moveOverShared && $this->permManager->userHasRight( $user,
'reupload-shared' ) )
915 && !$this->repoGroup->getLocalRepo()->findFile( $nt )
916 && $this->repoGroup->findFile( $nt )
918 $this->showForm( StatusValue::newFatal(
'file-exists-sharedrepo' ) );
923 # Show a warning if procted (showForm handles the warning )
924 if ( !$this->moveOverProtection ) {
925 if ( $this->restrictionStore->isProtected( $nt,
'create' ) ) {
929 if ( $this->moveTalk && $newTalk && $this->restrictionStore->isProtected( $newTalk,
'create' ) ) {
935 $handler = $this->contentHandlerFactory->getContentHandler( $ot->getContentModel() );
937 if ( !$handler->supportsRedirects() || (
943 $createRedirect =
false;
944 } elseif ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
945 $createRedirect = $this->leaveRedirect;
947 $createRedirect =
true;
951 $mp = $this->movePageFactory->newMovePage( $ot, $nt );
952 $permStatusMain = $mp->authorizeMove( $this->
getAuthority(), $this->reason );
953 $permStatusMain->merge( $mp->isValidMove() );
955 $onlyMovingTalkSubpages =
false;
956 if ( $this->moveTalk && $oldTalk && $newTalk ) {
957 $mpTalk = $this->movePageFactory->newMovePage( $oldTalk, $newTalk );
958 $permStatusTalk = $mpTalk->authorizeMove( $this->
getAuthority(), $this->reason );
959 $permStatusTalk->merge( $mpTalk->isValidMove() );
962 if ( !$permStatusTalk->isOK() &&
963 !$permStatusTalk->hasMessagesExcept(
'movepage-source-doesnt-exist' )
965 $permStatusTalk->setOK(
true );
966 $onlyMovingTalkSubpages =
true;
973 if ( $this->deleteAndMove ) {
976 if ( $permStatusMain->hasMessagesExcept(
'redirectexists',
'articleexists' ) ||
977 ( !$onlyMovingTalkSubpages &&
978 $permStatusTalk->hasMessagesExcept(
'redirectexists',
'articleexists' ) )
980 $this->showForm( $permStatusMain, $permStatusTalk );
989 $deleteStatus = $this->vacateTitle( $nt, $user, $ot );
990 if ( !$deleteStatus->isGood() ) {
991 $this->showForm( $deleteStatus );
994 if ( $this->moveTalk && $oldTalk && $newTalk ) {
995 $deleteStatus = $this->vacateTitle( $newTalk, $user, $oldTalk );
996 if ( !$deleteStatus->isGood() ) {
999 $this->showForm( StatusValue::newGood(), $deleteStatus );
1003 } elseif ( !$permStatusMain->isOK() || !$permStatusTalk->isOK() ) {
1005 $this->showForm( $permStatusMain, $permStatusTalk );
1011 $mainStatus = $mp->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1012 if ( !$mainStatus->isOK() ) {
1013 $this->showForm( $mainStatus );
1018 if ( $fixRedirects ) {
1027 if ( $mpTalk && !$onlyMovingTalkSubpages ) {
1028 $talkStatus = $mpTalk->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1032 '@phan-var Status<string> $talkStatus';
1033 $talkStatus->value = $newTalk->getPrefixedText();
1034 $moveStatuses[$oldTalk->getPrefixedText()] = $talkStatus;
1038 if ( $this->moveSubpages ) {
1039 if ( $this->permManager->userCan(
'move-subpages', $user, $ot ) ) {
1040 $mp->setMaximumMovedPages( $maximumMovedPages - count( $moveStatuses ) );
1041 $subpageStatus = $mp->moveSubpagesIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
1042 $moveStatuses = array_merge( $moveStatuses, $subpageStatus->value );
1044 if ( $mpTalk && $oldTalk && $maximumMovedPages > count( $moveStatuses ) &&
1045 $this->permManager->userCan(
'move-subpages', $user, $oldTalk ) &&
1046 ( $onlyMovingTalkSubpages || $talkStatus->isOK() )
1048 $mpTalk->setMaximumMovedPages( $maximumMovedPages - count( $moveStatuses ) );
1049 $talkSubStatus = $mpTalk->moveSubpagesIfAllowed(
1052 $moveStatuses = array_merge( $moveStatuses, $talkSubStatus->value );
1059 $out->setPageTitleMsg( $this->
msg(
'pagemovedsub' ) );
1062 $oldLink = $linkRenderer->makeLink(
1065 [
'id' =>
'movepage-oldlink' ],
1066 [
'redirect' =>
'no' ]
1068 $newLink = $linkRenderer->makeKnownLink(
1071 [
'id' =>
'movepage-newlink' ]
1073 $oldText = $ot->getPrefixedText();
1074 $newText = $nt->getPrefixedText();
1076 $out->addHTML( $this->
msg(
'movepage-moved' )->rawParams( $oldLink,
1077 $newLink )->params( $oldText, $newText )->parseAsBlock() );
1078 $out->addWikiMsg( isset( $mainStatus->getValue()[
'redirectRevision'] ) ?
1079 'movepage-moved-redirect' :
1080 'movepage-moved-noredirect' );
1082 $this->getHookRunner()->onSpecialMovepageAfterMove( $this, $ot, $nt );
1085 foreach ( $moveStatuses as $oldSubpage => $subpageStatus ) {
1086 if ( $subpageStatus->hasMessage(
'movepage-max-pages' ) ) {
1087 $extraOutput[] = $this->
msg(
'movepage-max-pages' )
1088 ->numParams( $maximumMovedPages )->escaped();
1091 $oldSubpage = Title::newFromText( $oldSubpage );
1092 $newSubpage = Title::newFromText( $subpageStatus->value );
1093 if ( $subpageStatus->isGood() ) {
1094 if ( $fixRedirects ) {
1097 $oldLink = $linkRenderer->makeLink( $oldSubpage,
null, [], [
'redirect' =>
"no" ] );
1098 $newLink = $linkRenderer->makeKnownLink( $newSubpage );
1100 $extraOutput[] = $this->
msg(
'movepage-page-moved' )->rawParams(
1103 } elseif ( $subpageStatus->hasMessage(
'articleexists' )
1104 || $subpageStatus->hasMessage(
'redirectexists' )
1106 $link = $linkRenderer->makeKnownLink( $newSubpage );
1107 $extraOutput[] = $this->
msg(
'movepage-page-exists' )->rawParams( $link )->escaped();
1109 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
1110 if ( $newSubpage ) {
1111 $newLink = $linkRenderer->makeLink( $newSubpage );
1114 $newLink = htmlspecialchars( $subpageStatus->value );
1116 $extraOutput[] = $this->
msg(
'movepage-page-unmoved' )
1117 ->rawParams( $oldLink, $newLink )->escaped();
1121 if ( $extraOutput !== [] ) {
1122 $out->addHTML(
"<ul>\n<li>" . implode(
"</li>\n<li>", $extraOutput ) .
"</li>\n</ul>" );
1125 # Deal with watches (we don't watch subpages)
1126 # Get text from expiry selection dropdown, T261230
1127 $expiry = $this->
getRequest()->getText(
'wpWatchlistExpiry' );
1129 $expiry = ExpiryDef::normalizeExpiry( $expiry, TS::ISO_8601 );
1134 $this->watchlistManager->setWatch(
1141 $this->watchlistManager->setWatch(
1149 private function showLogFragment( Title $title ) {
1150 $moveLogPage =
new LogPage(
'move' );
1152 $out->addHTML(
Html::element(
'h2', [], $moveLogPage->getName()->text() ) );
1153 LogEventsList::showLogExtract( $out,
'move', $title );
1162 private function showSubpages( $title ) {
1164 $nsHasSubpages = $this->nsInfo->hasSubpages( $title->getNamespace() );
1165 $subpages = $title->getSubpages( $maximumMovedPages + 1 );
1166 $count = $subpages instanceof TitleArrayFromResult ? $subpages->count() : 0;
1168 $titleIsTalk = $title->isTalkPage();
1169 $subpagesTalk = $title->getTalkPage()->getSubpages( $maximumMovedPages + 1 );
1170 $countTalk = $subpagesTalk instanceof TitleArrayFromResult ? $subpagesTalk->count() : 0;
1171 $totalCount = $count + $countTalk;
1173 if ( !$nsHasSubpages && $countTalk == 0 ) {
1179 [
'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ]
1182 if ( $nsHasSubpages ) {
1183 $this->showSubpagesList(
1184 $subpages, $count,
'movesubpagetext',
'movesubpagetext-truncated',
true
1188 if ( !$titleIsTalk && $countTalk > 0 ) {
1189 $this->showSubpagesList(
1190 $subpagesTalk, $countTalk,
'movesubpagetalktext',
'movesubpagetalktext-truncated'
1195 private function showSubpagesList(
1196 TitleArrayFromResult $subpages,
int $pagecount,
string $msg,
string $truncatedMsg,
bool $noSubpageMsg =
false
1201 if ( $pagecount == 0 && $noSubpageMsg ) {
1202 $out->addWikiMsg(
'movenosubpage' );
1208 if ( $pagecount > $maximumMovedPages ) {
1209 $subpages = $this->truncateSubpagesList( $subpages );
1210 $out->addWikiMsg( $truncatedMsg, $this->getLanguage()->formatNum( $maximumMovedPages ) );
1212 $out->addWikiMsg( $msg, $this->getLanguage()->formatNum( $pagecount ) );
1214 $out->addHTML(
"<ul>\n" );
1216 $this->linkBatchFactory->newLinkBatch( $subpages )
1217 ->setCaller( __METHOD__ )
1221 foreach ( $subpages as $subpage ) {
1222 $link = $linkRenderer->makeLink( $subpage );
1223 $out->addHTML(
"<li>$link</li>\n" );
1225 $out->addHTML(
"</ul>\n" );
1228 private function truncateSubpagesList( iterable $subpages ): array {
1230 foreach ( $subpages as $subpage ) {
1231 $returnArray[] = $subpage;
1236 return $returnArray;
1248 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );