13use InvalidArgumentException;
63 private $hookContainer;
101 return $this->maybePaginate(
function () use ( $term ) {
147 return Status::newGood( [] );
162 return $this->maybePaginate(
function () use ( $term ) {
188 private function maybePaginate( Closure $fn ) {
194 $resultSetOrStatus = $fn();
200 if ( $resultSetOrStatus instanceof ISearchResultSet ) {
201 $resultSet = $resultSetOrStatus;
202 } elseif ( $resultSetOrStatus instanceof Status &&
203 $resultSetOrStatus->getValue() instanceof ISearchResultSet
205 $resultSet = $resultSetOrStatus->getValue();
208 $resultSet->shrink( $this->limit );
211 return $resultSetOrStatus;
222 switch ( $feature ) {
223 case 'search-update':
225 case 'title-suffix-filter':
238 $this->features[$feature] = $data;
249 return $this->features[$feature] ??
null;
293 return "A-Za-z_'.0-9\\x80-\\xFF\\-";
304 $this->limit = intval(
$limit );
305 $this->offset = intval(
$offset );
319 static fn ( $id ) => $id < 0 || isset( $validNs[$id] )
360 throw new InvalidArgumentException(
"Invalid sort: $sort. " .
361 "Must be one of: " . implode(
', ', $this->
getValidSorts() ) );
391 $withAllKeyword =
true,
392 $withPrefixSearchExtractNamespaceHook =
false
395 if ( !str_contains( $query,
':' ) ) {
398 $extractedNamespace =
null;
401 if ( $withAllKeyword ) {
404 $allkeywords[] =
wfMessage(
'searchall' )->inContentLanguage()->text() .
":";
406 if ( !in_array(
'all:', $allkeywords ) ) {
407 $allkeywords[] =
'all:';
410 foreach ( $allkeywords as $kw ) {
411 if ( str_starts_with( $query, $kw ) ) {
412 $parsed = substr( $query, strlen( $kw ) );
419 if ( !$allQuery && str_contains( $query,
':' ) ) {
420 $prefix = str_replace(
' ',
'_', substr( $query, 0, strpos( $query,
':' ) ) );
422 $index = $services->getContentLanguage()->getNsIndex(
$prefix );
423 if ( $index !==
false ) {
424 $extractedNamespace = [ $index ];
425 $parsed = substr( $query, strlen(
$prefix ) + 1 );
426 } elseif ( $withPrefixSearchExtractNamespaceHook ) {
429 (
new HookRunner( $services->getHookContainer() ) )
430 ->onPrefixSearchExtractNamespace( $hookNamespaces, $hookQuery );
431 if ( $hookQuery !== $query ) {
432 $parsed = $hookQuery;
433 $extractedNamespace = $hookNamespaces;
442 return [ $parsed, $extractedNamespace ];
455 return [ $contextlines, $contextchars ];
467 public function update( $id, $title, $text ) {
491 public function delete( $id, $title ) {
503 if ( $queryAndNs !==
false ) {
505 return $queryAndNs[0];
537 $search = trim( $search );
548 if ( trim( $search ) ===
'' ) {
564 if ( trim( $search ) ===
'' ) {
570 $fallbackLimit = 1 + $this->limit - $results->getSize();
571 if ( $fallbackLimit > 0 ) {
573 $fallbackSearches = $services->getLanguageConverterFactory()
574 ->getLanguageConverter( $services->getContentLanguage() )
575 ->autoConvertToAllVariants( $search );
576 $fallbackSearches = array_diff( array_unique( $fallbackSearches ), [ $search ] );
580 foreach ( $fallbackSearches as $fbs ) {
584 $results->appendAll( $fallbackSearchResult );
585 $fallbackLimit -= $fallbackSearchResult->getSize();
589 if ( $fallbackLimit <= 0 ) {
618 $suggestions->
shrink( $this->limit );
620 $search = trim( $search );
626 $linkBatchFactory->newLinkBatch( $suggestedTitles )
627 ->setCaller( __METHOD__ )
635 $statsFactory->getCounter(
'search_completion_missing_total' )
636 ->incrementBy( (
float)$diff );
647 if ( $this->offset === 0 ) {
653 $rescoredResults = $rescorer->rescore( $search, $this->namespaces, $results, $this->limit );
658 $rescoredResults = $results;
661 if ( count( $rescoredResults ) > 0 ) {
662 $found = array_search( $rescoredResults[0], $results );
663 if ( $found ===
false ) {
667 $suggestions->
prepend( $exactMatch );
668 if ( $rescorer->getReplacedRedirect() !==
null ) {
672 Title::newFromText( $rescorer->getReplacedRedirect() ) ) );
674 $suggestions->
shrink( $this->limit );
678 $suggestions->
rescore( $found );
692 if ( trim( $search ) ===
'' ) {
709 return $backend->
defaultSearchBackend( $this->namespaces, $search, $this->limit, $this->offset );
756 $seenHandlers =
new SplObjectStorage();
757 foreach ( $models as $model ) {
760 ->getContentHandlerFactory()
761 ->getContentHandler( $model );
767 if ( $seenHandlers->offsetExists( $handler ) ) {
771 $seenHandlers->offsetSet( $handler );
772 $handlerFields = $handler->getFieldsForSearchIndex( $this );
773 foreach ( $handlerFields as $fieldName => $fieldData ) {
774 if ( empty( $fields[$fieldName] ) ) {
775 $fields[$fieldName] = $fieldData;
778 $mergeDef = $fields[$fieldName]->merge( $fieldData );
780 throw new InvalidArgumentException(
"Duplicate field $fieldName for model $model" );
782 $fields[$fieldName] = $mergeDef;
787 $this->
getHookRunner()->onSearchIndexFields( $fields, $this );
797 $this->
getHookRunner()->onSearchResultsAugment( $setAugmentors, $rowAugmentors );
798 if ( !$setAugmentors && !$rowAugmentors ) {
804 foreach ( $rowAugmentors as $name => $row ) {
805 if ( isset( $setAugmentors[$name] ) ) {
806 throw new InvalidArgumentException(
"Both row and set augmentors are defined for $name" );
815 foreach ( $setAugmentors as $name => $augmentor ) {
816 $data = $augmentor->augmentAll( $resultSet );
829 $this->hookContainer = $hookContainer;
830 $this->hookRunner =
new HookRunner( $hookContainer );
840 if ( !$this->hookContainer ) {
846 return $this->hookContainer;
858 if ( !$this->hookRunner ) {
859 $this->hookRunner =
new HookRunner( $this->getHookContainer() );
861 return $this->hookRunner;
867class_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.