MediaWiki  master
SpecialSearch.php
Go to the documentation of this file.
1 <?php
37 
42 class SpecialSearch extends SpecialPage {
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 
87 
91  protected $runSuggestion = true;
92 
97  protected $searchConfig;
98 
101 
103  private $nsInfo;
104 
107 
110 
112  private $readOnlyMode;
113 
116 
119 
124  private $loadStatus;
125 
126  private const NAMESPACES_CURRENT = 'sense';
127 
138  public function __construct(
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  // Show the create link ahead
514  $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
515 
516  $this->getHookRunner()->onSpecialSearchResults( $term, $titleMatches, $textMatches );
517 
518  // If we have no results and have not already displayed an error message
519  if ( $num === 0 && !$hasSearchErrors ) {
520  $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", [
521  $hasOtherResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
522  wfEscapeWikiText( $term ),
523  $term,
524  ] );
525  }
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,
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,
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 {
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( "<p class='mw-search-pager-bottom'>{$prevNext}</p>\n" );
888  }
889  }
890 
891  protected function getGroupName() {
892  return 'pages';
893  }
894 }
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,...
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:775
static errorBox( $html, $heading='', $className='')
Return an error box.
Definition: Html.php:788
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:1166
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.
Definition: OutputPage.php:54
addHTML( $text)
Append $text to the body HTML.
A service class for fetching the wiki's current read-only mode.
Configuration handling class for SearchEngine.
Factory class for SearchEngine.
const DEFAULT_SORT
Parent class for all special pages.
Definition: SpecialPage.php:44
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.
getUser()
Shortcut to get the User executing this instance.
buildPrevNextNavigation( $offset, $limit, array $query=[], $atend=false, $subpage=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:81
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
IContentHandlerFactory $contentHandlerFactory
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.
const NAMESPACES_CURRENT
null string $profile
Current search profile.
load()
Set up basic search parameters from the request and user settings.
SearchEngineConfig $searchConfig
Search engine configurations.
InterwikiLookup $interwikiLookup
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.
UserOptionsManager $userOptionsManager
getProfile()
Current search profile.
SearchEngine $searchEngine
Search engine.
NamespaceInfo $nsInfo
isPowerSearch()
Return true if current search is a power (advanced) search.
Status $loadStatus
Holds any parameter validation errors that should be displayed back to the user.
getNamespaces()
Current namespaces.
powerSearchOptions()
Reconstruct the 'power search' options for links TODO: Instead of exposing this publicly,...
showResults( $term)
powerSearch(&$request)
Extract "power search" namespace settings from the request object, returning a list of index numbers ...
showGoogleSearch( $term)
Output a google search form if search is disabled.
prevNextLinks(?int $totalRes, ?ISearchResultSet $textMatches, string $term, OutputPage $out)
array $extraParams
For links.
SearchEngineFactory $searchEngineFactory
ReadOnlyMode $readOnlyMode
execute( $par)
Entry point.
setupPage( $term)
Sets up everything for the HTML output page including styles, javascript, page title,...
showCreateLink( $title, $num, $titleMatches, $textMatches)
LanguageConverterFactory $languageConverterFactory
merge( $other, $overwriteValue=false)
Merge another status object into this one.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:370
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:43
A set of SearchEngine results.
const INLINE_RESULTS
Identifier for interwiki results that can be displayed even if no existing main wiki results exist.
const SECONDARY_RESULTS
Identifier for interwiki results that are displayed only together with existing main wiki results.
hasRewrittenQuery()
Some search modes will run an alternative query that it thinks gives a better result than the provide...
Service interface for looking up Interwiki records.