104 private $searchEngineFactory;
110 private $contentHandlerFactory;
113 private $interwikiLookup;
116 private $readOnlyMode;
119 private $userOptionsManager;
122 private $languageConverterFactory;
128 private $thumbnailProvider;
138 private const NAMESPACES_CURRENT =
'sense';
166 parent::__construct(
'Search' );
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;
190 $term = str_replace(
"\n",
" ", $request->getText(
'search' ) );
196 if ( $par !==
null && $par !==
'' && $term ===
'' ) {
197 $query = $request->getValues();
198 unset( $query[
'title'] );
201 $query[
'search'] = str_replace(
'_',
' ', $par );
210 if ( $request->getCheck(
'nsRemember' ) ) {
215 $query = $request->getValues();
216 unset( $query[
'title'], $query[
'nsRemember'] );
221 if ( !$request->getVal(
'fulltext' ) && !$request->getCheck(
'offset' ) ) {
223 if ( $url !==
null ) {
230 $title = Title::newFromText( $term );
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 );
244 $out->
addHTML( $this->showGoogleSearch( $term ) );
261 private function showGoogleSearch( $term ) {
262 return "<fieldset>" .
264 $this->
msg(
'search-external' )->escaped() .
266 "<p class='mw-searchdisabled'>" .
267 $this->
msg(
'searchdisabled' )->escaped() .
270 $this->
msg(
'googlesearch' )->rawParams(
271 htmlspecialchars( $term ),
273 $this->
msg(
'searchbutton' )->escaped()
284 $this->loadStatus =
new Status();
287 $this->searchEngineType = $request->getVal(
'srbackend' );
294 $this->mPrefix = $request->getVal(
'prefix',
'' );
295 if ( $this->mPrefix !==
'' ) {
301 if ( !in_array(
$sort, $validSorts ) ) {
302 $this->loadStatus->warning(
'search-invalid-sort-order',
$sort,
303 implode(
', ', $validSorts ) );
304 } elseif (
$sort !== $this->sort ) {
311 # Extract manually requested namespaces
313 if ( $nslist === [] ) {
314 # Fallback to user preference
315 $nslist = $this->searchConfig->userNamespaces( $user );
319 if ( $nslist === [] ) {
325 if ( $profile ===
null ) {
328 foreach ( $profiles as $key => $data ) {
329 if ( $nslist === $data[
'namespaces'] && $key !==
'advanced' ) {
333 $this->namespaces = $nslist;
334 } elseif (
$profile ===
'advanced' ) {
335 $this->namespaces = $nslist;
336 } elseif ( isset( $profiles[
$profile][
'namespaces'] ) ) {
337 $this->namespaces = $profiles[
$profile][
'namespaces'];
340 $this->loadStatus->warning(
'search-unknown-profile',
$profile );
342 $this->namespaces = $profiles[
'default'][
'namespaces'];
345 $this->fulltext = $request->getVal(
'fulltext' );
346 $this->runSuggestion = (bool)$request->getVal(
'runsuggestion',
'1' );
357 # If the string cannot be used to create a title
358 if ( Title::newFromText( $term ) ===
null ) {
361 # If there's an exact or very near match, jump right there.
362 $title = $this->titleMatcher->getNearMatch( $term );
374 !$this->redirectOnExactMatch()
380 && !(
$title->getNamespace() !==
NS_MAIN && strpos( $term,
':' ) > 0 )
385 return $url ??
$title->getFullUrlForRedirect();
388 private function redirectOnExactMatch() {
389 if ( !$this->
getConfig()->
get( MainConfigNames::SearchMatchRedirectPreference ) ) {
391 $defaultOptions = $this->userOptionsManager->getDefaultOptions();
392 return $defaultOptions[
'search-match-redirect'];
395 return $this->userOptionsManager->getOption( $this->
getUser(),
'search-match-redirect' );
403 if ( $this->searchEngineType !==
null ) {
404 $this->
setExtraParam(
'srbackend', $this->searchEngineType );
408 $widgetOptions = $this->
getConfig()->get( MainConfigNames::SpecialSearchFormOptions );
413 $this->languageConverterFactory->getLanguageConverter( $this->getLanguage() ),
415 $this->getSearchProfiles()
418 if ( trim( $term ) ===
'' || $filePrefix === trim( $term ) ) {
420 if ( !$this->
getHookRunner()->onSpecialSearchResultsPrepend( $this, $out, $term ) ) {
421 # Hook requested termination
428 $out->
addHTML( $formWidget->render(
429 $this->profile, $term, 0, 0, $this->offset, $this->isPowerSearch(), $widgetOptions
435 $engine->setFeatureData(
'rewrite', $this->runSuggestion );
436 $engine->setLimitOffset( $this->limit, $this->offset );
437 $engine->setNamespaces( $this->namespaces );
438 $engine->setSort( $this->sort );
441 $this->
getHookRunner()->onSpecialSearchSetupEngine( $this, $this->profile, $engine );
442 if ( !$this->
getHookRunner()->onSpecialSearchResultsPrepend( $this, $out, $term ) ) {
443 # Hook requested termination
447 $title = Title::newFromText( $term );
448 $languageConverter = $this->languageConverterFactory->getLanguageConverter( $this->
getContentLanguage() );
449 if ( $languageConverter->hasVariants() ) {
452 $variantTerm = $term;
453 $languageConverter->findVariantLink( $variantTerm,
$title );
456 $showSuggestion =
$title ===
null || !
$title->isKnown();
457 $engine->setShowSuggestion( $showSuggestion );
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 );
467 $titleMatches = $engine->searchTitle( $rewritten );
468 $textMatches = $engine->searchText( $rewritten );
471 if ( $textMatches instanceof
Status ) {
472 $textStatus = $textMatches;
473 $textMatches = $textStatus->getValue();
477 $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0;
478 if ( $titleMatches ) {
479 $titleMatchesNum = $titleMatches->
numRows();
480 $numTitleMatches = $titleMatches->getTotalHits();
482 if ( $textMatches ) {
483 $textMatchesNum = $textMatches->
numRows();
485 if ( $textMatchesNum > 0 ) {
486 $engine->augmentSearchResults( $textMatches );
489 $num = $titleMatchesNum + $textMatchesNum;
490 $totalRes = $numTitleMatches + $numTextMatches;
494 $out->
addHTML( $formWidget->render(
495 $this->profile, $term, $num, $totalRes, $this->offset, $this->isPowerSearch(), $widgetOptions
499 if ( $textMatches ) {
501 $out->
addHTML( $dymWidget->render( $term, $textMatches ) );
504 $hasSearchErrors = $textStatus && $textStatus->getErrors() !== [];
505 $hasInlineIwResults = $textMatches &&
507 $hasSecondaryIwResults = $textMatches &&
510 $classNames = [
'searchresults' ];
511 if ( $hasSecondaryIwResults ) {
512 $classNames[] =
'mw-searchresults-has-iw';
514 if ( $this->offset > 0 ) {
515 $classNames[] =
'mw-searchresults-has-offset';
517 $out->
addHTML(
'<div class="' . implode(
' ', $classNames ) .
'">' );
519 $out->
addHTML(
'<div class="mw-search-results-info">' );
521 if ( $hasSearchErrors || $this->loadStatus->getErrors() ) {
522 if ( $textStatus ===
null ) {
523 $textStatus = $this->loadStatus;
525 $textStatus->merge( $this->loadStatus );
527 [ $error, $warning ] = $textStatus->splitByErrorType();
528 if ( $error->getErrors() ) {
530 $error->getHTML(
'search-error' )
533 if ( $warning->getErrors() ) {
534 $out->
addHTML( Html::warningBox(
535 $warning->getHTML(
'search-warning' )
541 if ( $num === 0 && !$hasSearchErrors ) {
542 $out->
wrapWikiMsg(
"<p class=\"mw-search-nonefound\">\n$1</p>", [
543 $hasInlineIwResults ?
'search-nonefound-thiswiki' :
'search-nonefound',
552 $this->
getHookRunner()->onSpecialSearchResults( $term, $titleMatches, $textMatches );
565 $this->thumbnailProvider,
566 $this->userOptionsManager
570 if ( $engine->getFeatureData(
'enable-new-crossproject-page' ) !==
false ) {
574 $sidebarResultWidget,
576 $this->interwikiLookup,
577 $engine->getFeatureData(
'show-multimedia-search-results' )
583 $sidebarResultWidget,
585 $this->interwikiLookup
591 $out->
addHTML( $widget->render(
592 $term, $this->offset, $titleMatches, $textMatches
595 $out->
addHTML(
'<div class="mw-search-visualclear"></div>' );
596 $this->prevNextLinks( $totalRes, $textMatches, $term, $out );
601 $this->
getHookRunner()->onSpecialSearchResultsAppend( $this, $out, $term );
615 || ( $titleMatches !==
null && $titleMatches->searchContainedSyntax() )
620 $this->
getOutput()->addHTML(
'<p></p>' );
625 $messageName =
'searchmenu-new-nocreate';
626 $linkClass =
'mw-search-createlink';
628 if ( !
$title->isExternal() ) {
629 if (
$title->isKnown() ) {
630 $messageName =
'searchmenu-exists';
631 $linkClass =
'mw-search-exists';
633 $this->contentHandlerFactory->getContentHandler(
$title->getContentModel() )
634 ->supportsDirectEditing()
635 && $this->getAuthority()->probablyCan(
'edit',
$title )
637 $messageName =
'searchmenu-new';
650 if ( $messageName ) {
651 $this->
getOutput()->wrapWikiMsg(
"<p class=\"$linkClass\">\n$1</p>", $params );
654 $this->
getOutput()->addHTML(
'<p></p>' );
674 if ( strval( $term ) !==
'' ) {
677 ->plaintextParams( $this->
msg(
'searchresults-title' )->plaintextParams( $term )->text() )
678 ->inContentLanguage()->text()
682 if ( $this->mPrefix !==
'' ) {
683 $subtitle = $this->
msg(
'search-filter-title-prefix' )->plaintextParams( $this->mPrefix );
685 unset( $params[
'prefix'] );
695 'href' => $this->
getPageTitle()->getLocalURL( $params ),
696 'title' => $this->
msg(
'search-filter-title-prefix-reset' )->text(),
698 $this->
msg(
'search-filter-title-prefix-reset' )->text()
705 $out->
addModules(
'mediawiki.special.search' );
707 'mediawiki.special',
'mediawiki.special.search.styles',
'mediawiki.ui',
'mediawiki.ui.button',
708 'mediawiki.ui.input',
'mediawiki.widgets.SearchInputWidget.styles',
718 return $this->profile ===
'advanced';
730 foreach ( $this->searchConfig->searchableNamespaces() as $ns => $name ) {
731 if ( $request->getCheck(
'ns' . $ns ) ) {
749 foreach ( $this->namespaces as $n ) {
768 if ( $user->isRegistered() &&
769 $user->matchEditToken(
770 $request->getVal(
'nsRemember' ),
773 ) && !$this->readOnlyMode->isReadOnly()
777 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
778 $this->userOptionsManager->setOption( $user,
'searchNs' . $n,
false );
782 foreach ( $this->namespaces as $n ) {
783 $this->userOptionsManager->setOption( $user,
'searchNs' . $n,
true );
787 $user->saveSettings();
802 $nsAllSet = array_keys( $this->searchConfig->searchableNamespaces() );
803 $defaultNs = $this->searchConfig->defaultNamespaces();
806 'message' =>
'searchprofile-articles',
807 'tooltip' =>
'searchprofile-articles-tooltip',
808 'namespaces' => $defaultNs,
809 'namespace-messages' => $this->searchConfig->namespacesAsText(
814 'message' =>
'searchprofile-images',
815 'tooltip' =>
'searchprofile-images-tooltip',
819 'message' =>
'searchprofile-everything',
820 'tooltip' =>
'searchprofile-everything-tooltip',
821 'namespaces' => $nsAllSet,
824 'message' =>
'searchprofile-advanced',
825 'tooltip' =>
'searchprofile-advanced-tooltip',
826 'namespaces' => self::NAMESPACES_CURRENT,
830 $this->
getHookRunner()->onSpecialSearchProfiles( $profiles );
832 foreach ( $profiles as &$data ) {
833 if ( !is_array( $data[
'namespaces'] ) ) {
836 sort( $data[
'namespaces'] );
848 if ( $this->searchEngine ===
null ) {
849 $this->searchEngine = $this->searchEngineFactory->create( $this->searchEngineType );
881 $this->extraParams[$key] = $value;
903 if ( $totalRes > $this->limit || $this->offset ) {
906 if ( $textMatches && $textMatches->
getOffset() !==
null ) {
913 $newSearchTerm = $term;
922 $this->limit + $this->offset >= $totalRes );
923 $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,...
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
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.
Prioritized list of file repositories.
Configuration handling class for SearchEngine.
Factory class for SearchEngine.
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.
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.
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.
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
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.
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...
getTotalHits()
Some search modes return a total hit count for the query in the entire article database.