13use InvalidArgumentException;
63 private $hookContainer;
94 return $this->maybePaginate(
function () use ( $term ) {
140 return Status::newGood( [] );
155 return $this->maybePaginate(
function () use ( $term ) {
181 private function maybePaginate( Closure $fn ) {
187 $resultSetOrStatus = $fn();
193 if ( $resultSetOrStatus instanceof ISearchResultSet ) {
194 $resultSet = $resultSetOrStatus;
195 } elseif ( $resultSetOrStatus instanceof Status &&
196 $resultSetOrStatus->getValue() instanceof ISearchResultSet
198 $resultSet = $resultSetOrStatus->getValue();
201 $resultSet->shrink( $this->limit );
204 return $resultSetOrStatus;
215 switch ( $feature ) {
216 case 'search-update':
218 case 'title-suffix-filter':
231 $this->features[$feature] = $data;
242 return $this->features[$feature] ??
null;
286 return "A-Za-z_'.0-9\\x80-\\xFF\\-";
297 $this->limit = intval(
$limit );
298 $this->offset = intval(
$offset );
312 static fn ( $id ) => $id < 0 || isset( $validNs[$id] )
353 throw new InvalidArgumentException(
"Invalid sort: $sort. " .
354 "Must be one of: " . implode(
', ', $this->
getValidSorts() ) );
384 $withAllKeyword =
true,
385 $withPrefixSearchExtractNamespaceHook =
false
388 if ( !str_contains( $query,
':' ) ) {
391 $extractedNamespace =
null;
394 if ( $withAllKeyword ) {
397 $allkeywords[] =
wfMessage(
'searchall' )->inContentLanguage()->text() .
":";
399 if ( !in_array(
'all:', $allkeywords ) ) {
400 $allkeywords[] =
'all:';
403 foreach ( $allkeywords as $kw ) {
404 if ( str_starts_with( $query, $kw ) ) {
405 $parsed = substr( $query, strlen( $kw ) );
412 if ( !$allQuery && str_contains( $query,
':' ) ) {
413 $prefix = str_replace(
' ',
'_', substr( $query, 0, strpos( $query,
':' ) ) );
415 $index = $services->getContentLanguage()->getNsIndex(
$prefix );
416 if ( $index !==
false ) {
417 $extractedNamespace = [ $index ];
418 $parsed = substr( $query, strlen(
$prefix ) + 1 );
419 } elseif ( $withPrefixSearchExtractNamespaceHook ) {
422 (
new HookRunner( $services->getHookContainer() ) )
423 ->onPrefixSearchExtractNamespace( $hookNamespaces, $hookQuery );
424 if ( $hookQuery !== $query ) {
425 $parsed = $hookQuery;
426 $extractedNamespace = $hookNamespaces;
435 return [ $parsed, $extractedNamespace ];
448 return [ $contextlines, $contextchars ];
460 public function update( $id, $title, $text ) {
484 public function delete( $id, $title ) {
496 if ( $queryAndNs !==
false ) {
498 return $queryAndNs[0];
532 $search = trim( $search );
534 if ( !in_array(
NS_SPECIAL, $this->namespaces ) &&
536 $this->namespaces, $search, $this->limit, $results, $this->offset )
555 if ( trim( $search ) ===
'' ) {
571 if ( trim( $search ) ===
'' ) {
577 $fallbackLimit = 1 + $this->limit - $results->getSize();
578 if ( $fallbackLimit > 0 ) {
580 $fallbackSearches = $services->getLanguageConverterFactory()
581 ->getLanguageConverter( $services->getContentLanguage() )
582 ->autoConvertToAllVariants( $search );
583 $fallbackSearches = array_diff( array_unique( $fallbackSearches ), [ $search ] );
587 foreach ( $fallbackSearches as $fbs ) {
591 $results->appendAll( $fallbackSearchResult );
592 $fallbackLimit -= $fallbackSearchResult->getSize();
596 if ( $fallbackLimit <= 0 ) {
625 $suggestions->
shrink( $this->limit );
627 $search = trim( $search );
633 $linkBatchFactory->newLinkBatch( $suggestedTitles )
634 ->setCaller( __METHOD__ )
642 $statsFactory->getCounter(
'search_completion_missing_total' )
643 ->incrementBy( $diff );
654 if ( $this->offset === 0 ) {
660 $rescoredResults = $rescorer->rescore( $search, $this->namespaces, $results, $this->limit );
665 $rescoredResults = $results;
668 if ( count( $rescoredResults ) > 0 ) {
669 $found = array_search( $rescoredResults[0], $results );
670 if ( $found ===
false ) {
674 $suggestions->
prepend( $exactMatch );
675 if ( $rescorer->getReplacedRedirect() !==
null ) {
679 Title::newFromText( $rescorer->getReplacedRedirect() ) ) );
681 $suggestions->
shrink( $this->limit );
685 $suggestions->
rescore( $found );
699 if ( trim( $search ) ===
'' ) {
716 return $backend->
defaultSearchBackend( $this->namespaces, $search, $this->limit, $this->offset );
763 $seenHandlers =
new SplObjectStorage();
764 foreach ( $models as $model ) {
767 ->getContentHandlerFactory()
768 ->getContentHandler( $model );
774 if ( $seenHandlers->offsetExists( $handler ) ) {
778 $seenHandlers->offsetSet( $handler );
779 $handlerFields = $handler->getFieldsForSearchIndex( $this );
780 foreach ( $handlerFields as $fieldName => $fieldData ) {
781 if ( empty( $fields[$fieldName] ) ) {
782 $fields[$fieldName] = $fieldData;
785 $mergeDef = $fields[$fieldName]->merge( $fieldData );
787 throw new InvalidArgumentException(
"Duplicate field $fieldName for model $model" );
789 $fields[$fieldName] = $mergeDef;
794 $this->
getHookRunner()->onSearchIndexFields( $fields, $this );
804 $this->
getHookRunner()->onSearchResultsAugment( $setAugmentors, $rowAugmentors );
805 if ( !$setAugmentors && !$rowAugmentors ) {
811 foreach ( $rowAugmentors as $name => $row ) {
812 if ( isset( $setAugmentors[$name] ) ) {
813 throw new InvalidArgumentException(
"Both row and set augmentors are defined for $name" );
822 foreach ( $setAugmentors as $name => $augmentor ) {
823 $data = $augmentor->augmentAll( $resultSet );
836 $this->hookContainer = $hookContainer;
837 $this->hookRunner =
new HookRunner( $hookContainer );
847 if ( !$this->hookContainer ) {
853 return $this->hookContainer;
865 if ( !$this->hookRunner ) {
866 $this->hookRunner =
new HookRunner( $this->getHookContainer() );
868 return $this->hookRunner;
874class_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.