MediaWiki REL1_39
SpecialSearch.php
Go to the documentation of this file.
1<?php
37
51 protected $profile;
52
54 protected $searchEngine;
55
57 protected $searchEngineType = null;
58
60 protected $extraParams = [];
61
66 protected $mPrefix;
67
71 protected $limit, $offset;
72
76 protected $namespaces;
77
81 protected $fulltext;
82
86 protected $sort = SearchEngine::DEFAULT_SORT;
87
91 protected $runSuggestion = true;
92
97 protected $searchConfig;
98
100 private $searchEngineFactory;
101
103 private $nsInfo;
104
106 private $contentHandlerFactory;
107
109 private $interwikiLookup;
110
112 private $readOnlyMode;
113
115 private $userOptionsManager;
116
118 private $languageConverterFactory;
119
124 private $loadStatus;
125
126 private const NAMESPACES_CURRENT = 'sense';
127
138 public function __construct(
139 SearchEngineConfig $searchConfig,
140 SearchEngineFactory $searchEngineFactory,
141 NamespaceInfo $nsInfo,
142 IContentHandlerFactory $contentHandlerFactory,
143 InterwikiLookup $interwikiLookup,
144 ReadOnlyMode $readOnlyMode,
145 UserOptionsManager $userOptionsManager,
146 LanguageConverterFactory $languageConverterFactory
147 ) {
148 parent::__construct( 'Search' );
149 $this->searchConfig = $searchConfig;
150 $this->searchEngineFactory = $searchEngineFactory;
151 $this->nsInfo = $nsInfo;
152 $this->contentHandlerFactory = $contentHandlerFactory;
153 $this->interwikiLookup = $interwikiLookup;
154 $this->readOnlyMode = $readOnlyMode;
155 $this->userOptionsManager = $userOptionsManager;
156 $this->languageConverterFactory = $languageConverterFactory;
157 }
158
164 public function execute( $par ) {
165 $request = $this->getRequest();
166 $out = $this->getOutput();
167
168 // Fetch the search term
169 $term = str_replace( "\n", " ", $request->getText( 'search' ) );
170
171 // Historically search terms have been accepted not only in the search query
172 // parameter, but also as part of the primary url. This can have PII implications
173 // in releasing page view data. As such issue a 301 redirect to the correct
174 // URL.
175 if ( $par !== null && $par !== '' && $term === '' ) {
176 $query = $request->getValues();
177 unset( $query['title'] );
178 // Strip underscores from title parameter; most of the time we'll want
179 // text form here. But don't strip underscores from actual text params!
180 $query['search'] = str_replace( '_', ' ', $par );
181 $out->redirect( $this->getPageTitle()->getFullURL( $query ), 301 );
182 return;
183 }
184
185 // Need to load selected namespaces before handling nsRemember
186 $this->load();
187 // TODO: This performs database actions on GET request, which is going to
188 // be a problem for our multi-datacenter work.
189 if ( $request->getCheck( 'nsRemember' ) ) {
190 $this->saveNamespaces();
191 // Remove the token from the URL to prevent the user from inadvertently
192 // exposing it (e.g. by pasting it into a public wiki page) or undoing
193 // later settings changes (e.g. by reloading the page).
194 $query = $request->getValues();
195 unset( $query['title'], $query['nsRemember'] );
196 $out->redirect( $this->getPageTitle()->getFullURL( $query ) );
197 return;
198 }
199
200 if ( !$request->getVal( 'fulltext' ) && !$request->getCheck( 'offset' ) ) {
201 $url = $this->goResult( $term );
202 if ( $url !== null ) {
203 // successful 'go'
204 $out->redirect( $url );
205 return;
206 }
207 // No match. If it could plausibly be a title
208 // run the No go match hook.
209 $title = Title::newFromText( $term );
210 if ( $title !== null ) {
211 $this->getHookRunner()->onSpecialSearchNogomatch( $title );
212 }
213 }
214
215 $this->setupPage( $term );
216
217 if ( $this->getConfig()->get( MainConfigNames::DisableTextSearch ) ) {
218 $searchForwardUrl = $this->getConfig()->get( MainConfigNames::SearchForwardUrl );
219 if ( $searchForwardUrl ) {
220 $url = str_replace( '$1', urlencode( $term ), $searchForwardUrl );
221 $out->redirect( $url );
222 } else {
223 $out->addHTML( $this->showGoogleSearch( $term ) );
224 }
225
226 return;
227 }
228
229 $this->showResults( $term );
230 }
231
240 private function showGoogleSearch( $term ) {
241 return "<fieldset>" .
242 "<legend>" .
243 $this->msg( 'search-external' )->escaped() .
244 "</legend>" .
245 "<p class='mw-searchdisabled'>" .
246 $this->msg( 'searchdisabled' )->escaped() .
247 "</p>" .
248 // googlesearch is part of $wgRawHtmlMessages and safe to use as is here
249 $this->msg( 'googlesearch' )->rawParams(
250 htmlspecialchars( $term ),
251 'UTF-8',
252 $this->msg( 'searchbutton' )->escaped()
253 )->text() .
254 "</fieldset>";
255 }
256
262 public function load() {
263 $this->loadStatus = new Status();
264
265 $request = $this->getRequest();
266 $this->searchEngineType = $request->getVal( 'srbackend' );
267
268 list( $this->limit, $this->offset ) = $request->getLimitOffsetForUser(
269 $this->getUser(),
270 20,
271 'searchlimit'
272 );
273 $this->mPrefix = $request->getVal( 'prefix', '' );
274 if ( $this->mPrefix !== '' ) {
275 $this->setExtraParam( 'prefix', $this->mPrefix );
276 }
277
278 $sort = $request->getVal( 'sort', SearchEngine::DEFAULT_SORT );
279 $validSorts = $this->getSearchEngine()->getValidSorts();
280 if ( !in_array( $sort, $validSorts ) ) {
281 $this->loadStatus->warning( 'search-invalid-sort-order', $sort,
282 implode( ', ', $validSorts ) );
283 } elseif ( $sort !== $this->sort ) {
284 $this->sort = $sort;
285 $this->setExtraParam( 'sort', $this->sort );
286 }
287
288 $user = $this->getUser();
289
290 # Extract manually requested namespaces
291 $nslist = $this->powerSearch( $request );
292 if ( $nslist === [] ) {
293 # Fallback to user preference
294 $nslist = $this->searchConfig->userNamespaces( $user );
295 }
296
297 $profile = null;
298 if ( $nslist === [] ) {
299 $profile = 'default';
300 }
301
302 $profile = $request->getVal( 'profile', $profile );
303 $profiles = $this->getSearchProfiles();
304 if ( $profile === null ) {
305 // BC with old request format
306 $profile = 'advanced';
307 foreach ( $profiles as $key => $data ) {
308 if ( $nslist === $data['namespaces'] && $key !== 'advanced' ) {
309 $profile = $key;
310 }
311 }
312 $this->namespaces = $nslist;
313 } elseif ( $profile === 'advanced' ) {
314 $this->namespaces = $nslist;
315 } elseif ( isset( $profiles[$profile]['namespaces'] ) ) {
316 $this->namespaces = $profiles[$profile]['namespaces'];
317 } else {
318 // Unknown profile requested
319 $this->loadStatus->warning( 'search-unknown-profile', $profile );
320 $profile = 'default';
321 $this->namespaces = $profiles['default']['namespaces'];
322 }
323
324 $this->fulltext = $request->getVal( 'fulltext' );
325 $this->runSuggestion = (bool)$request->getVal( 'runsuggestion', '1' );
326 $this->profile = $profile;
327 }
328
335 public function goResult( $term ) {
336 # If the string cannot be used to create a title
337 if ( Title::newFromText( $term ) === null ) {
338 return null;
339 }
340 # If there's an exact or very near match, jump right there.
341 $title = $this->getSearchEngine()
342 ->getNearMatcher( $this->getConfig() )->getNearMatch( $term );
343 if ( $title === null ) {
344 return null;
345 }
346 $url = null;
347 if ( !$this->getHookRunner()->onSpecialSearchGoResult( $term, $title, $url ) ) {
348 return null;
349 }
350
351 if (
352 // If there is a preference set to NOT redirect on exact page match
353 // then return null (which prevents direction)
354 !$this->redirectOnExactMatch()
355 // BUT ...
356 // ... ignore no-redirect preference if the exact page match is an interwiki link
357 && !$title->isExternal()
358 // ... ignore no-redirect preference if the exact page match is NOT in the main
359 // namespace AND there's a namespace in the search string
360 && !( $title->getNamespace() !== NS_MAIN && strpos( $term, ':' ) > 0 )
361 ) {
362 return null;
363 }
364
365 return $url ?? $title->getFullUrlForRedirect();
366 }
367
368 private function redirectOnExactMatch() {
369 if ( !$this->getConfig()->get( MainConfigNames::SearchMatchRedirectPreference ) ) {
370 // If the preference for whether to redirect is disabled, use the default setting
371 $defaultOptions = $this->userOptionsManager->getDefaultOptions();
372 return $defaultOptions['search-match-redirect'];
373 } else {
374 // Otherwise use the user's preference
375 return $this->userOptionsManager->getOption( $this->getUser(), 'search-match-redirect' );
376 }
377 }
378
382 public function showResults( $term ) {
383 if ( $this->searchEngineType !== null ) {
384 $this->setExtraParam( 'srbackend', $this->searchEngineType );
385 }
386
387 $out = $this->getOutput();
388 $widgetOptions = $this->getConfig()->get( MainConfigNames::SpecialSearchFormOptions );
390 $this,
391 $this->searchConfig,
392 $this->getHookContainer(),
393 $this->languageConverterFactory->getLanguageConverter( $this->getLanguage() ),
394 $this->nsInfo,
395 $this->getSearchProfiles()
396 );
397 $filePrefix = $this->getContentLanguage()->getFormattedNsText( NS_FILE ) . ':';
398 if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
399 // Empty query -- straight view of search form
400 if ( !$this->getHookRunner()->onSpecialSearchResultsPrepend( $this, $out, $term ) ) {
401 # Hook requested termination
402 return;
403 }
404 $out->enableOOUI();
405 // The form also contains the 'Showing results 0 - 20 of 1234' so we can
406 // only do the form render here for the empty $term case. Rendering
407 // the form when a search is provided is repeated below.
408 $out->addHTML( $formWidget->render(
409 $this->profile, $term, 0, 0, $this->offset, $this->isPowerSearch(), $widgetOptions
410 ) );
411 return;
412 }
413
414 $engine = $this->getSearchEngine();
415 $engine->setFeatureData( 'rewrite', $this->runSuggestion );
416 $engine->setLimitOffset( $this->limit, $this->offset );
417 $engine->setNamespaces( $this->namespaces );
418 $engine->setSort( $this->sort );
419 $engine->prefix = $this->mPrefix;
420
421 $this->getHookRunner()->onSpecialSearchSetupEngine( $this, $this->profile, $engine );
422 if ( !$this->getHookRunner()->onSpecialSearchResultsPrepend( $this, $out, $term ) ) {
423 # Hook requested termination
424 return;
425 }
426
427 $title = Title::newFromText( $term );
428 $languageConverter = $this->languageConverterFactory->getLanguageConverter( $this->getContentLanguage() );
429 if ( $languageConverter->hasVariants() ) {
430 // findVariantLink will replace the link arg as well but we want to keep our original
431 // search string, use a copy in the $variantTerm var so that $term remains intact.
432 $variantTerm = $term;
433 $languageConverter->findVariantLink( $variantTerm, $title );
434 }
435
436 $showSuggestion = $title === null || !$title->isKnown();
437 $engine->setShowSuggestion( $showSuggestion );
438
439 $rewritten = $engine->replacePrefixes( $term );
440 if ( $rewritten !== $term ) {
441 wfDeprecatedMsg( 'SearchEngine::replacePrefixes() was overridden by ' .
442 get_class( $engine ) . ', this is deprecated since MediaWiki 1.32',
443 '1.32', false, false );
444 }
445
446 // fetch search results
447 $titleMatches = $engine->searchTitle( $rewritten );
448 $textMatches = $engine->searchText( $rewritten );
449
450 $textStatus = null;
451 if ( $textMatches instanceof Status ) {
452 $textStatus = $textMatches;
453 $textMatches = $textStatus->getValue();
454 }
455
456 // Get number of results
457 $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0;
458 if ( $titleMatches ) {
459 $titleMatchesNum = $titleMatches->numRows();
460 $numTitleMatches = $titleMatches->getTotalHits();
461 }
462 if ( $textMatches ) {
463 $textMatchesNum = $textMatches->numRows();
464 $numTextMatches = $textMatches->getTotalHits();
465 if ( $textMatchesNum > 0 ) {
466 $engine->augmentSearchResults( $textMatches );
467 }
468 }
469 $num = $titleMatchesNum + $textMatchesNum;
470 $totalRes = $numTitleMatches + $numTextMatches;
471
472 // start rendering the page
473 $out->enableOOUI();
474 $out->addHTML( $formWidget->render(
475 $this->profile, $term, $num, $totalRes, $this->offset, $this->isPowerSearch(), $widgetOptions
476 ) );
477
478 // did you mean... suggestions
479 if ( $textMatches ) {
480 $dymWidget = new MediaWiki\Search\SearchWidgets\DidYouMeanWidget( $this );
481 $out->addHTML( $dymWidget->render( $term, $textMatches ) );
482 }
483
484 $hasSearchErrors = $textStatus && $textStatus->getErrors() !== [];
485 $hasOtherResults = $textMatches &&
486 $textMatches->hasInterwikiResults( ISearchResultSet::INLINE_RESULTS );
487
488 if ( $textMatches && $textMatches->hasInterwikiResults( ISearchResultSet::SECONDARY_RESULTS ) ) {
489 $out->addHTML( '<div class="searchresults mw-searchresults-has-iw">' );
490 } else {
491 $out->addHTML( '<div class="searchresults">' );
492 }
493
494 if ( $hasSearchErrors || $this->loadStatus->getErrors() ) {
495 if ( $textStatus === null ) {
496 $textStatus = $this->loadStatus;
497 } else {
498 $textStatus->merge( $this->loadStatus );
499 }
500 list( $error, $warning ) = $textStatus->splitByErrorType();
501 if ( $error->getErrors() ) {
502 $out->addHTML( Html::errorBox(
503 $error->getHTML( 'search-error' )
504 ) );
505 }
506 if ( $warning->getErrors() ) {
507 $out->addHTML( Html::warningBox(
508 $warning->getHTML( 'search-warning' )
509 ) );
510 }
511 }
512
513 // If we have no results and have not already displayed an error message
514 if ( $num === 0 && !$hasSearchErrors ) {
515 $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", [
516 $hasOtherResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
517 wfEscapeWikiText( $term ),
518 $term,
519 ] );
520 }
521
522 // Show the create link ahead
523 $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
524
525 $this->getHookRunner()->onSpecialSearchResults( $term, $titleMatches, $textMatches );
526
527 // Although $num might be 0 there can still be secondary or inline
528 // results to display.
529 $linkRenderer = $this->getLinkRenderer();
530 $mainResultWidget = new FullSearchResultWidget(
531 $this, $linkRenderer, $this->getHookContainer() );
532
533 // Default (null) on. Can be explicitly disabled.
534 if ( $engine->getFeatureData( 'enable-new-crossproject-page' ) !== false ) {
535 $sidebarResultWidget = new InterwikiSearchResultWidget( $this, $linkRenderer );
536 $sidebarResultsWidget = new InterwikiSearchResultSetWidget(
537 $this,
538 $sidebarResultWidget,
539 $linkRenderer,
540 $this->interwikiLookup,
541 $engine->getFeatureData( 'show-multimedia-search-results' )
542 );
543 } else {
544 $sidebarResultWidget = new SimpleSearchResultWidget( $this, $linkRenderer );
545 $sidebarResultsWidget = new SimpleSearchResultSetWidget(
546 $this,
547 $sidebarResultWidget,
548 $linkRenderer,
549 $this->interwikiLookup
550 );
551 }
552
553 $widget = new BasicSearchResultSetWidget( $this, $mainResultWidget, $sidebarResultsWidget );
554
555 $out->addHTML( $widget->render(
556 $term, $this->offset, $titleMatches, $textMatches
557 ) );
558
559 $out->addHTML( '<div class="mw-search-visualclear"></div>' );
560 $this->prevNextLinks( $totalRes, $textMatches, $term, $out );
561
562 // Close <div class='searchresults'>
563 $out->addHTML( "</div>" );
564
565 $this->getHookRunner()->onSpecialSearchResultsAppend( $this, $out, $term );
566 }
567
574 protected function showCreateLink( $title, $num, $titleMatches, $textMatches ) {
575 // show direct page/create link if applicable
576
577 // Check DBkey !== '' in case of fragment link only.
578 if ( $title === null || $title->getDBkey() === ''
579 || ( $titleMatches !== null && $titleMatches->searchContainedSyntax() )
580 || ( $textMatches !== null && $textMatches->searchContainedSyntax() )
581 ) {
582 // invalid title
583 // preserve the paragraph for margins etc...
584 $this->getOutput()->addHTML( '<p></p>' );
585
586 return;
587 }
588
589 $messageName = 'searchmenu-new-nocreate';
590 $linkClass = 'mw-search-createlink';
591
592 if ( !$title->isExternal() ) {
593 if ( $title->isKnown() ) {
594 $messageName = 'searchmenu-exists';
595 $linkClass = 'mw-search-exists';
596 } elseif (
597 $this->contentHandlerFactory->getContentHandler( $title->getContentModel() )
598 ->supportsDirectEditing()
599 && $this->getAuthority()->probablyCan( 'edit', $title )
600 ) {
601 $messageName = 'searchmenu-new';
602 }
603 }
604
605 $params = [
606 $messageName,
607 wfEscapeWikiText( $title->getPrefixedText() ),
608 Message::numParam( $num )
609 ];
610 $this->getHookRunner()->onSpecialSearchCreateLink( $title, $params );
611
612 // Extensions using the hook might still return an empty $messageName
613 // @phan-suppress-next-line PhanRedundantCondition Set by hook
614 if ( $messageName ) {
615 $this->getOutput()->wrapWikiMsg( "<p class=\"$linkClass\">\n$1</p>", $params );
616 } else {
617 // preserve the paragraph for margins etc...
618 $this->getOutput()->addHTML( '<p></p>' );
619 }
620 }
621
628 protected function setupPage( $term ) {
629 $out = $this->getOutput();
630
631 $this->setHeaders();
632 $this->outputHeader();
633 // TODO: Is this true? The namespace remember uses a user token
634 // on save.
635 $out->setPreventClickjacking( false );
636 $this->addHelpLink( 'Help:Searching' );
637
638 if ( strval( $term ) !== '' ) {
639 $out->setPageTitle( $this->msg( 'searchresults' ) );
640 $out->setHTMLTitle( $this->msg( 'pagetitle' )
641 ->plaintextParams( $this->msg( 'searchresults-title' )->plaintextParams( $term )->text() )
642 ->inContentLanguage()->text()
643 );
644 }
645
646 if ( $this->mPrefix !== '' ) {
647 $subtitle = $this->msg( 'search-filter-title-prefix' )->plaintextParams( $this->mPrefix );
648 $params = $this->powerSearchOptions();
649 unset( $params['prefix'] );
650 $params += [
651 'search' => $term,
652 'fulltext' => 1,
653 ];
654
655 $subtitle .= ' (';
656 $subtitle .= Xml::element(
657 'a',
658 [
659 'href' => $this->getPageTitle()->getLocalURL( $params ),
660 'title' => $this->msg( 'search-filter-title-prefix-reset' )->text(),
661 ],
662 $this->msg( 'search-filter-title-prefix-reset' )->text()
663 );
664 $subtitle .= ')';
665 $out->setSubtitle( $subtitle );
666 }
667
668 $out->addJsConfigVars( [ 'searchTerm' => $term ] );
669 $out->addModules( 'mediawiki.special.search' );
670 $out->addModuleStyles( [
671 'mediawiki.special', 'mediawiki.special.search.styles', 'mediawiki.ui', 'mediawiki.ui.button',
672 'mediawiki.ui.input', 'mediawiki.widgets.SearchInputWidget.styles',
673 ] );
674 }
675
681 protected function isPowerSearch() {
682 return $this->profile === 'advanced';
683 }
684
692 protected function powerSearch( &$request ) {
693 $arr = [];
694 foreach ( $this->searchConfig->searchableNamespaces() as $ns => $name ) {
695 if ( $request->getCheck( 'ns' . $ns ) ) {
696 $arr[] = $ns;
697 }
698 }
699
700 return $arr;
701 }
702
710 public function powerSearchOptions() {
711 $opt = [];
712 if ( $this->isPowerSearch() ) {
713 foreach ( $this->namespaces as $n ) {
714 $opt['ns' . $n] = 1;
715 }
716 } else {
717 $opt['profile'] = $this->profile;
718 }
719
720 return $opt + $this->extraParams;
721 }
722
728 protected function saveNamespaces() {
729 $user = $this->getUser();
730 $request = $this->getRequest();
731
732 if ( $user->isRegistered() &&
733 $user->matchEditToken(
734 $request->getVal( 'nsRemember' ),
735 'searchnamespace',
736 $request
737 ) && !$this->readOnlyMode->isReadOnly()
738 ) {
739 // Reset namespace preferences: namespaces are not searched
740 // when they're not mentioned in the URL parameters.
741 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
742 $this->userOptionsManager->setOption( $user, 'searchNs' . $n, false );
743 }
744 // The request parameters include all the namespaces to be searched.
745 // Even if they're the same as an existing profile, they're not eaten.
746 foreach ( $this->namespaces as $n ) {
747 $this->userOptionsManager->setOption( $user, 'searchNs' . $n, true );
748 }
749
750 DeferredUpdates::addCallableUpdate( static function () use ( $user ) {
751 $user->saveSettings();
752 } );
753
754 return true;
755 }
756
757 return false;
758 }
759
764 protected function getSearchProfiles() {
765 // Builds list of Search Types (profiles)
766 $nsAllSet = array_keys( $this->searchConfig->searchableNamespaces() );
767 $defaultNs = $this->searchConfig->defaultNamespaces();
768 $profiles = [
769 'default' => [
770 'message' => 'searchprofile-articles',
771 'tooltip' => 'searchprofile-articles-tooltip',
772 'namespaces' => $defaultNs,
773 'namespace-messages' => $this->searchConfig->namespacesAsText(
774 $defaultNs
775 ),
776 ],
777 'images' => [
778 'message' => 'searchprofile-images',
779 'tooltip' => 'searchprofile-images-tooltip',
780 'namespaces' => [ NS_FILE ],
781 ],
782 'all' => [
783 'message' => 'searchprofile-everything',
784 'tooltip' => 'searchprofile-everything-tooltip',
785 'namespaces' => $nsAllSet,
786 ],
787 'advanced' => [
788 'message' => 'searchprofile-advanced',
789 'tooltip' => 'searchprofile-advanced-tooltip',
790 'namespaces' => self::NAMESPACES_CURRENT,
791 ]
792 ];
793
794 $this->getHookRunner()->onSpecialSearchProfiles( $profiles );
795
796 foreach ( $profiles as &$data ) {
797 if ( !is_array( $data['namespaces'] ) ) {
798 continue;
799 }
800 sort( $data['namespaces'] );
801 }
802
803 return $profiles;
804 }
805
811 public function getSearchEngine() {
812 if ( $this->searchEngine === null ) {
813 $this->searchEngine = $this->searchEngineFactory->create( $this->searchEngineType );
814 }
815
816 return $this->searchEngine;
817 }
818
823 public function getProfile() {
824 return $this->profile;
825 }
826
831 public function getNamespaces() {
832 return $this->namespaces;
833 }
834
844 public function setExtraParam( $key, $value ) {
845 $this->extraParams[$key] = $value;
846 }
847
856 public function getPrefix() {
857 return $this->mPrefix;
858 }
859
866 private function prevNextLinks( ?int $totalRes, ?ISearchResultSet $textMatches, string $term, OutputPage $out ) {
867 if ( $totalRes > $this->limit || $this->offset ) {
868 // Allow matches to define the correct offset, as interleaved
869 // AB testing may require a different next page offset.
870 if ( $textMatches && $textMatches->getOffset() !== null ) {
871 $offset = $textMatches->getOffset();
872 } else {
873 $offset = $this->offset;
874 }
875
876 // use the rewritten search term for subsequent page searches
877 $newSearchTerm = $term;
878 if ( $textMatches && $textMatches->hasRewrittenQuery() ) {
879 $newSearchTerm = $textMatches->getQueryAfterRewrite();
880 }
881
882 $prevNext =
883 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable offset is not null
884 $this->buildPrevNextNavigation( $offset, $this->limit,
885 $this->powerSearchOptions() + [ 'search' => $newSearchTerm ],
886 $this->limit + $this->offset >= $totalRes );
887 $out->addHTML( "<div class='mw-search-pager-bottom'>{$prevNext}</div>\n" );
888 }
889 }
890
891 protected function getGroupName() {
892 return 'pages';
893 }
894}
getUser()
const NS_FILE
Definition Defines.php:70
const NS_MAIN
Definition Defines.php:64
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
An interface for creating language converters.
A class containing constants representing the names of configuration variables.
Renders a suggested search for the user, or tells the user a suggested search was run instead of the ...
Renders a 'full' multi-line search result with metadata.
Renders one or more ISearchResultSets into a sidebar grouped by interwiki prefix.
Renders one or more ISearchResultSets into a sidebar grouped by interwiki prefix.
A service class to control user options.
static numParam( $num)
Definition Message.php:1145
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
This is one of the Core classes and should be read at least once by any new developers.
setPageTitle( $name)
"Page title" means the contents of <h1>.
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
setPreventClickjacking(bool $enable)
Set the prevent-clickjacking flag.
addHTML( $text)
Append $text to the body HTML.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
setSubtitle( $str)
Replace the subtitle with $str.
A service class for fetching the wiki's current read-only mode.
Configuration handling class for SearchEngine.
Factory class for SearchEngine.
Contain a class for special pages.
Parent class for all special pages.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
buildPrevNextNavigation( $offset, $limit, array $query=[], $atend=false, $subpage=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
getContentLanguage()
Shortcut to get content language.
implements Special:Search - Run text & title search and display the output
string null $searchEngineType
Search engine type, if not default.
goResult( $term)
If an exact title match can be found, jump straight ahead to it.
setExtraParam( $key, $value)
Users of hook SpecialSearchSetupEngine can use this to add more params to links to not lose selection...
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
string $mPrefix
The prefix url parameter.
null string $profile
Current search profile.
load()
Set up basic search parameters from the request and user settings.
SearchEngineConfig $searchConfig
Search engine configurations.
getPrefix()
The prefix value send to Special:Search using the 'prefix' URI param It means that the user is willin...
__construct(SearchEngineConfig $searchConfig, SearchEngineFactory $searchEngineFactory, NamespaceInfo $nsInfo, IContentHandlerFactory $contentHandlerFactory, InterwikiLookup $interwikiLookup, ReadOnlyMode $readOnlyMode, UserOptionsManager $userOptionsManager, LanguageConverterFactory $languageConverterFactory)
saveNamespaces()
Save namespace preferences when we're supposed to.
getProfile()
Current search profile.
SearchEngine $searchEngine
Search engine.
isPowerSearch()
Return true if current search is a power (advanced) search.
getNamespaces()
Current namespaces.
powerSearchOptions()
Reconstruct the 'power search' options for links TODO: Instead of exposing this publicly,...
powerSearch(&$request)
Extract "power search" namespace settings from the request object, returning a list of index numbers ...
array $extraParams
For links.
execute( $par)
Entry point.
setupPage( $term)
Sets up everything for the HTML output page including styles, javascript, page title,...
showCreateLink( $title, $num, $titleMatches, $textMatches)
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
A set of SearchEngine results.
searchContainedSyntax()
Did the search contain search syntax? If so, Special:Search won't offer the user a link to a create a...
hasInterwikiResults( $type=self::SECONDARY_RESULTS)
Check if there are results on other wikis.
hasRewrittenQuery()
Some search modes will run an alternative query that it thinks gives a better result than the provide...
getTotalHits()
Some search modes return a total hit count for the query in the entire article database.
Service interface for looking up Interwiki records.