101 private $watch =
false;
150 parent::__construct(
'Movepage' );
151 $this->movePageFactory = $movePageFactory;
152 $this->permManager = $permManager;
153 $this->userOptionsLookup = $userOptionsLookup;
154 $this->dbProvider = $dbProvider;
155 $this->contentHandlerFactory = $contentHandlerFactory;
156 $this->nsInfo = $nsInfo;
157 $this->linkBatchFactory = $linkBatchFactory;
158 $this->repoGroup = $repoGroup;
159 $this->wikiPageFactory = $wikiPageFactory;
160 $this->searchEngineFactory = $searchEngineFactory;
161 $this->watchlistManager = $watchlistManager;
162 $this->restrictionStore = $restrictionStore;
163 $this->titleFactory = $titleFactory;
164 $this->deletePageFactory = $deletePageFactory;
180 $target = $par ?? $request->getText(
'target' );
181 $oldTitleText = $request->getText(
'wpOldTitle', $target );
182 $this->oldTitle = Title::newFromText( $oldTitleText );
184 if ( !$this->oldTitle ) {
188 $this->
getOutput()->addBacklinkSubtitle( $this->oldTitle );
190 if ( !$this->oldTitle->exists() ) {
194 $newTitleTextMain = $request->getText(
'wpNewTitleMain' );
195 $newTitleTextNs = $request->getInt(
'wpNewTitleNs', $this->oldTitle->getNamespace() );
198 $newTitleText_bc = $request->getText(
'wpNewTitle' );
199 $this->newTitle = strlen( $newTitleText_bc ) > 0
200 ? Title::newFromText( $newTitleText_bc )
201 : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
204 $isSubmit = $request->getRawVal(
'action' ) ===
'submit' && $request->wasPosted();
206 $reasonList = $request->getText(
'wpReasonList',
'other' );
207 $reason = $request->getText(
'wpReason' );
208 if ( $reasonList ===
'other' ) {
211 $this->reason = $reasonList . $this->
msg(
'colon-separator' )->inContentLanguage()->text() .
$reason;
213 $this->reason = $reasonList;
218 $this->moveTalk = $request->getBool(
'wpMovetalk', $def );
219 $this->fixRedirects = $request->getBool(
'wpFixRedirects', $def );
220 $this->leaveRedirect = $request->getBool(
'wpLeaveRedirect', $def );
222 $this->moveSubpages = $request->getBool(
'wpMovesubpages', $def );
223 $this->deleteAndMove = $request->getBool(
'wpDeleteAndMove' );
224 $this->moveOverShared = $request->getBool(
'wpMoveOverSharedFile' );
225 $this->watch = $request->getCheck(
'wpWatch' ) && $user->isRegistered();
229 if ( $isSubmit && $user->matchEditToken( $request->getVal(
'wpEditToken' ) ) ) {
231 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
232 PermissionManager::RIGOR_SECURE );
234 DeferredUpdates::addCallableUpdate( [ $user,
'spreadAnyEditBlock' ] );
235 if ( !$permStatus->isGood() ) {
241 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
242 PermissionManager::RIGOR_FULL );
243 if ( !$permStatus->isGood() ) {
244 DeferredUpdates::addCallableUpdate( [ $user,
'spreadAnyEditBlock' ] );
257 private function showForm( ?
StatusValue $status =
null ) {
258 $this->
getSkin()->setRelevantTitle( $this->oldTitle );
261 $out->setPageTitleMsg( $this->
msg(
'move-page' )->plaintextParams( $this->oldTitle->getPrefixedText() ) );
262 $out->addModuleStyles( [
264 'mediawiki.interface.helpers.styles'
266 $out->addModules(
'mediawiki.misc-authed-ooui' );
269 $handlerSupportsRedirects = $this->contentHandlerFactory
270 ->getContentHandler( $this->oldTitle->getContentModel() )
271 ->supportsRedirects();
274 $out->addWikiMsg(
'movepagetext' );
276 $out->addWikiMsg( $handlerSupportsRedirects ?
277 'movepagetext-noredirectfixer' :
278 'movepagetext-noredirectsupport' );
281 if ( $this->oldTitle->getNamespace() ===
NS_USER && !$this->oldTitle->isSubpage() ) {
284 $out->msg(
'moveuserpage-warning' )->parse(),
285 'mw-moveuserpage-warning'
288 } elseif ( $this->oldTitle->getNamespace() ===
NS_CATEGORY ) {
291 $out->msg(
'movecategorypage-warning' )->parse(),
292 'mw-movecategorypage-warning'
304 # Show the current title as a default
305 # when the form is first opened.
307 } elseif ( !$status ) {
308 # If a title was supplied, probably from the move log revert
309 # link, check for validity. We can then show some diagnostic
310 # information and save a click.
311 $mp = $this->movePageFactory->newMovePage( $this->oldTitle,
$newTitle );
312 $status = $mp->isValidMove();
313 $status->merge( $mp->probablyCanMove( $this->getAuthority() ) );
319 if ( count( $status->getMessages() ) == 1 ) {
320 if ( $status->hasMessage(
'articleexists' )
321 && $this->permManager->quickUserCan(
'delete', $user,
$newTitle )
330 } elseif ( $status->hasMessage(
'redirectexists' ) && (
332 $this->permManager->quickUserCan(
'delete-redirect', $user,
$newTitle ) ||
333 $this->permManager->quickUserCan(
'delete', $user,
$newTitle ) )
342 } elseif ( $status->hasMessage(
'file-exists-sharedrepo' )
343 && $this->permManager->userHasRight( $user,
'reupload-shared' )
355 $oldTalk = $this->oldTitle->getTalkPageIfDefined();
356 $oldTitleSubpages = $this->oldTitle->hasSubpages();
357 $oldTitleTalkSubpages = $this->oldTitle->getTalkPageIfDefined()->hasSubpages();
359 $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
360 $this->permManager->quickUserCan(
366 # We also want to be able to move assoc. subpage talk-pages even if base page
367 # has no associated talk page, so || with $oldTitleTalkSubpages.
368 $considerTalk = !$this->oldTitle->isTalkPage() &&
370 || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
373 $queryBuilder = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
376 ->where( [
'rd_namespace' => $this->oldTitle->getNamespace() ] )
377 ->andWhere( [
'rd_title' => $this->oldTitle->getDBkey() ] )
378 ->andWhere( [
'rd_interwiki' =>
'' ] );
380 $hasRedirects = (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
382 $hasRedirects =
false;
385 $messages = $status->getMessages();
387 if ( $status instanceof PermissionStatus ) {
388 $action_desc = $this->
msg(
'action-move' )->plain();
389 $errMsgHtml = $this->
msg(
'permissionserrorstext-withaction',
390 count( $messages ), $action_desc )->parseAsBlock();
392 $errMsgHtml = $this->
msg(
'cannotmove', count( $messages ) )->parseAsBlock();
395 if ( count( $messages ) == 1 ) {
396 $errMsgHtml .= $this->
msg( $messages[0] )->parseAsBlock();
400 foreach ( $messages as $msg ) {
401 $errStr[] = $this->
msg( $msg )->parse();
404 $errMsgHtml .=
'<ul><li>' . implode(
"</li>\n<li>", $errStr ) .
"</li></ul>\n";
406 $out->addHTML( Html::errorBox( $errMsgHtml ) );
409 if ( $this->restrictionStore->isProtected( $this->oldTitle,
'move' ) ) {
410 # Is the title semi-protected?
411 if ( $this->restrictionStore->isSemiProtected( $this->oldTitle,
'move' ) ) {
412 $noticeMsg =
'semiprotectedpagemovewarning';
414 # Then it must be protected based on static groups (regular)
415 $noticeMsg =
'protectedpagemovewarning';
422 [
'lim' => 1,
'msgKey' => $noticeMsg ]
429 $immovableNamespaces = [];
430 foreach ( $this->
getLanguage()->getNamespaces() as $nsId => $_ ) {
431 if ( !$this->nsInfo->isMovable( $nsId ) ) {
432 $immovableNamespaces[] = $nsId;
439 $fields[] =
new FieldLayout(
440 new ComplexTitleInputWidget( [
441 'id' =>
'wpNewTitle',
443 'id' =>
'wpNewTitleNs',
444 'name' =>
'wpNewTitleNs',
446 'exclude' => $immovableNamespaces,
449 'id' =>
'wpNewTitleMain',
450 'name' =>
'wpNewTitleMain',
453 'suggestions' =>
false,
458 'label' => $this->msg(
'newtitle' )->text(),
463 $options = Html::listDropdownOptions(
464 $this->
msg(
'movepage-reason-dropdown' )
465 ->page( $this->oldTitle )
466 ->inContentLanguage()
468 [
'other' => $this->
msg(
'movereasonotherlist' )->text() ]
470 $options = Html::listDropdownOptionsOoui( $options );
472 $fields[] =
new FieldLayout(
473 new DropdownInputWidget( [
474 'name' =>
'wpReasonList',
475 'inputId' =>
'wpReasonList',
477 'value' => $this->
getRequest()->getText(
'wpReasonList',
'other' ),
478 'options' => $options,
481 'label' => $this->
msg(
'movereason' )->text(),
489 $fields[] =
new FieldLayout(
490 new TextInputWidget( [
491 'name' =>
'wpReason',
493 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
495 'value' => $this->
getRequest()->getText(
'wpReason' ),
498 'label' => $this->
msg(
'moveotherreason' )->text(),
503 if ( $considerTalk ) {
504 $fields[] =
new FieldLayout(
505 new CheckboxInputWidget( [
506 'name' =>
'wpMovetalk',
507 'id' =>
'wpMovetalk',
509 'selected' => $this->moveTalk,
512 'label' => $this->
msg(
'movetalk' )->text(),
513 'help' =>
new HtmlSnippet( $this->
msg(
'movepagetalktext' )->parseAsBlock() ),
514 'helpInline' =>
true,
516 'id' =>
'wpMovetalk-field',
521 if ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
522 if ( $handlerSupportsRedirects ) {
529 $fields[] =
new FieldLayout(
530 new CheckboxInputWidget( [
531 'name' =>
'wpLeaveRedirect',
532 'id' =>
'wpLeaveRedirect',
534 'selected' => $isChecked,
535 'disabled' => $isDisabled,
538 'label' => $this->
msg(
'move-leave-redirect' )->text(),
544 if ( $hasRedirects ) {
545 $fields[] =
new FieldLayout(
546 new CheckboxInputWidget( [
547 'name' =>
'wpFixRedirects',
548 'id' =>
'wpFixRedirects',
550 'selected' => $this->fixRedirects,
553 'label' => $this->
msg(
'fix-double-redirects' )->text(),
559 if ( $canMoveSubpage ) {
561 $fields[] =
new FieldLayout(
562 new CheckboxInputWidget( [
563 'name' =>
'wpMovesubpages',
564 'id' =>
'wpMovesubpages',
566 'selected' => $this->moveSubpages,
569 'label' =>
new HtmlSnippet(
571 ( $this->oldTitle->hasSubpages()
573 :
'move-talk-subpages' )
574 )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
581 # Don't allow watching if user is not logged in
582 if ( $user->isRegistered() ) {
583 $watchChecked = ( $this->watch || $this->userOptionsLookup->getBoolOption( $user,
'watchmoves' )
584 || $this->watchlistManager->isWatched( $user, $this->oldTitle ) );
585 $fields[] =
new FieldLayout(
586 new CheckboxInputWidget( [
588 'id' =>
'watch', # ew
590 'selected' => $watchChecked,
593 'label' => $this->
msg(
'move-watch' )->text(),
601 $hiddenFields .= Html::hidden(
'wpMoveOverSharedFile',
'1' );
605 $fields[] =
new FieldLayout(
606 new CheckboxInputWidget( [
607 'name' =>
'wpDeleteAndMove',
608 'id' =>
'wpDeleteAndMove',
612 'label' => $this->
msg(
'delete_and_move_confirm', $newTitle->
getPrefixedText() )->text(),
618 $fields[] =
new FieldLayout(
619 new ButtonInputWidget( [
621 'value' => $this->
msg(
'movepagebtn' )->text(),
622 'label' => $this->
msg(
'movepagebtn' )->text(),
623 'flags' => [
'primary',
'progressive' ],
631 $fieldset =
new FieldsetLayout( [
632 'label' => $this->
msg(
'move-page-legend' )->text(),
633 'id' =>
'mw-movepage-table',
637 $form =
new FormLayout( [
639 'action' => $this->
getPageTitle()->getLocalURL(
'action=submit' ),
642 $form->appendContent(
646 Html::hidden(
'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
647 Html::hidden(
'wpEditToken', $user->getEditToken() )
653 'classes' => [
'movepage-wrapper' ],
660 if ( $this->
getAuthority()->isAllowed(
'editinterface' ) ) {
662 $this->
msg(
'movepage-reason-dropdown' )->inContentLanguage()->
getTitle(),
663 $this->
msg(
'movepage-edit-reasonlist' )->text(),
665 [
'action' =>
'edit' ]
667 $out->addHTML( Html::rawElement(
'p', [
'class' =>
'mw-movepage-editreasons' ], $link ) );
670 $this->showLogFragment( $this->oldTitle );
671 $this->showSubpages( $this->oldTitle );
674 private function doSubmit() {
677 if ( $user->pingLimiter(
'move' ) ) {
684 # don't allow moving to pages with # in
685 if ( !$nt || $nt->hasFragment() ) {
686 $this->showForm( StatusValue::newFatal(
'badtitletext' ) );
691 # Show a warning if the target file exists on a shared repo
692 if ( $nt->getNamespace() ===
NS_FILE
693 && !( $this->moveOverShared && $this->permManager->userHasRight( $user,
'reupload-shared' ) )
694 && !$this->repoGroup->getLocalRepo()->findFile( $nt )
695 && $this->repoGroup->findFile( $nt )
697 $this->showForm( StatusValue::newFatal(
'file-exists-sharedrepo' ) );
702 # Delete to make way if requested
703 if ( $this->deleteAndMove ) {
704 $redir2 = $nt->isSingleRevRedirect();
706 $permStatus = $this->permManager->getPermissionStatus(
707 $redir2 ?
'delete-redirect' :
'delete',
710 if ( !$permStatus->isGood() ) {
712 if ( !$this->permManager->userCan(
'delete', $user, $nt ) ) {
714 $this->showForm( $permStatus );
723 $this->showForm( $permStatus );
728 $page = $this->wikiPageFactory->newFromTitle( $nt );
729 $delPage = $this->deletePageFactory->newDeletePage( $page, $user );
732 if ( $delPage->isBatchedDelete( 5 ) ) {
733 $this->showForm( StatusValue::newFatal(
'movepage-delete-first' ) );
738 $reason = $this->
msg(
'delete_and_move_reason', $ot )->inContentLanguage()->text();
741 if ( $nt->getNamespace() ===
NS_FILE ) {
742 $file = $this->repoGroup->getLocalRepo()->newFile( $nt );
743 $file->load( IDBAccessObject::READ_LATEST );
744 if ( $file->exists() ) {
745 $file->deleteFile(
$reason, $user,
false );
749 $deletionLog = $redir2 ?
'delete_redir2' :
'delete';
750 $deleteStatus = $delPage
751 ->setLogSubtype( $deletionLog )
753 ->forceImmediate(
true )
756 if ( !$deleteStatus->isGood() ) {
757 $this->showForm( $deleteStatus );
763 $handler = $this->contentHandlerFactory->getContentHandler( $ot->getContentModel() );
765 if ( !$handler->supportsRedirects() ) {
766 $createRedirect =
false;
767 } elseif ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
770 $createRedirect =
true;
773 # Do the actual move.
774 $mp = $this->movePageFactory->newMovePage( $ot, $nt );
776 if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
777 $this->moveTalk =
false;
779 if ( $this->moveSubpages ) {
780 $this->moveSubpages = $this->permManager->userCan(
'move-subpages', $user, $ot );
783 # check whether the requested actions are permitted / possible
784 $permStatus = $mp->authorizeMove( $this->
getAuthority(), $this->reason );
785 if ( !$permStatus->isOK() ) {
786 $this->showForm( $permStatus );
789 $status = $mp->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
790 if ( !$status->isOK() ) {
791 $this->showForm( $status );
796 $this->fixRedirects ) {
801 $out->setPageTitleMsg( $this->
msg(
'pagemovedsub' ) );
804 $oldLink = $linkRenderer->makeLink(
807 [
'id' =>
'movepage-oldlink' ],
808 [
'redirect' =>
'no' ]
810 $newLink = $linkRenderer->makeKnownLink(
813 [
'id' =>
'movepage-newlink' ]
815 $oldText = $ot->getPrefixedText();
816 $newText = $nt->getPrefixedText();
818 if ( $status->getValue()[
'redirectRevision'] !==
null ) {
819 $msgName =
'movepage-moved-redirect';
821 $msgName =
'movepage-moved-noredirect';
824 $out->addHTML( $this->
msg(
'movepage-moved' )->rawParams( $oldLink,
825 $newLink )->params( $oldText, $newText )->parseAsBlock() );
826 $out->addWikiMsg( $msgName );
828 $this->
getHookRunner()->onSpecialMovepageAfterMove( $this, $ot, $nt );
845 $dbr = $this->dbProvider->getReplicaDatabase();
846 if ( $this->moveSubpages && (
847 $this->nsInfo->hasSubpages( $nt->getNamespace() ) || (
849 && $this->nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
856 new LikeValue( $ot->getDBkey() .
'/', $dbr->anyString() )
857 )->or(
'page_title',
'=', $ot->getDBkey() )
859 $conds[
'page_namespace'] = [];
860 if ( $this->nsInfo->hasSubpages( $nt->getNamespace() ) ) {
861 $conds[
'page_namespace'][] = $ot->getNamespace();
863 if ( $this->moveTalk &&
864 $this->nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
866 $conds[
'page_namespace'][] = $ot->getTalkPage()->getNamespace();
868 } elseif ( $this->moveTalk ) {
870 'page_namespace' => $ot->getTalkPage()->getNamespace(),
871 'page_title' => $ot->getDBkey()
879 if ( $conds !==
null ) {
880 $extraPages = $this->titleFactory->newTitleArrayFromResult(
881 $dbr->newSelectQueryBuilder()
882 ->select( [
'page_id',
'page_namespace',
'page_title' ] )
885 ->caller( __METHOD__ )->fetchResultSet()
891 foreach ( $extraPages as $oldSubpage ) {
892 if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) {
893 # Already did this one.
897 $newPageName = preg_replace(
898 '#^' . preg_quote( $ot->getDBkey(),
'#' ) .
'#',
899 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
900 $oldSubpage->getDBkey()
903 if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) {
905 $newNs = $nt->getNamespace();
906 } elseif ( $oldSubpage->isTalkPage() ) {
907 $newNs = $nt->getTalkPage()->getNamespace();
909 $newNs = $nt->getSubjectPage()->getNamespace();
912 # T16385: we need makeTitleSafe because the new page names may
913 # be longer than 255 characters.
914 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
915 if ( !$newSubpage ) {
916 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
917 $extraOutput[] = $this->
msg(
'movepage-page-unmoved' )->rawParams( $oldLink )
918 ->params( Title::makeName( $newNs, $newPageName ) )->escaped();
922 $mp = $this->movePageFactory->newMovePage( $oldSubpage, $newSubpage );
923 # This was copy-pasted from Renameuser, bleh.
924 if ( $newSubpage->exists() && !$mp->isValidMove()->isOK() ) {
925 $link = $linkRenderer->makeKnownLink( $newSubpage );
926 $extraOutput[] = $this->
msg(
'movepage-page-exists' )->rawParams( $link )->escaped();
928 $status = $mp->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
930 if ( $status->isOK() ) {
931 if ( $this->fixRedirects ) {
934 $oldLink = $linkRenderer->makeLink(
938 [
'redirect' =>
'no' ]
941 $newLink = $linkRenderer->makeKnownLink( $newSubpage );
942 $extraOutput[] = $this->
msg(
'movepage-page-moved' )
943 ->rawParams( $oldLink, $newLink )->escaped();
948 if ( $count >= $maximumMovedPages ) {
949 $extraOutput[] = $this->
msg(
'movepage-max-pages' )
950 ->numParams( $maximumMovedPages )->escaped();
954 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
955 $newLink = $linkRenderer->makeLink( $newSubpage );
956 $extraOutput[] = $this->
msg(
'movepage-page-unmoved' )
957 ->rawParams( $oldLink, $newLink )->escaped();
962 if ( $extraOutput !== [] ) {
963 $out->addHTML(
"<ul>\n<li>" . implode(
"</li>\n<li>", $extraOutput ) .
"</li>\n</ul>" );
966 # Deal with watches (we don't watch subpages)
967 $this->watchlistManager->setWatch( $this->watch, $this->
getAuthority(), $ot );
968 $this->watchlistManager->setWatch( $this->watch, $this->
getAuthority(), $nt );
971 private function showLogFragment( $title ) {
972 $moveLogPage =
new LogPage(
'move' );
974 $out->addHTML(
Xml::element(
'h2',
null, $moveLogPage->getName()->text() ) );
984 private function showSubpages( $title ) {
986 $nsHasSubpages = $this->nsInfo->hasSubpages( $title->getNamespace() );
987 $subpages = $title->getSubpages( $maximumMovedPages + 1 );
988 $count = $subpages instanceof TitleArrayFromResult ? $subpages->count() : 0;
990 $titleIsTalk = $title->isTalkPage();
991 $subpagesTalk = $title->getTalkPage()->getSubpages( $maximumMovedPages + 1 );
992 $countTalk = $subpagesTalk instanceof TitleArrayFromResult ? $subpagesTalk->count() : 0;
993 $totalCount = $count + $countTalk;
995 if ( !$nsHasSubpages && $countTalk == 0 ) {
1001 [
'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ]
1004 if ( $nsHasSubpages ) {
1005 $this->showSubpagesList(
1006 $subpages, $count,
'movesubpagetext',
'movesubpagetext-truncated',
true
1010 if ( !$titleIsTalk && $countTalk > 0 ) {
1011 $this->showSubpagesList(
1012 $subpagesTalk, $countTalk,
'movesubpagetalktext',
'movesubpagetalktext-truncated'
1017 private function showSubpagesList( $subpages, $pagecount, $msg, $truncatedMsg, $noSubpageMsg =
false ) {
1021 if ( $pagecount == 0 && $noSubpageMsg ) {
1022 $out->addWikiMsg(
'movenosubpage' );
1028 if ( $pagecount > $maximumMovedPages ) {
1029 $subpages = $this->truncateSubpagesList( $subpages );
1030 $out->addWikiMsg( $truncatedMsg, $this->
getLanguage()->formatNum( $maximumMovedPages ) );
1032 $out->addWikiMsg( $msg, $this->
getLanguage()->formatNum( $pagecount ) );
1034 $out->addHTML(
"<ul>\n" );
1036 $linkBatch = $this->linkBatchFactory->newLinkBatch( $subpages );
1037 $linkBatch->setCaller( __METHOD__ );
1038 $linkBatch->execute();
1041 foreach ( $subpages as $subpage ) {
1042 $link = $linkRenderer->makeLink( $subpage );
1043 $out->addHTML(
"<li>$link</li>\n" );
1045 $out->addHTML(
"</ul>\n" );
1048 private function truncateSubpagesList( iterable $subpages ): array {
1050 foreach ( $subpages as $subpage ) {
1051 $returnArray[] = $subpage;
1056 return $returnArray;
1068 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );