MediaWiki  master
ApiQuerySearch.php
Go to the documentation of this file.
1 <?php
29  use SearchApi;
30 
32  private $allowedParams;
33 
36 
39 
46  public function __construct(
47  ApiQuery $query,
48  $moduleName,
51  ) {
52  parent::__construct( $query, $moduleName, 'sr' );
53  // Services also needed in SearchApi trait
54  $this->searchEngineConfig = $searchEngineConfig;
55  $this->searchEngineFactory = $searchEngineFactory;
56  }
57 
58  public function execute() {
59  $this->run();
60  }
61 
62  public function executeGenerator( $resultPageSet ) {
63  $this->run( $resultPageSet );
64  }
65 
70  private function run( $resultPageSet = null ) {
71  $params = $this->extractRequestParams();
72 
73  // Extract parameters
74  $query = $params['search'];
75  $what = $params['what'];
76  $interwiki = $params['interwiki'];
77  $searchInfo = array_fill_keys( $params['info'], true );
78  $prop = array_fill_keys( $params['prop'], true );
79 
80  // Create search engine instance and set options
81  $search = $this->buildSearchEngine( $params );
82  if ( isset( $params['sort'] ) ) {
83  $search->setSort( $params['sort'] );
84  }
85  $search->setFeatureData( 'rewrite', (bool)$params['enablerewrites'] );
86  $search->setFeatureData( 'interwiki', (bool)$interwiki );
87 
88  $nquery = $search->replacePrefixes( $query );
89  if ( $nquery !== $query ) {
90  $query = $nquery;
91  wfDeprecatedMsg( 'SearchEngine::replacePrefixes() is overridden by ' .
92  get_class( $search ) . ', this was deprecated in MediaWiki 1.32',
93  '1.32' );
94  }
95  // Perform the actual search
96  if ( $what == 'text' ) {
97  $matches = $search->searchText( $query );
98  } elseif ( $what == 'title' ) {
99  $matches = $search->searchTitle( $query );
100  } elseif ( $what == 'nearmatch' ) {
101  // near matches must receive the user input as provided, otherwise
102  // the near matches within namespaces are lost.
103  $matches = $search->getNearMatcher( $this->getConfig() )
104  ->getNearMatchResultSet( $params['search'] );
105  } else {
106  // We default to title searches; this is a terrible legacy
107  // of the way we initially set up the MySQL fulltext-based
108  // search engine with separate title and text fields.
109  // In the future, the default should be for a combined index.
110  $what = 'title';
111  $matches = $search->searchTitle( $query );
112 
113  // Not all search engines support a separate title search,
114  // for instance the Lucene-based engine we use on Wikipedia.
115  // In this case, fall back to full-text search (which will
116  // include titles in it!)
117  if ( $matches === null ) {
118  $what = 'text';
119  $matches = $search->searchText( $query );
120  }
121  }
122 
123  if ( $matches instanceof Status ) {
124  $status = $matches;
125  $matches = $status->getValue();
126  } else {
127  $status = null;
128  }
129 
130  if ( $status ) {
131  if ( $status->isOK() ) {
132  $this->getMain()->getErrorFormatter()->addMessagesFromStatus(
133  $this->getModuleName(),
134  $status
135  );
136  } else {
137  $this->dieStatus( $status );
138  }
139  } elseif ( $matches === null ) {
140  $this->dieWithError( [ 'apierror-searchdisabled', $what ], "search-{$what}-disabled" );
141  }
142 
143  $apiResult = $this->getResult();
144  // Add search meta data to result
145  if ( isset( $searchInfo['totalhits'] ) ) {
146  $totalhits = $matches->getTotalHits();
147  if ( $totalhits !== null ) {
148  $apiResult->addValue( [ 'query', 'searchinfo' ],
149  'totalhits', $totalhits );
150  }
151  }
152  if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) {
153  $apiResult->addValue( [ 'query', 'searchinfo' ],
154  'suggestion', $matches->getSuggestionQuery() );
155  $apiResult->addValue( [ 'query', 'searchinfo' ],
156  'suggestionsnippet', HtmlArmor::getHtml( $matches->getSuggestionSnippet() ) );
157  }
158  if ( isset( $searchInfo['rewrittenquery'] ) && $matches->hasRewrittenQuery() ) {
159  $apiResult->addValue( [ 'query', 'searchinfo' ],
160  'rewrittenquery', $matches->getQueryAfterRewrite() );
161  $apiResult->addValue( [ 'query', 'searchinfo' ],
162  'rewrittenquerysnippet', HtmlArmor::getHtml( $matches->getQueryAfterRewriteSnippet() ) );
163  }
164 
165  $titles = [];
166  $data = [];
167  $count = 0;
168 
169  if ( $matches->hasMoreResults() ) {
170  $this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] );
171  }
172 
173  foreach ( $matches as $result ) {
174  $count++;
175  // Silently skip broken and missing titles
176  if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
177  continue;
178  }
179 
180  $vals = $this->getSearchResultData( $result, $prop );
181 
182  if ( $resultPageSet === null ) {
183  if ( $vals ) {
184  // Add item to results and see whether it fits
185  $fit = $apiResult->addValue( [ 'query', $this->getModuleName() ], null, $vals );
186  if ( !$fit ) {
187  $this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 );
188  break;
189  }
190  }
191  } else {
192  $titles[] = $result->getTitle();
193  $data[] = $vals ?: [];
194  }
195  }
196 
197  // Here we assume interwiki results do not count with
198  // regular search results. We may want to reconsider this
199  // if we ever return a lot of interwiki results or want pagination
200  // for them.
201  // Interwiki results inside main result set
202  $canAddInterwiki = (bool)$params['enablerewrites'] && ( $resultPageSet === null );
203  if ( $canAddInterwiki ) {
204  $this->addInterwikiResults( $matches, $apiResult, $prop, 'additional',
206  }
207 
208  // Interwiki results outside main result set
209  if ( $interwiki && $resultPageSet === null ) {
210  $this->addInterwikiResults( $matches, $apiResult, $prop, 'interwiki',
212  }
213 
214  if ( $resultPageSet === null ) {
215  $apiResult->addIndexedTagName( [
216  'query', $this->getModuleName()
217  ], 'p' );
218  } else {
219  $resultPageSet->setRedirectMergePolicy( static function ( $current, $new ) {
220  if ( !isset( $current['index'] ) || $new['index'] < $current['index'] ) {
221  $current['index'] = $new['index'];
222  }
223  return $current;
224  } );
225  $resultPageSet->populateFromTitles( $titles );
226  $offset = $params['offset'] + 1;
227  foreach ( $titles as $index => $title ) {
228  $resultPageSet->setGeneratorData(
229  $title,
230  $data[ $index ] + [ 'index' => $index + $offset ]
231  );
232  }
233  }
234  }
235 
242  private function getSearchResultData( SearchResult $result, $prop ) {
243  // Silently skip broken and missing titles
244  if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
245  return null;
246  }
247 
248  $vals = [];
249 
250  $title = $result->getTitle();
252  $vals['pageid'] = $title->getArticleID();
253 
254  if ( isset( $prop['size'] ) ) {
255  $vals['size'] = $result->getByteSize();
256  }
257  if ( isset( $prop['wordcount'] ) ) {
258  $vals['wordcount'] = $result->getWordCount();
259  }
260  if ( isset( $prop['snippet'] ) ) {
261  $vals['snippet'] = $result->getTextSnippet();
262  }
263  if ( isset( $prop['timestamp'] ) ) {
264  $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $result->getTimestamp() );
265  }
266  if ( isset( $prop['titlesnippet'] ) ) {
267  $vals['titlesnippet'] = $result->getTitleSnippet();
268  }
269  if ( isset( $prop['categorysnippet'] ) ) {
270  $vals['categorysnippet'] = $result->getCategorySnippet();
271  }
272  if ( $result->getRedirectTitle() !== null ) {
273  if ( isset( $prop['redirecttitle'] ) ) {
274  $vals['redirecttitle'] = $result->getRedirectTitle()->getPrefixedText();
275  }
276  if ( isset( $prop['redirectsnippet'] ) ) {
277  $vals['redirectsnippet'] = $result->getRedirectSnippet();
278  }
279  }
280  if ( $result->getSectionTitle() !== null ) {
281  if ( isset( $prop['sectiontitle'] ) ) {
282  $vals['sectiontitle'] = $result->getSectionTitle()->getFragment();
283  }
284  if ( isset( $prop['sectionsnippet'] ) ) {
285  $vals['sectionsnippet'] = $result->getSectionSnippet();
286  }
287  }
288  if ( isset( $prop['isfilematch'] ) ) {
289  $vals['isfilematch'] = $result->isFileMatch();
290  }
291 
292  if ( isset( $prop['extensiondata'] ) ) {
293  $extra = $result->getExtensionData();
294  // Add augmented data to the result. The data would be organized as a map:
295  // augmentorName => data
296  if ( $extra ) {
297  $vals['extensiondata'] = ApiResult::addMetadataToResultVars( $extra );
298  }
299  }
300 
301  return $vals;
302  }
303 
313  private function addInterwikiResults(
314  ISearchResultSet $matches, ApiResult $apiResult, $prop,
315  $section, $type
316  ) {
317  $totalhits = null;
318  if ( $matches->hasInterwikiResults( $type ) ) {
319  foreach ( $matches->getInterwikiResults( $type ) as $interwikiMatches ) {
320  // Include number of results if requested
321  $totalhits += $interwikiMatches->getTotalHits();
322 
323  foreach ( $interwikiMatches as $result ) {
324  $title = $result->getTitle();
325  $vals = $this->getSearchResultData( $result, $prop );
326 
327  $vals['namespace'] = $result->getInterwikiNamespaceText();
328  $vals['title'] = $title->getText();
329  $vals['url'] = $title->getFullURL();
330 
331  // Add item to results and see whether it fits
332  $fit = $apiResult->addValue( [
333  'query',
334  $section . $this->getModuleName(),
335  $result->getInterwikiPrefix()
336  ], null, $vals );
337 
338  if ( !$fit ) {
339  // We hit the limit. We can't really provide any meaningful
340  // pagination info so just bail out
341  break;
342  }
343  }
344  }
345  if ( $totalhits !== null ) {
346  $apiResult->addValue( [ 'query', $section . 'searchinfo' ], 'totalhits', $totalhits );
347  $apiResult->addIndexedTagName( [
348  'query', $section . $this->getModuleName()
349  ], 'p' );
350  }
351  }
352  return $totalhits;
353  }
354 
355  public function getCacheMode( $params ) {
356  return 'public';
357  }
358 
359  public function getAllowedParams() {
360  if ( $this->allowedParams !== null ) {
361  return $this->allowedParams;
362  }
363 
364  $this->allowedParams = $this->buildCommonApiParams() + [
365  'what' => [
367  'title',
368  'text',
369  'nearmatch',
370  ]
371  ],
372  'info' => [
373  ApiBase::PARAM_DFLT => 'totalhits|suggestion|rewrittenquery',
375  'totalhits',
376  'suggestion',
377  'rewrittenquery',
378  ],
379  ApiBase::PARAM_ISMULTI => true,
380  ],
381  'prop' => [
382  ApiBase::PARAM_DFLT => 'size|wordcount|timestamp|snippet',
384  'size',
385  'wordcount',
386  'timestamp',
387  'snippet',
388  'titlesnippet',
389  'redirecttitle',
390  'redirectsnippet',
391  'sectiontitle',
392  'sectionsnippet',
393  'isfilematch',
394  'categorysnippet',
395  'score', // deprecated
396  'hasrelated', // deprecated
397  'extensiondata',
398  ],
399  ApiBase::PARAM_ISMULTI => true,
402  'score' => true,
403  'hasrelated' => true
404  ],
405  ],
406  'interwiki' => false,
407  'enablerewrites' => false,
408  ];
409 
410  // Generators only add info/properties if explicitly requested. T263841
411  if ( $this->isInGeneratorMode() ) {
412  $this->allowedParams['prop'][ApiBase::PARAM_DFLT] = '';
413  $this->allowedParams['info'][ApiBase::PARAM_DFLT] = '';
414  }
415 
416  // If we have more than one engine the list of available sorts is
417  // difficult to represent. For now don't expose it.
418  $alternatives = $this->searchEngineConfig->getSearchTypes();
419  if ( count( $alternatives ) == 1 ) {
420  $this->allowedParams['sort'] = [
422  ApiBase::PARAM_TYPE => $this->searchEngineFactory->create()->getValidSorts(),
423  ];
424  }
425 
426  return $this->allowedParams;
427  }
428 
429  public function getSearchProfileParams() {
430  return [
431  'qiprofile' => [
433  'help-message' => 'apihelp-query+search-param-qiprofile',
434  ],
435  ];
436  }
437 
438  protected function getExamplesMessages() {
439  return [
440  'action=query&list=search&srsearch=meaning'
441  => 'apihelp-query+search-example-simple',
442  'action=query&list=search&srwhat=text&srsearch=meaning'
443  => 'apihelp-query+search-example-text',
444  'action=query&generator=search&gsrsearch=meaning&prop=info'
445  => 'apihelp-query+search-example-generator',
446  ];
447  }
448 
449  public function getHelpUrls() {
450  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Search';
451  }
452 }
ApiQuerySearch\run
run( $resultPageSet=null)
Definition: ApiQuerySearch.php:70
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:72
ApiResult\addIndexedTagName
addIndexedTagName( $path, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:616
ApiQuery
This is the main query class.
Definition: ApiQuery.php:37
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1379
true
return true
Definition: router.php:90
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1691
ApiBase\PARAM_TYPE
const PARAM_TYPE
Definition: ApiBase.php:72
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:571
SearchEngine\FT_QUERY_INDEP_PROFILE_TYPE
const FT_QUERY_INDEP_PROFILE_TYPE
Profile type for query independent ranking features.
Definition: SearchEngine.php:75
SearchEngineFactory
Factory class for SearchEngine.
Definition: SearchEngineFactory.php:12
ApiQuerySearch\$allowedParams
array $allowedParams
list of api allowed params
Definition: ApiQuerySearch.php:32
ApiQuerySearch\addInterwikiResults
addInterwikiResults(ISearchResultSet $matches, ApiResult $apiResult, $prop, $section, $type)
Add interwiki results as a section in query results.
Definition: ApiQuerySearch.php:313
ApiBase\PARAM_DEPRECATED_VALUES
const PARAM_DEPRECATED_VALUES
Definition: ApiBase.php:84
SearchEngine\DEFAULT_SORT
const DEFAULT_SORT
Definition: SearchEngine.php:38
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
ApiQuerySearch\executeGenerator
executeGenerator( $resultPageSet)
Execute this module as a generator.
Definition: ApiQuerySearch.php:62
ApiQueryGeneratorBase\setContinueEnumParameter
setContinueEnumParameter( $paramName, $paramValue)
Overridden to set the generator param if in generator mode.
Definition: ApiQueryGeneratorBase.php:83
ApiQueryGeneratorBase\isInGeneratorMode
isInGeneratorMode()
Indicate whether the module is in generator mode.
Definition: ApiQueryGeneratorBase.php:48
ApiQuerySearch\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiQuerySearch.php:359
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1028
SearchResult
NOTE: this class is being refactored into an abstract base class.
Definition: SearchResult.php:38
ApiResult\addMetadataToResultVars
static addMetadataToResultVars( $vars, $forceHash=true)
Add the correct metadata to an array of vars we want to export through the API.
Definition: ApiResult.php:1139
ApiResult
This class represents the result of the API operations.
Definition: ApiResult.php:35
HtmlArmor\getHtml
static getHtml( $input)
Provide a string or HtmlArmor object and get safe HTML back.
Definition: HtmlArmor.php:54
$matches
$matches
Definition: NoLocalSettings.php:24
buildSearchEngine
buildSearchEngine(array $params=null)
Build the search engine to use.
Definition: SearchApi.php:174
ApiQuerySearch\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiQuerySearch.php:58
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:707
ApiQuerySearch\$searchEngineFactory
SearchEngineFactory $searchEngineFactory
Definition: ApiQuerySearch.php:38
ApiQuerySearch\getSearchProfileParams
getSearchProfileParams()
Definition: ApiQuerySearch.php:429
$title
$title
Definition: testCompression.php:38
ISearchResultSet
A set of SearchEngine results.
Definition: ISearchResultSet.php:12
ApiResult\addValue
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
Definition: ApiResult.php:393
ISearchResultSet\INLINE_RESULTS
const INLINE_RESULTS
Identifier for interwiki results that can be displayed even if no existing main wiki results exist.
Definition: ISearchResultSet.php:23
ApiQuerySearch\$searchEngineConfig
SearchEngineConfig $searchEngineConfig
Definition: ApiQuerySearch.php:35
ApiQuerySearch\getCacheMode
getCacheMode( $params)
Get the cache mode for the data generated by this module.
Definition: ApiQuerySearch.php:355
ApiQueryGeneratorBase
Definition: ApiQueryGeneratorBase.php:28
ApiQuerySearch
Query module to perform full text search within wiki titles and content.
Definition: ApiQuerySearch.php:28
ApiQuerySearch\__construct
__construct(ApiQuery $query, $moduleName, SearchEngineConfig $searchEngineConfig, SearchEngineFactory $searchEngineFactory)
Definition: ApiQuerySearch.php:46
ApiBase\PARAM_DFLT
const PARAM_DFLT
Definition: ApiBase.php:70
ApiBase\dieStatus
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:1442
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:440
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
Definition: ApiBase.php:71
ISearchResultSet\SECONDARY_RESULTS
const SECONDARY_RESULTS
Identifier for interwiki results that are displayed only together with existing main wiki results.
Definition: ISearchResultSet.php:17
ApiQuerySearch\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiQuerySearch.php:438
SearchEngineConfig
Configuration handling class for SearchEngine.
Definition: SearchEngineConfig.php:12
ApiQuerySearch\getSearchResultData
getSearchResultData(SearchResult $result, $prop)
Assemble search result data.
Definition: ApiQuerySearch.php:242
ApiBase\getMain
getMain()
Get the main module.
Definition: ApiBase.php:456
ApiBase\PARAM_HELP_MSG_PER_VALUE
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition: ApiBase.php:138
ApiQuerySearch\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiQuerySearch.php:449
SearchApi
trait SearchApi
Traits for API components that use a SearchEngine.
Definition: SearchApi.php:27
ApiQueryBase\addTitleInfo
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
Definition: ApiQueryBase.php:466
buildCommonApiParams
buildCommonApiParams( $isScrollable=true)
The set of api parameters that are shared between api calls that call the SearchEngine.
Definition: SearchApi.php:63
$type
$type
Definition: testCompression.php:52