MediaWiki 1.40.4
SpecialSearch.php
Go to the documentation of this file.
1<?php
41
55 protected $profile;
56
58 protected $searchEngine;
59
61 protected $searchEngineType = null;
62
64 protected $extraParams = [];
65
70 protected $mPrefix;
71
75 protected $limit, $offset;
76
80 protected $namespaces;
81
85 protected $fulltext;
86
90 protected $sort = SearchEngine::DEFAULT_SORT;
91
95 protected $runSuggestion = true;
96
101 protected $searchConfig;
102
104 private $searchEngineFactory;
105
107 private $nsInfo;
108
110 private $contentHandlerFactory;
111
113 private $interwikiLookup;
114
116 private $readOnlyMode;
117
119 private $userOptionsManager;
120
122 private $languageConverterFactory;
123
125 private $repoGroup;
126
128 private $thumbnailProvider;
129
130 private TitleMatcher $titleMatcher;
131
136 private $loadStatus;
137
138 private const NAMESPACES_CURRENT = 'sense';
139
153 public function __construct(
154 SearchEngineConfig $searchConfig,
155 SearchEngineFactory $searchEngineFactory,
156 NamespaceInfo $nsInfo,
157 IContentHandlerFactory $contentHandlerFactory,
158 InterwikiLookup $interwikiLookup,
159 ReadOnlyMode $readOnlyMode,
160 UserOptionsManager $userOptionsManager,
161 LanguageConverterFactory $languageConverterFactory,
162 RepoGroup $repoGroup,
163 SearchResultThumbnailProvider $thumbnailProvider,
164 TitleMatcher $titleMatcher
165 ) {
166 parent::__construct( 'Search' );
167 $this->searchConfig = $searchConfig;
168 $this->searchEngineFactory = $searchEngineFactory;
169 $this->nsInfo = $nsInfo;
170 $this->contentHandlerFactory = $contentHandlerFactory;
171 $this->interwikiLookup = $interwikiLookup;
172 $this->readOnlyMode = $readOnlyMode;
173 $this->userOptionsManager = $userOptionsManager;
174 $this->languageConverterFactory = $languageConverterFactory;
175 $this->repoGroup = $repoGroup;
176 $this->thumbnailProvider = $thumbnailProvider;
177 $this->titleMatcher = $titleMatcher;
178 }
179
185 public function execute( $par ) {
186 $request = $this->getRequest();
187 $out = $this->getOutput();
188
189 // Fetch the search term
190 $term = str_replace( "\n", " ", $request->getText( 'search' ) );
191
192 // Historically search terms have been accepted not only in the search query
193 // parameter, but also as part of the primary url. This can have PII implications
194 // in releasing page view data. As such issue a 301 redirect to the correct
195 // URL.
196 if ( $par !== null && $par !== '' && $term === '' ) {
197 $query = $request->getValues();
198 unset( $query['title'] );
199 // Strip underscores from title parameter; most of the time we'll want
200 // text form here. But don't strip underscores from actual text params!
201 $query['search'] = str_replace( '_', ' ', $par );
202 $out->redirect( $this->getPageTitle()->getFullURL( $query ), 301 );
203 return;
204 }
205
206 // Need to load selected namespaces before handling nsRemember
207 $this->load();
208 // TODO: This performs database actions on GET request, which is going to
209 // be a problem for our multi-datacenter work.
210 if ( $request->getCheck( 'nsRemember' ) ) {
211 $this->saveNamespaces();
212 // Remove the token from the URL to prevent the user from inadvertently
213 // exposing it (e.g. by pasting it into a public wiki page) or undoing
214 // later settings changes (e.g. by reloading the page).
215 $query = $request->getValues();
216 unset( $query['title'], $query['nsRemember'] );
217 $out->redirect( $this->getPageTitle()->getFullURL( $query ) );
218 return;
219 }
220
221 if ( !$request->getVal( 'fulltext' ) && !$request->getCheck( 'offset' ) ) {
222 $url = $this->goResult( $term );
223 if ( $url !== null ) {
224 // successful 'go'
225 $out->redirect( $url );
226 return;
227 }
228 // No match. If it could plausibly be a title
229 // run the No go match hook.
230 $title = Title::newFromText( $term );
231 if ( $title !== null ) {
232 $this->getHookRunner()->onSpecialSearchNogomatch( $title );
233 }
234 }
235
236 $this->setupPage( $term );
237
238 if ( $this->getConfig()->get( MainConfigNames::DisableTextSearch ) ) {
239 $searchForwardUrl = $this->getConfig()->get( MainConfigNames::SearchForwardUrl );
240 if ( $searchForwardUrl ) {
241 $url = str_replace( '$1', urlencode( $term ), $searchForwardUrl );
242 $out->redirect( $url );
243 } else {
244 $out->addHTML( $this->showGoogleSearch( $term ) );
245 }
246
247 return;
248 }
249
250 $this->showResults( $term );
251 }
252
261 private function showGoogleSearch( $term ) {
262 return "<fieldset>" .
263 "<legend>" .
264 $this->msg( 'search-external' )->escaped() .
265 "</legend>" .
266 "<p class='mw-searchdisabled'>" .
267 $this->msg( 'searchdisabled' )->escaped() .
268 "</p>" .
269 // googlesearch is part of $wgRawHtmlMessages and safe to use as is here
270 $this->msg( 'googlesearch' )->rawParams(
271 htmlspecialchars( $term ),
272 'UTF-8',
273 $this->msg( 'searchbutton' )->escaped()
274 )->text() .
275 "</fieldset>";
276 }
277
283 public function load() {
284 $this->loadStatus = new Status();
285
286 $request = $this->getRequest();
287 $this->searchEngineType = $request->getVal( 'srbackend' );
288
289 [ $this->limit, $this->offset ] = $request->getLimitOffsetForUser(
290 $this->getUser(),
291 20,
292 'searchlimit'
293 );
294 $this->mPrefix = $request->getVal( 'prefix', '' );
295 if ( $this->mPrefix !== '' ) {
296 $this->setExtraParam( 'prefix', $this->mPrefix );
297 }
298
299 $sort = $request->getVal( 'sort', SearchEngine::DEFAULT_SORT );
300 $validSorts = $this->getSearchEngine()->getValidSorts();
301 if ( !in_array( $sort, $validSorts ) ) {
302 $this->loadStatus->warning( 'search-invalid-sort-order', $sort,
303 implode( ', ', $validSorts ) );
304 } elseif ( $sort !== $this->sort ) {
305 $this->sort = $sort;
306 $this->setExtraParam( 'sort', $this->sort );
307 }
308
309 $user = $this->getUser();
310
311 # Extract manually requested namespaces
312 $nslist = $this->powerSearch( $request );
313 if ( $nslist === [] ) {
314 # Fallback to user preference
315 $nslist = $this->searchConfig->userNamespaces( $user );
316 }
317
318 $profile = null;
319 if ( $nslist === [] ) {
320 $profile = 'default';
321 }
322
323 $profile = $request->getVal( 'profile', $profile );
324 $profiles = $this->getSearchProfiles();
325 if ( $profile === null ) {
326 // BC with old request format
327 $profile = 'advanced';
328 foreach ( $profiles as $key => $data ) {
329 if ( $nslist === $data['namespaces'] && $key !== 'advanced' ) {
330 $profile = $key;
331 }
332 }
333 $this->namespaces = $nslist;
334 } elseif ( $profile === 'advanced' ) {
335 $this->namespaces = $nslist;
336 } elseif ( isset( $profiles[$profile]['namespaces'] ) ) {
337 $this->namespaces = $profiles[$profile]['namespaces'];
338 } else {
339 // Unknown profile requested
340 $this->loadStatus->warning( 'search-unknown-profile', $profile );
341 $profile = 'default';
342 $this->namespaces = $profiles['default']['namespaces'];
343 }
344
345 $this->fulltext = $request->getVal( 'fulltext' );
346 $this->runSuggestion = (bool)$request->getVal( 'runsuggestion', '1' );
347 $this->profile = $profile;
348 }
349
356 public function goResult( $term ) {
357 # If the string cannot be used to create a title
358 if ( Title::newFromText( $term ) === null ) {
359 return null;
360 }
361 # If there's an exact or very near match, jump right there.
362 $title = $this->titleMatcher->getNearMatch( $term );
363 if ( $title === null ) {
364 return null;
365 }
366 $url = null;
367 if ( !$this->getHookRunner()->onSpecialSearchGoResult( $term, $title, $url ) ) {
368 return null;
369 }
370
371 if (
372 // If there is a preference set to NOT redirect on exact page match
373 // then return null (which prevents direction)
374 !$this->redirectOnExactMatch()
375 // BUT ...
376 // ... ignore no-redirect preference if the exact page match is an interwiki link
377 && !$title->isExternal()
378 // ... ignore no-redirect preference if the exact page match is NOT in the main
379 // namespace AND there's a namespace in the search string
380 && !( $title->getNamespace() !== NS_MAIN && strpos( $term, ':' ) > 0 )
381 ) {
382 return null;
383 }
384
385 return $url ?? $title->getFullUrlForRedirect();
386 }
387
388 private function redirectOnExactMatch() {
389 if ( !$this->getConfig()->get( MainConfigNames::SearchMatchRedirectPreference ) ) {
390 // If the preference for whether to redirect is disabled, use the default setting
391 $defaultOptions = $this->userOptionsManager->getDefaultOptions();
392 return $defaultOptions['search-match-redirect'];
393 } else {
394 // Otherwise use the user's preference
395 return $this->userOptionsManager->getOption( $this->getUser(), 'search-match-redirect' );
396 }
397 }
398
402 public function showResults( $term ) {
403 if ( $this->searchEngineType !== null ) {
404 $this->setExtraParam( 'srbackend', $this->searchEngineType );
405 }
406
407 $out = $this->getOutput();
408 $widgetOptions = $this->getConfig()->get( MainConfigNames::SpecialSearchFormOptions );
410 $this,
411 $this->searchConfig,
412 $this->getHookContainer(),
413 $this->languageConverterFactory->getLanguageConverter( $this->getLanguage() ),
414 $this->nsInfo,
415 $this->getSearchProfiles()
416 );
417 $filePrefix = $this->getContentLanguage()->getFormattedNsText( NS_FILE ) . ':';
418 if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
419 // Empty query -- straight view of search form
420 if ( !$this->getHookRunner()->onSpecialSearchResultsPrepend( $this, $out, $term ) ) {
421 # Hook requested termination
422 return;
423 }
424 $out->enableOOUI();
425 // The form also contains the 'Showing results 0 - 20 of 1234' so we can
426 // only do the form render here for the empty $term case. Rendering
427 // the form when a search is provided is repeated below.
428 $out->addHTML( $formWidget->render(
429 $this->profile, $term, 0, 0, $this->offset, $this->isPowerSearch(), $widgetOptions
430 ) );
431 return;
432 }
433
434 $engine = $this->getSearchEngine();
435 $engine->setFeatureData( 'rewrite', $this->runSuggestion );
436 $engine->setLimitOffset( $this->limit, $this->offset );
437 $engine->setNamespaces( $this->namespaces );
438 $engine->setSort( $this->sort );
439 $engine->prefix = $this->mPrefix;
440
441 $this->getHookRunner()->onSpecialSearchSetupEngine( $this, $this->profile, $engine );
442 if ( !$this->getHookRunner()->onSpecialSearchResultsPrepend( $this, $out, $term ) ) {
443 # Hook requested termination
444 return;
445 }
446
447 $title = Title::newFromText( $term );
448 $languageConverter = $this->languageConverterFactory->getLanguageConverter( $this->getContentLanguage() );
449 if ( $languageConverter->hasVariants() ) {
450 // findVariantLink will replace the link arg as well but we want to keep our original
451 // search string, use a copy in the $variantTerm var so that $term remains intact.
452 $variantTerm = $term;
453 $languageConverter->findVariantLink( $variantTerm, $title );
454 }
455
456 $showSuggestion = $title === null || !$title->isKnown();
457 $engine->setShowSuggestion( $showSuggestion );
458
459 $rewritten = $engine->replacePrefixes( $term );
460 if ( $rewritten !== $term ) {
461 wfDeprecatedMsg( 'SearchEngine::replacePrefixes() was overridden by ' .
462 get_class( $engine ) . ', this is deprecated since MediaWiki 1.32',
463 '1.32', false, false );
464 }
465
466 // fetch search results
467 $titleMatches = $engine->searchTitle( $rewritten );
468 $textMatches = $engine->searchText( $rewritten );
469
470 $textStatus = null;
471 if ( $textMatches instanceof Status ) {
472 $textStatus = $textMatches;
473 $textMatches = $textStatus->getValue();
474 }
475
476 // Get number of results
477 $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0;
478 if ( $titleMatches ) {
479 $titleMatchesNum = $titleMatches->numRows();
480 $numTitleMatches = $titleMatches->getTotalHits();
481 }
482 if ( $textMatches ) {
483 $textMatchesNum = $textMatches->numRows();
484 $numTextMatches = $textMatches->getTotalHits();
485 if ( $textMatchesNum > 0 ) {
486 $engine->augmentSearchResults( $textMatches );
487 }
488 }
489 $num = $titleMatchesNum + $textMatchesNum;
490 $totalRes = $numTitleMatches + $numTextMatches;
491
492 // start rendering the page
493 $out->enableOOUI();
494 $out->addHTML( $formWidget->render(
495 $this->profile, $term, $num, $totalRes, $this->offset, $this->isPowerSearch(), $widgetOptions
496 ) );
497
498 // did you mean... suggestions
499 if ( $textMatches ) {
500 $dymWidget = new MediaWiki\Search\SearchWidgets\DidYouMeanWidget( $this );
501 $out->addHTML( $dymWidget->render( $term, $textMatches ) );
502 }
503
504 $hasSearchErrors = $textStatus && $textStatus->getErrors() !== [];
505 $hasInlineIwResults = $textMatches &&
506 $textMatches->hasInterwikiResults( ISearchResultSet::INLINE_RESULTS );
507 $hasSecondaryIwResults = $textMatches &&
508 $textMatches->hasInterwikiResults( ISearchResultSet::SECONDARY_RESULTS );
509
510 $classNames = [ 'searchresults' ];
511 if ( $hasSecondaryIwResults ) {
512 $classNames[] = 'mw-searchresults-has-iw';
513 }
514 if ( $this->offset > 0 ) {
515 $classNames[] = 'mw-searchresults-has-offset';
516 }
517 $out->addHTML( '<div class="' . implode( ' ', $classNames ) . '">' );
518
519 $out->addHTML( '<div class="mw-search-results-info">' );
520
521 if ( $hasSearchErrors || $this->loadStatus->getErrors() ) {
522 if ( $textStatus === null ) {
523 $textStatus = $this->loadStatus;
524 } else {
525 $textStatus->merge( $this->loadStatus );
526 }
527 [ $error, $warning ] = $textStatus->splitByErrorType();
528 if ( $error->getErrors() ) {
529 $out->addHTML( Html::errorBox(
530 $error->getHTML( 'search-error' )
531 ) );
532 }
533 if ( $warning->getErrors() ) {
534 $out->addHTML( Html::warningBox(
535 $warning->getHTML( 'search-warning' )
536 ) );
537 }
538 }
539
540 // If we have no results and have not already displayed an error message
541 if ( $num === 0 && !$hasSearchErrors ) {
542 $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", [
543 $hasInlineIwResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
544 wfEscapeWikiText( $term ),
545 $term
546 ] );
547 }
548
549 // Show the create link ahead
550 $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
551
552 $this->getHookRunner()->onSpecialSearchResults( $term, $titleMatches, $textMatches );
553
554 // Close <div class='mw-search-results-info'>
555 $out->addHTML( '</div>' );
556
557 // Although $num might be 0 there can still be secondary or inline
558 // results to display.
559 $linkRenderer = $this->getLinkRenderer();
560 $mainResultWidget = new FullSearchResultWidget(
561 $this,
562 $linkRenderer,
563 $this->getHookContainer(),
564 $this->repoGroup,
565 $this->thumbnailProvider,
566 $this->userOptionsManager
567 );
568
569 // Default (null) on. Can be explicitly disabled.
570 if ( $engine->getFeatureData( 'enable-new-crossproject-page' ) !== false ) {
571 $sidebarResultWidget = new InterwikiSearchResultWidget( $this, $linkRenderer );
572 $sidebarResultsWidget = new InterwikiSearchResultSetWidget(
573 $this,
574 $sidebarResultWidget,
575 $linkRenderer,
576 $this->interwikiLookup,
577 $engine->getFeatureData( 'show-multimedia-search-results' )
578 );
579 } else {
580 $sidebarResultWidget = new SimpleSearchResultWidget( $this, $linkRenderer );
581 $sidebarResultsWidget = new SimpleSearchResultSetWidget(
582 $this,
583 $sidebarResultWidget,
584 $linkRenderer,
585 $this->interwikiLookup
586 );
587 }
588
589 $widget = new BasicSearchResultSetWidget( $this, $mainResultWidget, $sidebarResultsWidget );
590
591 $out->addHTML( $widget->render(
592 $term, $this->offset, $titleMatches, $textMatches
593 ) );
594
595 $out->addHTML( '<div class="mw-search-visualclear"></div>' );
596 $this->prevNextLinks( $totalRes, $textMatches, $term, $out );
597
598 // Close <div class='searchresults'>
599 $out->addHTML( "</div>" );
600
601 $this->getHookRunner()->onSpecialSearchResultsAppend( $this, $out, $term );
602 }
603
610 protected function showCreateLink( $title, $num, $titleMatches, $textMatches ) {
611 // show direct page/create link if applicable
612
613 // Check DBkey !== '' in case of fragment link only.
614 if ( $title === null || $title->getDBkey() === ''
615 || ( $titleMatches !== null && $titleMatches->searchContainedSyntax() )
616 || ( $textMatches !== null && $textMatches->searchContainedSyntax() )
617 ) {
618 // invalid title
619 // preserve the paragraph for margins etc...
620 $this->getOutput()->addHTML( '<p></p>' );
621
622 return;
623 }
624
625 $messageName = 'searchmenu-new-nocreate';
626 $linkClass = 'mw-search-createlink';
627
628 if ( !$title->isExternal() ) {
629 if ( $title->isKnown() ) {
630 $messageName = 'searchmenu-exists';
631 $linkClass = 'mw-search-exists';
632 } elseif (
633 $this->contentHandlerFactory->getContentHandler( $title->getContentModel() )
634 ->supportsDirectEditing()
635 && $this->getAuthority()->probablyCan( 'edit', $title )
636 ) {
637 $messageName = 'searchmenu-new';
638 }
639 }
640
641 $params = [
642 $messageName,
643 wfEscapeWikiText( $title->getPrefixedText() ),
644 Message::numParam( $num )
645 ];
646 $this->getHookRunner()->onSpecialSearchCreateLink( $title, $params );
647
648 // Extensions using the hook might still return an empty $messageName
649 // @phan-suppress-next-line PhanRedundantCondition Set by hook
650 if ( $messageName ) {
651 $this->getOutput()->wrapWikiMsg( "<p class=\"$linkClass\">\n$1</p>", $params );
652 } else {
653 // preserve the paragraph for margins etc...
654 $this->getOutput()->addHTML( '<p></p>' );
655 }
656 }
657
664 protected function setupPage( $term ) {
665 $out = $this->getOutput();
666
667 $this->setHeaders();
668 $this->outputHeader();
669 // TODO: Is this true? The namespace remember uses a user token
670 // on save.
671 $out->setPreventClickjacking( false );
672 $this->addHelpLink( 'Help:Searching' );
673
674 if ( strval( $term ) !== '' ) {
675 $out->setPageTitle( $this->msg( 'searchresults' ) );
676 $out->setHTMLTitle( $this->msg( 'pagetitle' )
677 ->plaintextParams( $this->msg( 'searchresults-title' )->plaintextParams( $term )->text() )
678 ->inContentLanguage()->text()
679 );
680 }
681
682 if ( $this->mPrefix !== '' ) {
683 $subtitle = $this->msg( 'search-filter-title-prefix' )->plaintextParams( $this->mPrefix );
684 $params = $this->powerSearchOptions();
685 unset( $params['prefix'] );
686 $params += [
687 'search' => $term,
688 'fulltext' => 1,
689 ];
690
691 $subtitle .= ' (';
692 $subtitle .= Xml::element(
693 'a',
694 [
695 'href' => $this->getPageTitle()->getLocalURL( $params ),
696 'title' => $this->msg( 'search-filter-title-prefix-reset' )->text(),
697 ],
698 $this->msg( 'search-filter-title-prefix-reset' )->text()
699 );
700 $subtitle .= ')';
701 $out->setSubtitle( $subtitle );
702 }
703
704 $out->addJsConfigVars( [ 'searchTerm' => $term ] );
705 $out->addModules( 'mediawiki.special.search' );
706 $out->addModuleStyles( [
707 'mediawiki.special', 'mediawiki.special.search.styles', 'mediawiki.ui', 'mediawiki.ui.button',
708 'mediawiki.ui.input', 'mediawiki.widgets.SearchInputWidget.styles',
709 ] );
710 }
711
717 protected function isPowerSearch() {
718 return $this->profile === 'advanced';
719 }
720
728 protected function powerSearch( &$request ) {
729 $arr = [];
730 foreach ( $this->searchConfig->searchableNamespaces() as $ns => $name ) {
731 if ( $request->getCheck( 'ns' . $ns ) ) {
732 $arr[] = $ns;
733 }
734 }
735
736 return $arr;
737 }
738
746 public function powerSearchOptions() {
747 $opt = [];
748 if ( $this->isPowerSearch() ) {
749 foreach ( $this->namespaces as $n ) {
750 $opt['ns' . $n] = 1;
751 }
752 } else {
753 $opt['profile'] = $this->profile;
754 }
755
756 return $opt + $this->extraParams;
757 }
758
764 protected function saveNamespaces() {
765 $user = $this->getUser();
766 $request = $this->getRequest();
767
768 if ( $user->isRegistered() &&
769 $user->matchEditToken(
770 $request->getVal( 'nsRemember' ),
771 'searchnamespace',
772 $request
773 ) && !$this->readOnlyMode->isReadOnly()
774 ) {
775 // Reset namespace preferences: namespaces are not searched
776 // when they're not mentioned in the URL parameters.
777 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
778 $this->userOptionsManager->setOption( $user, 'searchNs' . $n, false );
779 }
780 // The request parameters include all the namespaces to be searched.
781 // Even if they're the same as an existing profile, they're not eaten.
782 foreach ( $this->namespaces as $n ) {
783 $this->userOptionsManager->setOption( $user, 'searchNs' . $n, true );
784 }
785
786 DeferredUpdates::addCallableUpdate( static function () use ( $user ) {
787 $user->saveSettings();
788 } );
789
790 return true;
791 }
792
793 return false;
794 }
795
800 protected function getSearchProfiles() {
801 // Builds list of Search Types (profiles)
802 $nsAllSet = array_keys( $this->searchConfig->searchableNamespaces() );
803 $defaultNs = $this->searchConfig->defaultNamespaces();
804 $profiles = [
805 'default' => [
806 'message' => 'searchprofile-articles',
807 'tooltip' => 'searchprofile-articles-tooltip',
808 'namespaces' => $defaultNs,
809 'namespace-messages' => $this->searchConfig->namespacesAsText(
810 $defaultNs
811 ),
812 ],
813 'images' => [
814 'message' => 'searchprofile-images',
815 'tooltip' => 'searchprofile-images-tooltip',
816 'namespaces' => [ NS_FILE ],
817 ],
818 'all' => [
819 'message' => 'searchprofile-everything',
820 'tooltip' => 'searchprofile-everything-tooltip',
821 'namespaces' => $nsAllSet,
822 ],
823 'advanced' => [
824 'message' => 'searchprofile-advanced',
825 'tooltip' => 'searchprofile-advanced-tooltip',
826 'namespaces' => self::NAMESPACES_CURRENT,
827 ]
828 ];
829
830 $this->getHookRunner()->onSpecialSearchProfiles( $profiles );
831
832 foreach ( $profiles as &$data ) {
833 if ( !is_array( $data['namespaces'] ) ) {
834 continue;
835 }
836 sort( $data['namespaces'] );
837 }
838
839 return $profiles;
840 }
841
847 public function getSearchEngine() {
848 if ( $this->searchEngine === null ) {
849 $this->searchEngine = $this->searchEngineFactory->create( $this->searchEngineType );
850 }
851
852 return $this->searchEngine;
853 }
854
859 public function getProfile() {
860 return $this->profile;
861 }
862
867 public function getNamespaces() {
868 return $this->namespaces;
869 }
870
880 public function setExtraParam( $key, $value ) {
881 $this->extraParams[$key] = $value;
882 }
883
892 public function getPrefix() {
893 return $this->mPrefix;
894 }
895
902 private function prevNextLinks( ?int $totalRes, ?ISearchResultSet $textMatches, string $term, OutputPage $out ) {
903 if ( $totalRes > $this->limit || $this->offset ) {
904 // Allow matches to define the correct offset, as interleaved
905 // AB testing may require a different next page offset.
906 if ( $textMatches && $textMatches->getOffset() !== null ) {
907 $offset = $textMatches->getOffset();
908 } else {
909 $offset = $this->offset;
910 }
911
912 // use the rewritten search term for subsequent page searches
913 $newSearchTerm = $term;
914 if ( $textMatches && $textMatches->hasRewrittenQuery() ) {
915 $newSearchTerm = $textMatches->getQueryAfterRewrite();
916 }
917
918 $prevNext =
919 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable offset is not null
920 $this->buildPrevNextNavigation( $offset, $this->limit,
921 $this->powerSearchOptions() + [ 'search' => $newSearchTerm ],
922 $this->limit + $this->offset >= $totalRes );
923 $out->addHTML( "<div class='mw-search-pager-bottom'>{$prevNext}</div>\n" );
924 }
925 }
926
927 protected function getGroupName() {
928 return 'pages';
929 }
930}
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,...
This class is a collection of static functions that serve two purposes:
Definition Html.php:55
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.
Service implementation of near match title search.
Represents a title within MediaWiki.
Definition Title.php:82
A service class to control user options.
static numParam( $num)
Definition Message.php:1146
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.
Prioritized list of file repositories.
Definition RepoGroup.php:30
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
__construct(SearchEngineConfig $searchConfig, SearchEngineFactory $searchEngineFactory, NamespaceInfo $nsInfo, IContentHandlerFactory $contentHandlerFactory, InterwikiLookup $interwikiLookup, ReadOnlyMode $readOnlyMode, UserOptionsManager $userOptionsManager, LanguageConverterFactory $languageConverterFactory, RepoGroup $repoGroup, SearchResultThumbnailProvider $thumbnailProvider, TitleMatcher $titleMatcher)
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...
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:46
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.