102 private $watch =
false;
151 parent::__construct(
'Movepage' );
152 $this->movePageFactory = $movePageFactory;
153 $this->permManager = $permManager;
154 $this->userOptionsLookup = $userOptionsLookup;
155 $this->dbProvider = $dbProvider;
156 $this->contentHandlerFactory = $contentHandlerFactory;
157 $this->nsInfo = $nsInfo;
158 $this->linkBatchFactory = $linkBatchFactory;
159 $this->repoGroup = $repoGroup;
160 $this->wikiPageFactory = $wikiPageFactory;
161 $this->searchEngineFactory = $searchEngineFactory;
162 $this->watchlistManager = $watchlistManager;
163 $this->restrictionStore = $restrictionStore;
164 $this->titleFactory = $titleFactory;
165 $this->deletePageFactory = $deletePageFactory;
181 $target = $par ?? $request->getText(
'target' );
182 $oldTitleText = $request->getText(
'wpOldTitle', $target );
183 $this->oldTitle = Title::newFromText( $oldTitleText );
185 if ( !$this->oldTitle ) {
189 $this->
getOutput()->addBacklinkSubtitle( $this->oldTitle );
191 if ( !$this->oldTitle->exists() ) {
195 $newTitleTextMain = $request->getText(
'wpNewTitleMain' );
196 $newTitleTextNs = $request->getInt(
'wpNewTitleNs', $this->oldTitle->getNamespace() );
199 $newTitleText_bc = $request->getText(
'wpNewTitle' );
200 $this->newTitle = strlen( $newTitleText_bc ) > 0
201 ? Title::newFromText( $newTitleText_bc )
202 : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
205 $isSubmit = $request->getRawVal(
'action' ) ===
'submit' && $request->wasPosted();
207 $reasonList = $request->getText(
'wpReasonList',
'other' );
208 $reason = $request->getText(
'wpReason' );
209 if ( $reasonList ===
'other' ) {
212 $this->reason = $reasonList . $this->
msg(
'colon-separator' )->inContentLanguage()->text() .
$reason;
214 $this->reason = $reasonList;
219 $this->moveTalk = $request->getBool(
'wpMovetalk', $def );
220 $this->fixRedirects = $request->getBool(
'wpFixRedirects', $def );
221 $this->leaveRedirect = $request->getBool(
'wpLeaveRedirect', $def );
223 $this->moveSubpages = $request->getBool(
'wpMovesubpages', $def );
224 $this->deleteAndMove = $request->getBool(
'wpDeleteAndMove' );
225 $this->moveOverShared = $request->getBool(
'wpMoveOverSharedFile' );
226 $this->watch = $request->getCheck(
'wpWatch' ) && $user->isRegistered();
230 if ( $isSubmit && $user->matchEditToken( $request->getVal(
'wpEditToken' ) ) ) {
232 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
233 PermissionManager::RIGOR_SECURE );
235 DeferredUpdates::addCallableUpdate( [ $user,
'spreadAnyEditBlock' ] );
236 if ( !$permStatus->isGood() ) {
242 $permStatus = $this->permManager->getPermissionStatus(
'move', $user, $this->oldTitle,
243 PermissionManager::RIGOR_FULL );
244 if ( !$permStatus->isGood() ) {
245 DeferredUpdates::addCallableUpdate( [ $user,
'spreadAnyEditBlock' ] );
258 private function showForm( ?
StatusValue $status =
null ) {
259 $this->
getSkin()->setRelevantTitle( $this->oldTitle );
262 $out->setPageTitleMsg( $this->
msg(
'move-page' )->plaintextParams( $this->oldTitle->getPrefixedText() ) );
263 $out->addModuleStyles( [
265 'mediawiki.interface.helpers.styles'
267 $out->addModules(
'mediawiki.misc-authed-ooui' );
270 $handler = $this->contentHandlerFactory
271 ->getContentHandler( $this->oldTitle->getContentModel() );
272 $createRedirect = $handler->supportsRedirects() && !(
280 $out->addWikiMsg(
'movepagetext' );
282 $out->addWikiMsg( $createRedirect ?
283 'movepagetext-noredirectfixer' :
284 'movepagetext-noredirectsupport' );
287 if ( $this->oldTitle->getNamespace() ===
NS_USER && !$this->oldTitle->isSubpage() ) {
290 $out->msg(
'moveuserpage-warning' )->parse(),
291 'mw-moveuserpage-warning'
294 } elseif ( $this->oldTitle->getNamespace() ===
NS_CATEGORY ) {
297 $out->msg(
'movecategorypage-warning' )->parse(),
298 'mw-movecategorypage-warning'
310 # Show the current title as a default
311 # when the form is first opened.
313 } elseif ( !$status ) {
314 # If a title was supplied, probably from the move log revert
315 # link, check for validity. We can then show some diagnostic
316 # information and save a click.
317 $mp = $this->movePageFactory->newMovePage( $this->oldTitle,
$newTitle );
318 $status = $mp->isValidMove();
319 $status->merge( $mp->probablyCanMove( $this->getAuthority() ) );
325 if ( count( $status->getMessages() ) == 1 ) {
326 if ( $status->hasMessage(
'articleexists' )
327 && $this->permManager->quickUserCan(
'delete', $user,
$newTitle )
336 } elseif ( $status->hasMessage(
'redirectexists' ) && (
338 $this->permManager->quickUserCan(
'delete-redirect', $user,
$newTitle ) ||
339 $this->permManager->quickUserCan(
'delete', $user,
$newTitle ) )
348 } elseif ( $status->hasMessage(
'file-exists-sharedrepo' )
349 && $this->permManager->userHasRight( $user,
'reupload-shared' )
361 $oldTalk = $this->oldTitle->getTalkPageIfDefined();
362 $oldTitleSubpages = $this->oldTitle->hasSubpages();
363 $oldTitleTalkSubpages = $this->oldTitle->getTalkPageIfDefined()->hasSubpages();
365 $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
366 $this->permManager->quickUserCan(
372 # We also want to be able to move assoc. subpage talk-pages even if base page
373 # has no associated talk page, so || with $oldTitleTalkSubpages.
374 $considerTalk = !$this->oldTitle->isTalkPage() &&
376 || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
379 $queryBuilder = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
382 ->where( [
'rd_namespace' => $this->oldTitle->getNamespace() ] )
383 ->andWhere( [
'rd_title' => $this->oldTitle->getDBkey() ] )
384 ->andWhere( [
'rd_interwiki' =>
'' ] );
386 $hasRedirects = (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
388 $hasRedirects =
false;
391 $messages = $status->getMessages();
393 if ( $status instanceof PermissionStatus ) {
394 $action_desc = $this->
msg(
'action-move' )->plain();
395 $errMsgHtml = $this->
msg(
'permissionserrorstext-withaction',
396 count( $messages ), $action_desc )->parseAsBlock();
398 $errMsgHtml = $this->
msg(
'cannotmove', count( $messages ) )->parseAsBlock();
401 if ( count( $messages ) == 1 ) {
402 $errMsgHtml .= $this->
msg( $messages[0] )->parseAsBlock();
406 foreach ( $messages as $msg ) {
407 $errStr[] = $this->
msg( $msg )->parse();
410 $errMsgHtml .=
'<ul><li>' . implode(
"</li>\n<li>", $errStr ) .
"</li></ul>\n";
412 $out->addHTML( Html::errorBox( $errMsgHtml ) );
415 if ( $this->restrictionStore->isProtected( $this->oldTitle,
'move' ) ) {
416 # Is the title semi-protected?
417 if ( $this->restrictionStore->isSemiProtected( $this->oldTitle,
'move' ) ) {
418 $noticeMsg =
'semiprotectedpagemovewarning';
420 # Then it must be protected based on static groups (regular)
421 $noticeMsg =
'protectedpagemovewarning';
428 [
'lim' => 1,
'msgKey' => $noticeMsg ]
435 $immovableNamespaces = [];
436 foreach ( $this->
getLanguage()->getNamespaces() as $nsId => $_ ) {
437 if ( !$this->nsInfo->isMovable( $nsId ) ) {
438 $immovableNamespaces[] = $nsId;
445 $fields[] =
new FieldLayout(
446 new ComplexTitleInputWidget( [
447 'id' =>
'wpNewTitle',
449 'id' =>
'wpNewTitleNs',
450 'name' =>
'wpNewTitleNs',
452 'exclude' => $immovableNamespaces,
455 'id' =>
'wpNewTitleMain',
456 'name' =>
'wpNewTitleMain',
459 'suggestions' =>
false,
464 'label' => $this->msg(
'newtitle' )->text(),
469 $options = Html::listDropdownOptions(
470 $this->
msg(
'movepage-reason-dropdown' )
471 ->page( $this->oldTitle )
472 ->inContentLanguage()
474 [
'other' => $this->
msg(
'movereasonotherlist' )->text() ]
476 $options = Html::listDropdownOptionsOoui( $options );
478 $fields[] =
new FieldLayout(
479 new DropdownInputWidget( [
480 'name' =>
'wpReasonList',
481 'inputId' =>
'wpReasonList',
483 'value' => $this->
getRequest()->getText(
'wpReasonList',
'other' ),
484 'options' => $options,
487 'label' => $this->
msg(
'movereason' )->text(),
495 $fields[] =
new FieldLayout(
496 new TextInputWidget( [
497 'name' =>
'wpReason',
499 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
501 'value' => $this->
getRequest()->getText(
'wpReason' ),
504 'label' => $this->
msg(
'moveotherreason' )->text(),
509 if ( $considerTalk ) {
510 $fields[] =
new FieldLayout(
511 new CheckboxInputWidget( [
512 'name' =>
'wpMovetalk',
513 'id' =>
'wpMovetalk',
515 'selected' => $this->moveTalk,
518 'label' => $this->
msg(
'movetalk' )->text(),
519 'help' =>
new HtmlSnippet( $this->
msg(
'movepagetalktext' )->parseAsBlock() ),
520 'helpInline' =>
true,
522 'id' =>
'wpMovetalk-field',
527 if ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
528 if ( $createRedirect ) {
535 $fields[] =
new FieldLayout(
536 new CheckboxInputWidget( [
537 'name' =>
'wpLeaveRedirect',
538 'id' =>
'wpLeaveRedirect',
540 'selected' => $isChecked,
541 'disabled' => $isDisabled,
544 'label' => $this->
msg(
'move-leave-redirect' )->text(),
550 if ( $hasRedirects ) {
551 $fields[] =
new FieldLayout(
552 new CheckboxInputWidget( [
553 'name' =>
'wpFixRedirects',
554 'id' =>
'wpFixRedirects',
556 'selected' => $this->fixRedirects,
559 'label' => $this->
msg(
'fix-double-redirects' )->text(),
565 if ( $canMoveSubpage ) {
567 $fields[] =
new FieldLayout(
568 new CheckboxInputWidget( [
569 'name' =>
'wpMovesubpages',
570 'id' =>
'wpMovesubpages',
572 'selected' => $this->moveSubpages,
575 'label' =>
new HtmlSnippet(
577 ( $this->oldTitle->hasSubpages()
579 :
'move-talk-subpages' )
580 )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
587 # Don't allow watching if user is not logged in
588 if ( $user->isRegistered() ) {
589 $watchChecked = ( $this->watch || $this->userOptionsLookup->getBoolOption( $user,
'watchmoves' )
590 || $this->watchlistManager->isWatched( $user, $this->oldTitle ) );
591 $fields[] =
new FieldLayout(
592 new CheckboxInputWidget( [
594 'id' =>
'watch', # ew
596 'selected' => $watchChecked,
599 'label' => $this->
msg(
'move-watch' )->text(),
607 $hiddenFields .= Html::hidden(
'wpMoveOverSharedFile',
'1' );
611 $fields[] =
new FieldLayout(
612 new CheckboxInputWidget( [
613 'name' =>
'wpDeleteAndMove',
614 'id' =>
'wpDeleteAndMove',
618 'label' => $this->
msg(
'delete_and_move_confirm', $newTitle->
getPrefixedText() )->text(),
624 $fields[] =
new FieldLayout(
625 new ButtonInputWidget( [
627 'value' => $this->
msg(
'movepagebtn' )->text(),
628 'label' => $this->
msg(
'movepagebtn' )->text(),
629 'flags' => [
'primary',
'progressive' ],
637 $fieldset =
new FieldsetLayout( [
638 'label' => $this->
msg(
'move-page-legend' )->text(),
639 'id' =>
'mw-movepage-table',
643 $form =
new FormLayout( [
645 'action' => $this->
getPageTitle()->getLocalURL(
'action=submit' ),
648 $form->appendContent(
652 Html::hidden(
'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
653 Html::hidden(
'wpEditToken', $user->getEditToken() )
659 'classes' => [
'movepage-wrapper' ],
666 if ( $this->
getAuthority()->isAllowed(
'editinterface' ) ) {
668 $this->
msg(
'movepage-reason-dropdown' )->inContentLanguage()->
getTitle(),
669 $this->
msg(
'movepage-edit-reasonlist' )->text(),
671 [
'action' =>
'edit' ]
673 $out->addHTML( Html::rawElement(
'p', [
'class' =>
'mw-movepage-editreasons' ], $link ) );
676 $this->showLogFragment( $this->oldTitle );
677 $this->showSubpages( $this->oldTitle );
680 private function doSubmit() {
683 if ( $user->pingLimiter(
'move' ) ) {
690 # don't allow moving to pages with # in
691 if ( !$nt || $nt->hasFragment() ) {
692 $this->showForm( StatusValue::newFatal(
'badtitletext' ) );
697 # Show a warning if the target file exists on a shared repo
698 if ( $nt->getNamespace() ===
NS_FILE
699 && !( $this->moveOverShared && $this->permManager->userHasRight( $user,
'reupload-shared' ) )
700 && !$this->repoGroup->getLocalRepo()->findFile( $nt )
701 && $this->repoGroup->findFile( $nt )
703 $this->showForm( StatusValue::newFatal(
'file-exists-sharedrepo' ) );
708 # Delete to make way if requested
709 if ( $this->deleteAndMove ) {
710 $redir2 = $nt->isSingleRevRedirect();
712 $permStatus = $this->permManager->getPermissionStatus(
713 $redir2 ?
'delete-redirect' :
'delete',
716 if ( !$permStatus->isGood() ) {
718 if ( !$this->permManager->userCan(
'delete', $user, $nt ) ) {
720 $this->showForm( $permStatus );
729 $this->showForm( $permStatus );
734 $page = $this->wikiPageFactory->newFromTitle( $nt );
735 $delPage = $this->deletePageFactory->newDeletePage( $page, $user );
738 if ( $delPage->isBatchedDelete( 5 ) ) {
739 $this->showForm( StatusValue::newFatal(
'movepage-delete-first' ) );
744 $reason = $this->
msg(
'delete_and_move_reason', $ot )->inContentLanguage()->text();
747 if ( $nt->getNamespace() ===
NS_FILE ) {
748 $file = $this->repoGroup->getLocalRepo()->newFile( $nt );
749 $file->load( IDBAccessObject::READ_LATEST );
750 if ( $file->exists() ) {
751 $file->deleteFile(
$reason, $user,
false );
755 $deletionLog = $redir2 ?
'delete_redir2' :
'delete';
756 $deleteStatus = $delPage
757 ->setLogSubtype( $deletionLog )
759 ->forceImmediate(
true )
762 if ( !$deleteStatus->isGood() ) {
763 $this->showForm( $deleteStatus );
769 $handler = $this->contentHandlerFactory->getContentHandler( $ot->getContentModel() );
771 if ( !$handler->supportsRedirects() || (
777 $createRedirect =
false;
778 } elseif ( $this->permManager->userHasRight( $user,
'suppressredirect' ) ) {
781 $createRedirect =
true;
784 # Do the actual move.
785 $mp = $this->movePageFactory->newMovePage( $ot, $nt );
787 if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
788 $this->moveTalk =
false;
790 if ( $this->moveSubpages ) {
791 $this->moveSubpages = $this->permManager->userCan(
'move-subpages', $user, $ot );
794 # check whether the requested actions are permitted / possible
795 $permStatus = $mp->authorizeMove( $this->
getAuthority(), $this->reason );
796 if ( !$permStatus->isOK() ) {
797 $this->showForm( $permStatus );
800 $status = $mp->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
801 if ( !$status->isOK() ) {
802 $this->showForm( $status );
807 $this->fixRedirects ) {
812 $out->setPageTitleMsg( $this->
msg(
'pagemovedsub' ) );
815 $oldLink = $linkRenderer->makeLink(
818 [
'id' =>
'movepage-oldlink' ],
819 [
'redirect' =>
'no' ]
821 $newLink = $linkRenderer->makeKnownLink(
824 [
'id' =>
'movepage-newlink' ]
826 $oldText = $ot->getPrefixedText();
827 $newText = $nt->getPrefixedText();
829 if ( $status->getValue()[
'redirectRevision'] !==
null ) {
830 $msgName =
'movepage-moved-redirect';
832 $msgName =
'movepage-moved-noredirect';
835 $out->addHTML( $this->
msg(
'movepage-moved' )->rawParams( $oldLink,
836 $newLink )->params( $oldText, $newText )->parseAsBlock() );
837 $out->addWikiMsg( $msgName );
839 $this->
getHookRunner()->onSpecialMovepageAfterMove( $this, $ot, $nt );
856 $dbr = $this->dbProvider->getReplicaDatabase();
857 if ( $this->moveSubpages && (
858 $this->nsInfo->hasSubpages( $nt->getNamespace() ) || (
860 && $this->nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
867 new LikeValue( $ot->getDBkey() .
'/', $dbr->anyString() )
868 )->or(
'page_title',
'=', $ot->getDBkey() )
870 $conds[
'page_namespace'] = [];
871 if ( $this->nsInfo->hasSubpages( $nt->getNamespace() ) ) {
872 $conds[
'page_namespace'][] = $ot->getNamespace();
874 if ( $this->moveTalk &&
875 $this->nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
877 $conds[
'page_namespace'][] = $ot->getTalkPage()->getNamespace();
879 } elseif ( $this->moveTalk ) {
881 'page_namespace' => $ot->getTalkPage()->getNamespace(),
882 'page_title' => $ot->getDBkey()
890 if ( $conds !==
null ) {
891 $extraPages = $this->titleFactory->newTitleArrayFromResult(
892 $dbr->newSelectQueryBuilder()
893 ->select( [
'page_id',
'page_namespace',
'page_title' ] )
896 ->caller( __METHOD__ )->fetchResultSet()
902 foreach ( $extraPages as $oldSubpage ) {
903 if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) {
904 # Already did this one.
908 $newPageName = preg_replace(
909 '#^' . preg_quote( $ot->getDBkey(),
'#' ) .
'#',
910 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
911 $oldSubpage->getDBkey()
914 if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) {
916 $newNs = $nt->getNamespace();
917 } elseif ( $oldSubpage->isTalkPage() ) {
918 $newNs = $nt->getTalkPage()->getNamespace();
920 $newNs = $nt->getSubjectPage()->getNamespace();
923 # T16385: we need makeTitleSafe because the new page names may
924 # be longer than 255 characters.
925 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
926 if ( !$newSubpage ) {
927 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
928 $extraOutput[] = $this->
msg(
'movepage-page-unmoved' )->rawParams( $oldLink )
929 ->params( Title::makeName( $newNs, $newPageName ) )->escaped();
933 $mp = $this->movePageFactory->newMovePage( $oldSubpage, $newSubpage );
934 # This was copy-pasted from Renameuser, bleh.
935 if ( $newSubpage->exists() && !$mp->isValidMove()->isOK() ) {
936 $link = $linkRenderer->makeKnownLink( $newSubpage );
937 $extraOutput[] = $this->
msg(
'movepage-page-exists' )->rawParams( $link )->escaped();
939 $status = $mp->moveIfAllowed( $this->
getAuthority(), $this->reason, $createRedirect );
941 if ( $status->isOK() ) {
942 if ( $this->fixRedirects ) {
945 $oldLink = $linkRenderer->makeLink(
949 [
'redirect' =>
'no' ]
952 $newLink = $linkRenderer->makeKnownLink( $newSubpage );
953 $extraOutput[] = $this->
msg(
'movepage-page-moved' )
954 ->rawParams( $oldLink, $newLink )->escaped();
959 if ( $count >= $maximumMovedPages ) {
960 $extraOutput[] = $this->
msg(
'movepage-max-pages' )
961 ->numParams( $maximumMovedPages )->escaped();
965 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
966 $newLink = $linkRenderer->makeLink( $newSubpage );
967 $extraOutput[] = $this->
msg(
'movepage-page-unmoved' )
968 ->rawParams( $oldLink, $newLink )->escaped();
973 if ( $extraOutput !== [] ) {
974 $out->addHTML(
"<ul>\n<li>" . implode(
"</li>\n<li>", $extraOutput ) .
"</li>\n</ul>" );
977 # Deal with watches (we don't watch subpages)
978 $this->watchlistManager->setWatch( $this->watch, $this->
getAuthority(), $ot );
979 $this->watchlistManager->setWatch( $this->watch, $this->
getAuthority(), $nt );
982 private function showLogFragment( $title ) {
983 $moveLogPage =
new LogPage(
'move' );
985 $out->addHTML(
Xml::element(
'h2',
null, $moveLogPage->getName()->text() ) );
995 private function showSubpages( $title ) {
997 $nsHasSubpages = $this->nsInfo->hasSubpages( $title->getNamespace() );
998 $subpages = $title->getSubpages( $maximumMovedPages + 1 );
999 $count = $subpages instanceof TitleArrayFromResult ? $subpages->count() : 0;
1001 $titleIsTalk = $title->isTalkPage();
1002 $subpagesTalk = $title->getTalkPage()->getSubpages( $maximumMovedPages + 1 );
1003 $countTalk = $subpagesTalk instanceof TitleArrayFromResult ? $subpagesTalk->count() : 0;
1004 $totalCount = $count + $countTalk;
1006 if ( !$nsHasSubpages && $countTalk == 0 ) {
1012 [
'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ]
1015 if ( $nsHasSubpages ) {
1016 $this->showSubpagesList(
1017 $subpages, $count,
'movesubpagetext',
'movesubpagetext-truncated',
true
1021 if ( !$titleIsTalk && $countTalk > 0 ) {
1022 $this->showSubpagesList(
1023 $subpagesTalk, $countTalk,
'movesubpagetalktext',
'movesubpagetalktext-truncated'
1028 private function showSubpagesList( $subpages, $pagecount, $msg, $truncatedMsg, $noSubpageMsg =
false ) {
1032 if ( $pagecount == 0 && $noSubpageMsg ) {
1033 $out->addWikiMsg(
'movenosubpage' );
1039 if ( $pagecount > $maximumMovedPages ) {
1040 $subpages = $this->truncateSubpagesList( $subpages );
1041 $out->addWikiMsg( $truncatedMsg, $this->
getLanguage()->formatNum( $maximumMovedPages ) );
1043 $out->addWikiMsg( $msg, $this->
getLanguage()->formatNum( $pagecount ) );
1045 $out->addHTML(
"<ul>\n" );
1047 $linkBatch = $this->linkBatchFactory->newLinkBatch( $subpages );
1048 $linkBatch->setCaller( __METHOD__ );
1049 $linkBatch->execute();
1052 foreach ( $subpages as $subpage ) {
1053 $link = $linkRenderer->makeLink( $subpage );
1054 $out->addHTML(
"<li>$link</li>\n" );
1056 $out->addHTML(
"</ul>\n" );
1059 private function truncateSubpagesList( iterable $subpages ): array {
1061 foreach ( $subpages as $subpage ) {
1062 $returnArray[] = $subpage;
1067 return $returnArray;
1079 return $this->prefixSearchString( $search, $limit, $offset, $this->searchEngineFactory );