13use InvalidArgumentException;
65 private $hookContainer;
106 return $this->maybePaginate(
function () use ( $term ) {
152 return Status::newGood( [] );
167 return $this->maybePaginate(
function () use ( $term ) {
193 private function maybePaginate( Closure $fn ) {
199 $resultSetOrStatus = $fn();
205 if ( $resultSetOrStatus instanceof ISearchResultSet ) {
206 $resultSet = $resultSetOrStatus;
207 } elseif ( $resultSetOrStatus instanceof Status &&
208 $resultSetOrStatus->getValue() instanceof ISearchResultSet
210 $resultSet = $resultSetOrStatus->getValue();
213 $resultSet->shrink( $this->limit );
216 return $resultSetOrStatus;
227 switch ( $feature ) {
228 case 'search-update':
230 case 'title-suffix-filter':
243 $this->features[$feature] = $data;
254 return $this->features[$feature] ??
null;
298 return "A-Za-z_'.0-9\\x80-\\xFF\\-";
309 $this->limit = intval(
$limit );
310 $this->offset = intval(
$offset );
324 static fn ( $id ) => $id < 0 || isset( $validNs[$id] )
365 throw new InvalidArgumentException(
"Invalid sort: $sort. " .
366 "Must be one of: " . implode(
', ', $this->
getValidSorts() ) );
396 $withAllKeyword =
true,
397 $withPrefixSearchExtractNamespaceHook =
false
400 if ( !str_contains( $query,
':' ) ) {
403 $extractedNamespace =
null;
406 if ( $withAllKeyword ) {
409 $allkeywords[] =
wfMessage(
'searchall' )->inContentLanguage()->text() .
":";
411 if ( !in_array(
'all:', $allkeywords ) ) {
412 $allkeywords[] =
'all:';
415 foreach ( $allkeywords as $kw ) {
416 if ( str_starts_with( $query, $kw ) ) {
417 $parsed = substr( $query, strlen( $kw ) );
424 if ( !$allQuery && str_contains( $query,
':' ) ) {
425 $colonPos = strpos( $query,
':' );
428 $prefix = str_replace(
' ',
'_', trim( substr( $query, 0, $colonPos ) ) );
430 $index = $services->getContentLanguage()->getNsIndex(
$prefix );
431 if ( $index !==
false ) {
432 $extractedNamespace = [ $index ];
433 $parsed = ltrim( substr( $query, $colonPos + 1 ) );
434 } elseif ( $withPrefixSearchExtractNamespaceHook ) {
437 (
new HookRunner( $services->getHookContainer() ) )
438 ->onPrefixSearchExtractNamespace( $hookNamespaces, $hookQuery );
439 if ( $hookQuery !== $query ) {
440 $parsed = $hookQuery;
441 $extractedNamespace = $hookNamespaces;
450 return [ $parsed, $extractedNamespace ];
463 return [ $contextlines, $contextchars ];
475 public function update( $id, $title, $text ) {
499 public function delete( $id, $title ) {
511 if ( $queryAndNs !==
false ) {
513 return $queryAndNs[0];
545 $search = trim( $search );
556 if ( trim( $search ) ===
'' ) {
572 if ( trim( $search ) ===
'' ) {
578 $fallbackLimit = 1 + $this->limit - $results->getSize();
579 if ( $fallbackLimit > 0 ) {
581 $fallbackSearches = $services->getLanguageConverterFactory()
582 ->getLanguageConverter( $services->getContentLanguage() )
583 ->autoConvertToAllVariants( $search );
584 $fallbackSearches = array_diff( array_unique( $fallbackSearches ), [ $search ] );
588 foreach ( $fallbackSearches as $fbs ) {
592 $results->appendAll( $fallbackSearchResult );
593 $fallbackLimit -= $fallbackSearchResult->getSize();
597 if ( $fallbackLimit <= 0 ) {
626 $suggestions->
shrink( $this->limit );
628 $search = trim( $search );
634 $linkBatchFactory->newLinkBatch( $suggestedTitles )
635 ->setCaller( __METHOD__ )
643 $statsFactory->getCounter(
'search_completion_missing_total' )
644 ->incrementBy( (
float)$diff );
655 if ( $this->offset === 0 ) {
661 $rescoredResults = $rescorer->rescore( $search, $this->namespaces, $results, $this->limit );
666 $rescoredResults = $results;
669 if ( count( $rescoredResults ) > 0 ) {
670 $found = array_search( $rescoredResults[0], $results );
671 if ( $found ===
false ) {
675 $suggestions->
prepend( $exactMatch );
676 if ( $rescorer->getReplacedRedirect() !==
null ) {
680 Title::newFromText( $rescorer->getReplacedRedirect() ) ) );
682 $suggestions->
shrink( $this->limit );
686 $suggestions->
rescore( $found );
700 if ( trim( $search ) ===
'' ) {
729 if ( $search ===
'' ) {
733 $search = preg_replace( TitleParser::getTitleInvalidRegex(),
'', $search );
735 $title = Title::makeTitleSafe( $namespace, $search );
737 $prefixes[$title->getDBkey()][] = $namespace;
746 $dbr = $services->getConnectionProvider()->getReplicaDatabase();
751 $expr = $dbr->expr(
'page_namespace',
'=',
$namespaces );
762 $queryBuilder = $dbr->newSelectQueryBuilder()
763 ->select( [
'page_id',
'page_namespace',
'page_title' ] )
765 ->where( $dbr->orExpr( $conds ) )
766 ->orderBy( [
'page_title',
'page_namespace' ] )
767 ->limit( $this->limit )
768 ->offset( $this->offset );
769 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
771 return iterator_to_array( $services->getTitleFactory()->newTitleArrayFromResult( $res ) );
818 $seenHandlers =
new SplObjectStorage();
819 foreach ( $models as $model ) {
822 ->getContentHandlerFactory()
823 ->getContentHandler( $model );
829 if ( $seenHandlers->offsetExists( $handler ) ) {
833 $seenHandlers->offsetSet( $handler );
834 $handlerFields = $handler->getFieldsForSearchIndex( $this );
835 foreach ( $handlerFields as $fieldName => $fieldData ) {
836 if ( empty( $fields[$fieldName] ) ) {
837 $fields[$fieldName] = $fieldData;
840 $mergeDef = $fields[$fieldName]->merge( $fieldData );
842 throw new InvalidArgumentException(
"Duplicate field $fieldName for model $model" );
844 $fields[$fieldName] = $mergeDef;
849 $this->
getHookRunner()->onSearchIndexFields( $fields, $this );
859 $this->
getHookRunner()->onSearchResultsAugment( $setAugmentors, $rowAugmentors );
860 if ( !$setAugmentors && !$rowAugmentors ) {
866 foreach ( $rowAugmentors as $name => $row ) {
867 if ( isset( $setAugmentors[$name] ) ) {
868 throw new InvalidArgumentException(
"Both row and set augmentors are defined for $name" );
877 foreach ( $setAugmentors as $name => $augmentor ) {
878 $data = $augmentor->augmentAll( $resultSet );
891 $this->hookContainer = $hookContainer;
892 $this->hookRunner =
new HookRunner( $hookContainer );
902 if ( !$this->hookContainer ) {
908 return $this->hookContainer;
920 if ( !$this->hookRunner ) {
921 $this->hookRunner =
new HookRunner( $this->getHookContainer() );
923 return $this->hookRunner;
933 if ( !$this->specialPageSuggester ) {
935 MediaWikiServices::getInstance()->getSpecialPageFactory(),
936 MediaWikiServices::getInstance()->getContentLanguage(),
939 return $this->specialPageSuggester;
944class_alias( SearchEngine::class,
'SearchEngine' );
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.
Handles searching prefixes of titles and finding any page names that match.