39 protected $limit = 10;
42 protected $offset = 0;
48 protected $searchTerms = [];
51 protected $showSuggestion =
true;
53 private $sort = self::DEFAULT_SORT;
56 protected $features = [];
59 private $hookContainer;
90 return $this->maybePaginate(
function () use ( $term ) {
136 return Status::newGood( [] );
151 return $this->maybePaginate(
function () use ( $term ) {
177 private function maybePaginate( Closure $fn ) {
183 $resultSetOrStatus = $fn();
190 $resultSet = $resultSetOrStatus;
191 } elseif ( $resultSetOrStatus instanceof
Status &&
194 $resultSet = $resultSetOrStatus->getValue();
197 $resultSet->shrink( $this->limit );
200 return $resultSetOrStatus;
211 switch ( $feature ) {
212 case 'search-update':
214 case 'title-suffix-filter':
227 $this->features[$feature] = $data;
238 return $this->features[$feature] ??
null;
251 return MediaWikiServices::getInstance()->getContentLanguage()->segmentByWord( $string );
261 return MediaWikiServices::getInstance()->getTitleMatcher();
272 return MediaWikiServices::getInstance()->getTitleMatcher();
282 return "A-Za-z_'.0-9\\x80-\\xFF\\-";
293 $this->limit = intval( $limit );
294 $this->offset = intval( $offset );
306 $validNs = MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
308 static fn ( $id ) => $id < 0 || isset( $validNs[$id] )
324 $this->showSuggestion = $showSuggestion;
337 return [ self::DEFAULT_SORT ];
349 throw new InvalidArgumentException(
"Invalid sort: $sort. " .
350 "Must be one of: " . implode(
', ', $this->
getValidSorts() ) );
380 $withAllKeyword =
true,
381 $withPrefixSearchExtractNamespaceHook =
false
384 if ( !str_contains( $query,
':' ) ) {
387 $extractedNamespace =
null;
390 if ( $withAllKeyword ) {
393 $allkeywords[] =
wfMessage(
'searchall' )->inContentLanguage()->text() .
":";
395 if ( !in_array(
'all:', $allkeywords ) ) {
396 $allkeywords[] =
'all:';
399 foreach ( $allkeywords as $kw ) {
400 if ( str_starts_with( $query, $kw ) ) {
401 $parsed = substr( $query, strlen( $kw ) );
408 if ( !$allQuery && str_contains( $query,
':' ) ) {
409 $prefix = str_replace(
' ',
'_', substr( $query, 0, strpos( $query,
':' ) ) );
410 $services = MediaWikiServices::getInstance();
411 $index = $services->getContentLanguage()->getNsIndex( $prefix );
412 if ( $index !==
false ) {
413 $extractedNamespace = [ $index ];
414 $parsed = substr( $query, strlen( $prefix ) + 1 );
415 } elseif ( $withPrefixSearchExtractNamespaceHook ) {
418 (
new HookRunner( $services->getHookContainer() ) )
419 ->onPrefixSearchExtractNamespace( $hookNamespaces, $hookQuery );
420 if ( $hookQuery !== $query ) {
421 $parsed = $hookQuery;
422 $extractedNamespace = $hookNamespaces;
431 return [ $parsed, $extractedNamespace ];
444 return [ $contextlines, $contextchars ];
456 public function update( $id, $title, $text ) {
480 public function delete( $id, $title ) {
491 $queryAndNs = self::parseNamespacePrefixes( $search,
false,
true );
492 if ( $queryAndNs !==
false ) {
494 return $queryAndNs[0];
528 $search = trim( $search );
530 if ( !in_array(
NS_SPECIAL, $this->namespaces ) &&
532 $this->namespaces, $search, $this->limit, $results, $this->offset )
551 if ( trim( $search ) ===
'' ) {
567 if ( trim( $search ) ===
'' ) {
573 $fallbackLimit = 1 + $this->limit - $results->getSize();
574 if ( $fallbackLimit > 0 ) {
575 $services = MediaWikiServices::getInstance();
576 $fallbackSearches = $services->getLanguageConverterFactory()
577 ->getLanguageConverter( $services->getContentLanguage() )
578 ->autoConvertToAllVariants( $search );
579 $fallbackSearches = array_diff( array_unique( $fallbackSearches ), [ $search ] );
581 $origLimit = $this->limit;
582 $origOffset = $this->offset;
583 foreach ( $fallbackSearches as $fbs ) {
587 $results->appendAll( $fallbackSearchResult );
588 $fallbackLimit -= $fallbackSearchResult->getSize();
592 if ( $fallbackLimit <= 0 ) {
621 $suggestions->
shrink( $this->limit );
623 $search = trim( $search );
628 $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
629 $linkBatchFactory->newLinkBatch( $suggestedTitles )
630 ->setCaller( __METHOD__ )
637 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
638 $statsFactory->getCounter(
'search_completion_missing_total' )
639 ->incrementBy( $diff );
650 if ( $this->offset === 0 ) {
656 $rescoredResults = $rescorer->rescore( $search, $this->namespaces, $results, $this->limit );
661 $rescoredResults = $results;
664 if ( count( $rescoredResults ) > 0 ) {
665 $found = array_search( $rescoredResults[0], $results );
666 if ( $found ===
false ) {
669 $exactMatch = SearchSuggestion::fromTitle( 0, Title::newFromText( $rescoredResults[0] ) );
670 $suggestions->
prepend( $exactMatch );
671 if ( $rescorer->getReplacedRedirect() !==
null ) {
674 $suggestions->
remove( SearchSuggestion::fromTitle( 0,
675 Title::newFromText( $rescorer->getReplacedRedirect() ) ) );
677 $suggestions->
shrink( $this->limit );
681 $suggestions->
rescore( $found );
695 if ( trim( $search ) ===
'' ) {
712 return $backend->
defaultSearchBackend( $this->namespaces, $search, $this->limit, $this->offset );
757 $models = MediaWikiServices::getInstance()->getContentHandlerFactory()->getContentModels();
759 $seenHandlers =
new SplObjectStorage();
760 foreach ( $models as $model ) {
762 $handler = MediaWikiServices::getInstance()
763 ->getContentHandlerFactory()
764 ->getContentHandler( $model );
770 if ( $seenHandlers->contains( $handler ) ) {
774 $seenHandlers->attach( $handler );
775 $handlerFields = $handler->getFieldsForSearchIndex( $this );
776 foreach ( $handlerFields as $fieldName => $fieldData ) {
777 if ( empty( $fields[$fieldName] ) ) {
778 $fields[$fieldName] = $fieldData;
781 $mergeDef = $fields[$fieldName]->merge( $fieldData );
783 throw new InvalidArgumentException(
"Duplicate field $fieldName for model $model" );
785 $fields[$fieldName] = $mergeDef;
790 $this->
getHookRunner()->onSearchIndexFields( $fields, $this );
800 $this->
getHookRunner()->onSearchResultsAugment( $setAugmentors, $rowAugmentors );
801 if ( !$setAugmentors && !$rowAugmentors ) {
807 foreach ( $rowAugmentors as $name => $row ) {
808 if ( isset( $setAugmentors[$name] ) ) {
809 throw new InvalidArgumentException(
"Both row and set augmentors are defined for $name" );
818 foreach ( $setAugmentors as $name => $augmentor ) {
819 $data = $augmentor->augmentAll( $resultSet );
832 $this->hookContainer = $hookContainer;
833 $this->hookRunner =
new HookRunner( $hookContainer );
843 if ( !$this->hookContainer ) {
847 $this->hookContainer = MediaWikiServices::getInstance()->getHookContainer();
849 return $this->hookContainer;
861 if ( !$this->hookRunner ) {
862 $this->hookRunner =
new HookRunner( $this->getHookContainer() );
864 return $this->hookRunner;
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Exception thrown when an unregistered content model is requested.
Null index field - means search engine does not implement this field.
Perform augmentation of each row and return composite result, indexed by ID.
defaultSearchBackend( $namespaces, $search, $limit, $offset)
Unless overridden by PrefixSearchBackend hook... This is case-sensitive (First character may be autom...
Contain a class for special pages.
completionSearchBackendOverfetch( $search)
Perform an overfetch of completion search results.
makeSearchFieldMapping( $name, $type)
Create a search field definition.
getNearMatcher(Config $config)
Get service class to finding near matches.
getHookRunner()
Get a HookRunner for running core hooks.
searchTitle( $term)
Perform a title-only search query and return a result set.
processCompletionResults( $search, SearchSuggestionSet $suggestions)
Process completion search results.
getFeatureData( $feature)
Way to retrieve custom data set by setFeatureData or by the engine itself.
const FT_QUERY_DEP_PROFILE_TYPE
Profile type for query dependent ranking features (ex: field weights)
update( $id, $title, $text)
Create or update the search index record for the given page.
setNamespaces( $namespaces)
Set which namespaces the search should include.
static parseNamespacePrefixes( $query, $withAllKeyword=true, $withPrefixSearchExtractNamespaceHook=false)
Parse some common prefixes: all (search everything) or namespace names.
doSearchArchiveTitle( $term)
Perform a title search in the article archive.
array $features
Feature values.
defaultPrefixSearch( $search)
Simple prefix search for subpages.
augmentSearchResults(ISearchResultSet $resultSet)
Augment search results with extra data.
searchArchiveTitle( $term)
Perform a title search in the article archive.
normalizeText( $string)
When overridden in derived class, performs database-specific conversions on text to be used for searc...
setFeatureData( $feature, $data)
Way to pass custom data for engines.
completionSearchBackend( $search)
Perform a completion search.
getSort()
Get the sort direction of the search results.
static defaultNearMatcher()
Get near matcher for default SearchEngine.
getSearchIndexFields()
Get fields for search index.
getValidSorts()
Get the valid sort directions.
static userHighlightPrefs()
Find snippet highlight settings for all users.
updateTitle( $id, $title)
Update a search index record's title only.
completionSearchWithVariants( $search)
Perform a completion search with variants.
doSearchText( $term)
Perform a full text search query and return a result set.
normalizeNamespaces( $search)
Makes search simple string if it was namespaced.
const CHARS_ALL
Integer flag for legalSearchChars: includes all chars allowed in a search query.
getHookContainer()
Get a HookContainer, for running extension hooks or for hook metadata.
completionSearch( $search)
Perform a completion search.
setLimitOffset( $limit, $offset=0)
Set the maximum number of results to return and how many to skip before returning the first.
const CHARS_NO_SYNTAX
Integer flag for legalSearchChars: includes all chars allowed in a search term.
setShowSuggestion( $showSuggestion)
Set whether the searcher should try to build a suggestion.
getProfiles( $profileType, ?User $user=null)
Get a list of supported profiles.
simplePrefixSearch( $search)
Call out to simple search backend.
setSort( $sort)
Set the sort direction of the search results.
const FT_QUERY_INDEP_PROFILE_TYPE
Profile type for query independent ranking features (ex: article popularity)
setHookContainer(HookContainer $hookContainer)
searchText( $term)
Perform a full text search query and return a result set.
legalSearchChars( $type=self::CHARS_ALL)
Get chars legal for search.
extractTitles(SearchSuggestionSet $completionResults)
Extract titles from completion results.
const COMPLETION_PROFILE_TYPE
Profile type for completionSearch.
doSearchTitle( $term)
Perform a title-only search query and return a result set.
An utility class to rescore search results by looking for an exact match in the db and add the page f...
const DEFAULT_CONTEXT_LINES
const DEFAULT_CONTEXT_CHARS
A set of search suggestions.
filter( $callback)
Filter the suggestions array.
rescore( $key)
Move the suggestion at index $key to the first position.
shrink( $limit)
Remove any extra elements in the suggestions set.
static fromStrings(array $titles, $hasMoreResults=false)
Builds a new set of suggestion based on a string array.
static fromTitles(array $titles, $hasMoreResults=false)
Builds a new set of suggestion based on a title array.
static emptySuggestionSet()
map( $callback)
Call array_map on the suggestions array.
prepend(SearchSuggestion $suggestion)
Add a new suggestion at the top.
remove(SearchSuggestion $suggestion)
Remove a suggestion from the set.
getSuggestedTitle()
Title object in the case this suggestion is based on a title.
Performs prefix search, returning Title objects.
A set of SearchEngine results.
setAugmentedData( $name, $data)
Sets augmented data for result set.
Marker class for search engines that can handle their own pagination, by reporting in their ISearchRe...