60 protected $extraParams = [];
86 protected $sort = SearchEngine::DEFAULT_SORT;
91 protected $runSuggestion =
true;
100 private $searchEngineFactory;
106 private $contentHandlerFactory;
109 private $interwikiLookup;
112 private $readOnlyMode;
115 private $userOptionsManager;
118 private $languageConverterFactory;
126 private const NAMESPACES_CURRENT =
'sense';
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;
169 $term = str_replace(
"\n",
" ", $request->getText(
'search' ) );
175 if ( $par !==
null && $par !==
'' && $term ===
'' ) {
176 $query = $request->getValues();
177 unset( $query[
'title'] );
180 $query[
'search'] = str_replace(
'_',
' ', $par );
189 if ( $request->getCheck(
'nsRemember' ) ) {
194 $query = $request->getValues();
195 unset( $query[
'title'], $query[
'nsRemember'] );
200 if ( !$request->getVal(
'fulltext' ) && !$request->getCheck(
'offset' ) ) {
202 if ( $url !==
null ) {
209 $title = Title::newFromText( $term );
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 );
223 $out->
addHTML( $this->showGoogleSearch( $term ) );
240 private function showGoogleSearch( $term ) {
241 return "<fieldset>" .
243 $this->msg(
'search-external' )->escaped() .
245 "<p class='mw-searchdisabled'>" .
246 $this->msg(
'searchdisabled' )->escaped() .
249 $this->msg(
'googlesearch' )->rawParams(
250 htmlspecialchars( $term ),
252 $this->msg(
'searchbutton' )->escaped()
263 $this->loadStatus =
new Status();
266 $this->searchEngineType = $request->getVal(
'srbackend' );
268 list( $this->limit, $this->offset ) = $request->getLimitOffsetForUser(
273 $this->mPrefix = $request->getVal(
'prefix',
'' );
274 if ( $this->mPrefix !==
'' ) {
278 $sort = $request->getVal(
'sort', SearchEngine::DEFAULT_SORT );
280 if ( !in_array( $sort, $validSorts ) ) {
281 $this->loadStatus->warning(
'search-invalid-sort-order', $sort,
282 implode(
', ', $validSorts ) );
283 } elseif ( $sort !== $this->sort ) {
290 # Extract manually requested namespaces
292 if ( $nslist === [] ) {
293 # Fallback to user preference
294 $nslist = $this->searchConfig->userNamespaces( $user );
298 if ( $nslist === [] ) {
299 $profile =
'default';
302 $profile = $request->getVal(
'profile', $profile );
304 if ( $profile ===
null ) {
306 $profile =
'advanced';
307 foreach ( $profiles as $key => $data ) {
308 if ( $nslist === $data[
'namespaces'] && $key !==
'advanced' ) {
312 $this->namespaces = $nslist;
313 } elseif ( $profile ===
'advanced' ) {
314 $this->namespaces = $nslist;
315 } elseif ( isset( $profiles[$profile][
'namespaces'] ) ) {
316 $this->namespaces = $profiles[$profile][
'namespaces'];
319 $this->loadStatus->warning(
'search-unknown-profile', $profile );
320 $profile =
'default';
321 $this->namespaces = $profiles[
'default'][
'namespaces'];
324 $this->fulltext = $request->getVal(
'fulltext' );
325 $this->runSuggestion = (bool)$request->getVal(
'runsuggestion',
'1' );
326 $this->profile = $profile;
336 # If the string cannot be used to create a title
337 if ( Title::newFromText( $term ) ===
null ) {
340 # If there's an exact or very near match, jump right there.
342 ->getNearMatcher( $this->
getConfig() )->getNearMatch( $term );
354 !$this->redirectOnExactMatch()
360 && !(
$title->getNamespace() !==
NS_MAIN && strpos( $term,
':' ) > 0 )
365 return $url ??
$title->getFullUrlForRedirect();
368 private function redirectOnExactMatch() {
369 if ( !$this->getConfig()->
get( MainConfigNames::SearchMatchRedirectPreference ) ) {
371 $defaultOptions = $this->userOptionsManager->getDefaultOptions();
372 return $defaultOptions[
'search-match-redirect'];
375 return $this->userOptionsManager->getOption( $this->
getUser(),
'search-match-redirect' );
383 if ( $this->searchEngineType !==
null ) {
384 $this->
setExtraParam(
'srbackend', $this->searchEngineType );
388 $widgetOptions = $this->
getConfig()->get( MainConfigNames::SpecialSearchFormOptions );
393 $this->languageConverterFactory->getLanguageConverter( $this->getLanguage() ),
395 $this->getSearchProfiles()
398 if ( trim( $term ) ===
'' || $filePrefix === trim( $term ) ) {
400 if ( !$this->
getHookRunner()->onSpecialSearchResultsPrepend( $this, $out, $term ) ) {
401 # Hook requested termination
408 $out->
addHTML( $formWidget->render(
409 $this->profile, $term, 0, 0, $this->offset, $this->isPowerSearch(), $widgetOptions
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;
421 $this->
getHookRunner()->onSpecialSearchSetupEngine( $this, $this->profile, $engine );
422 if ( !$this->
getHookRunner()->onSpecialSearchResultsPrepend( $this, $out, $term ) ) {
423 # Hook requested termination
427 $title = Title::newFromText( $term );
428 $languageConverter = $this->languageConverterFactory->getLanguageConverter( $this->
getContentLanguage() );
429 if ( $languageConverter->hasVariants() ) {
432 $variantTerm = $term;
433 $languageConverter->findVariantLink( $variantTerm,
$title );
436 $showSuggestion =
$title ===
null || !
$title->isKnown();
437 $engine->setShowSuggestion( $showSuggestion );
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 );
447 $titleMatches = $engine->searchTitle( $rewritten );
448 $textMatches = $engine->searchText( $rewritten );
451 if ( $textMatches instanceof
Status ) {
452 $textStatus = $textMatches;
453 $textMatches = $textStatus->getValue();
457 $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0;
458 if ( $titleMatches ) {
459 $titleMatchesNum = $titleMatches->
numRows();
460 $numTitleMatches = $titleMatches->getTotalHits();
462 if ( $textMatches ) {
463 $textMatchesNum = $textMatches->
numRows();
465 if ( $textMatchesNum > 0 ) {
466 $engine->augmentSearchResults( $textMatches );
469 $num = $titleMatchesNum + $textMatchesNum;
470 $totalRes = $numTitleMatches + $numTextMatches;
474 $out->
addHTML( $formWidget->render(
475 $this->profile, $term, $num, $totalRes, $this->offset, $this->isPowerSearch(), $widgetOptions
479 if ( $textMatches ) {
481 $out->
addHTML( $dymWidget->render( $term, $textMatches ) );
484 $hasSearchErrors = $textStatus && $textStatus->getErrors() !== [];
485 $hasOtherResults = $textMatches &&
488 if ( $textMatches && $textMatches->
hasInterwikiResults( ISearchResultSet::SECONDARY_RESULTS ) ) {
489 $out->
addHTML(
'<div class="searchresults mw-searchresults-has-iw">' );
491 $out->
addHTML(
'<div class="searchresults">' );
494 if ( $hasSearchErrors || $this->loadStatus->getErrors() ) {
495 if ( $textStatus ===
null ) {
496 $textStatus = $this->loadStatus;
498 $textStatus->merge( $this->loadStatus );
500 list( $error, $warning ) = $textStatus->splitByErrorType();
501 if ( $error->getErrors() ) {
503 $error->getHTML(
'search-error' )
506 if ( $warning->getErrors() ) {
507 $out->
addHTML( Html::warningBox(
508 $warning->getHTML(
'search-warning' )
514 if ( $num === 0 && !$hasSearchErrors ) {
515 $out->
wrapWikiMsg(
"<p class=\"mw-search-nonefound\">\n$1</p>", [
516 $hasOtherResults ?
'search-nonefound-thiswiki' :
'search-nonefound',
525 $this->
getHookRunner()->onSpecialSearchResults( $term, $titleMatches, $textMatches );
534 if ( $engine->getFeatureData(
'enable-new-crossproject-page' ) !==
false ) {
538 $sidebarResultWidget,
540 $this->interwikiLookup,
541 $engine->getFeatureData(
'show-multimedia-search-results' )
547 $sidebarResultWidget,
549 $this->interwikiLookup
555 $out->
addHTML( $widget->render(
556 $term, $this->offset, $titleMatches, $textMatches
559 $out->
addHTML(
'<div class="mw-search-visualclear"></div>' );
560 $this->prevNextLinks( $totalRes, $textMatches, $term, $out );
565 $this->
getHookRunner()->onSpecialSearchResultsAppend( $this, $out, $term );
579 || ( $titleMatches !==
null && $titleMatches->searchContainedSyntax() )
584 $this->
getOutput()->addHTML(
'<p></p>' );
589 $messageName =
'searchmenu-new-nocreate';
590 $linkClass =
'mw-search-createlink';
592 if ( !
$title->isExternal() ) {
593 if (
$title->isKnown() ) {
594 $messageName =
'searchmenu-exists';
595 $linkClass =
'mw-search-exists';
597 $this->contentHandlerFactory->getContentHandler(
$title->getContentModel() )
598 ->supportsDirectEditing()
599 && $this->getAuthority()->probablyCan(
'edit',
$title )
601 $messageName =
'searchmenu-new';
614 if ( $messageName ) {
615 $this->
getOutput()->wrapWikiMsg(
"<p class=\"$linkClass\">\n$1</p>", $params );
618 $this->
getOutput()->addHTML(
'<p></p>' );
638 if ( strval( $term ) !==
'' ) {
641 ->plaintextParams( $this->
msg(
'searchresults-title' )->plaintextParams( $term )->text() )
642 ->inContentLanguage()->text()
646 if ( $this->mPrefix !==
'' ) {
647 $subtitle = $this->
msg(
'search-filter-title-prefix' )->plaintextParams( $this->mPrefix );
649 unset( $params[
'prefix'] );
656 $subtitle .= Xml::element(
659 'href' => $this->
getPageTitle()->getLocalURL( $params ),
660 'title' => $this->
msg(
'search-filter-title-prefix-reset' )->text(),
662 $this->
msg(
'search-filter-title-prefix-reset' )->text()
669 $out->
addModules(
'mediawiki.special.search' );
671 'mediawiki.special',
'mediawiki.special.search.styles',
'mediawiki.ui',
'mediawiki.ui.button',
672 'mediawiki.ui.input',
'mediawiki.widgets.SearchInputWidget.styles',
682 return $this->profile ===
'advanced';
694 foreach ( $this->searchConfig->searchableNamespaces() as $ns => $name ) {
695 if ( $request->getCheck(
'ns' . $ns ) ) {
713 foreach ( $this->namespaces as $n ) {
717 $opt[
'profile'] = $this->profile;
720 return $opt + $this->extraParams;
732 if ( $user->isRegistered() &&
733 $user->matchEditToken(
734 $request->getVal(
'nsRemember' ),
737 ) && !$this->readOnlyMode->isReadOnly()
741 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
742 $this->userOptionsManager->setOption( $user,
'searchNs' . $n,
false );
746 foreach ( $this->namespaces as $n ) {
747 $this->userOptionsManager->setOption( $user,
'searchNs' . $n,
true );
750 DeferredUpdates::addCallableUpdate(
static function () use ( $user ) {
751 $user->saveSettings();
766 $nsAllSet = array_keys( $this->searchConfig->searchableNamespaces() );
767 $defaultNs = $this->searchConfig->defaultNamespaces();
770 'message' =>
'searchprofile-articles',
771 'tooltip' =>
'searchprofile-articles-tooltip',
772 'namespaces' => $defaultNs,
773 'namespace-messages' => $this->searchConfig->namespacesAsText(
778 'message' =>
'searchprofile-images',
779 'tooltip' =>
'searchprofile-images-tooltip',
783 'message' =>
'searchprofile-everything',
784 'tooltip' =>
'searchprofile-everything-tooltip',
785 'namespaces' => $nsAllSet,
788 'message' =>
'searchprofile-advanced',
789 'tooltip' =>
'searchprofile-advanced-tooltip',
790 'namespaces' => self::NAMESPACES_CURRENT,
794 $this->
getHookRunner()->onSpecialSearchProfiles( $profiles );
796 foreach ( $profiles as &$data ) {
797 if ( !is_array( $data[
'namespaces'] ) ) {
800 sort( $data[
'namespaces'] );
812 if ( $this->searchEngine ===
null ) {
813 $this->searchEngine = $this->searchEngineFactory->create( $this->searchEngineType );
816 return $this->searchEngine;
824 return $this->profile;
832 return $this->namespaces;
845 $this->extraParams[$key] = $value;
857 return $this->mPrefix;
867 if ( $totalRes > $this->limit || $this->offset ) {
870 if ( $textMatches && $textMatches->
getOffset() !==
null ) {
873 $offset = $this->offset;
877 $newSearchTerm = $term;
886 $this->limit + $this->offset >= $totalRes );
887 $out->
addHTML(
"<div class='mw-search-pager-bottom'>{$prevNext}</div>\n" );
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,...
A class containing constants representing the names of configuration variables.
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.
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.