MediaWiki  master
ApiQuerySearch.php
Go to the documentation of this file.
1 <?php
29  use SearchApi;
30 
32  private $allowedParams;
33 
38  public function __construct( ApiQuery $query, $moduleName ) {
39  parent::__construct( $query, $moduleName, 'sr' );
40  }
41 
42  public function execute() {
43  $this->run();
44  }
45 
46  public function executeGenerator( $resultPageSet ) {
47  $this->run( $resultPageSet );
48  }
49 
54  private function run( $resultPageSet = null ) {
55  $params = $this->extractRequestParams();
56 
57  // Extract parameters
58  $query = $params['search'];
59  $what = $params['what'];
60  $interwiki = $params['interwiki'];
61  $searchInfo = array_flip( $params['info'] );
62  $prop = array_flip( $params['prop'] );
63 
64  // Create search engine instance and set options
65  $search = $this->buildSearchEngine( $params );
66  if ( isset( $params['sort'] ) ) {
67  $search->setSort( $params['sort'] );
68  }
69  $search->setFeatureData( 'rewrite', (bool)$params['enablerewrites'] );
70  $search->setFeatureData( 'interwiki', (bool)$interwiki );
71 
72  $nquery = $search->replacePrefixes( $query );
73  if ( $nquery !== $query ) {
74  $query = $nquery;
75  wfDeprecatedMsg( 'SearchEngine::replacePrefixes() is overridden by ' .
76  get_class( $search ) . ', this was deprecated in MediaWiki 1.32',
77  '1.32' );
78  }
79  // Perform the actual search
80  if ( $what == 'text' ) {
81  $matches = $search->searchText( $query );
82  } elseif ( $what == 'title' ) {
83  $matches = $search->searchTitle( $query );
84  } elseif ( $what == 'nearmatch' ) {
85  // near matches must receive the user input as provided, otherwise
86  // the near matches within namespaces are lost.
87  $matches = $search->getNearMatcher( $this->getConfig() )
88  ->getNearMatchResultSet( $params['search'] );
89  } else {
90  // We default to title searches; this is a terrible legacy
91  // of the way we initially set up the MySQL fulltext-based
92  // search engine with separate title and text fields.
93  // In the future, the default should be for a combined index.
94  $what = 'title';
95  $matches = $search->searchTitle( $query );
96 
97  // Not all search engines support a separate title search,
98  // for instance the Lucene-based engine we use on Wikipedia.
99  // In this case, fall back to full-text search (which will
100  // include titles in it!)
101  if ( $matches === null ) {
102  $what = 'text';
103  $matches = $search->searchText( $query );
104  }
105  }
106 
107  if ( $matches instanceof Status ) {
108  $status = $matches;
109  $matches = $status->getValue();
110  } else {
111  $status = null;
112  }
113 
114  if ( $status ) {
115  if ( $status->isOK() ) {
116  $this->getMain()->getErrorFormatter()->addMessagesFromStatus(
117  $this->getModuleName(),
118  $status
119  );
120  } else {
121  $this->dieStatus( $status );
122  }
123  } elseif ( $matches === null ) {
124  $this->dieWithError( [ 'apierror-searchdisabled', $what ], "search-{$what}-disabled" );
125  }
126 
127  $apiResult = $this->getResult();
128  // Add search meta data to result
129  if ( isset( $searchInfo['totalhits'] ) ) {
130  $totalhits = $matches->getTotalHits();
131  if ( $totalhits !== null ) {
132  $apiResult->addValue( [ 'query', 'searchinfo' ],
133  'totalhits', $totalhits );
134  }
135  }
136  if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) {
137  $apiResult->addValue( [ 'query', 'searchinfo' ],
138  'suggestion', $matches->getSuggestionQuery() );
139  $apiResult->addValue( [ 'query', 'searchinfo' ],
140  'suggestionsnippet', HtmlArmor::getHtml( $matches->getSuggestionSnippet() ) );
141  }
142  if ( isset( $searchInfo['rewrittenquery'] ) && $matches->hasRewrittenQuery() ) {
143  $apiResult->addValue( [ 'query', 'searchinfo' ],
144  'rewrittenquery', $matches->getQueryAfterRewrite() );
145  $apiResult->addValue( [ 'query', 'searchinfo' ],
146  'rewrittenquerysnippet', HtmlArmor::getHtml( $matches->getQueryAfterRewriteSnippet() ) );
147  }
148 
149  $titles = [];
150  $data = [];
151  $count = 0;
152 
153  if ( $matches->hasMoreResults() ) {
154  $this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] );
155  }
156 
157  foreach ( $matches as $result ) {
158  $count++;
159  // Silently skip broken and missing titles
160  if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
161  continue;
162  }
163 
164  $vals = $this->getSearchResultData( $result, $prop );
165 
166  if ( $resultPageSet === null ) {
167  if ( $vals ) {
168  // Add item to results and see whether it fits
169  $fit = $apiResult->addValue( [ 'query', $this->getModuleName() ], null, $vals );
170  if ( !$fit ) {
171  $this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 );
172  break;
173  }
174  }
175  } else {
176  $titles[] = $result->getTitle();
177  $data[] = $vals ?: [];
178  }
179  }
180 
181  // Here we assume interwiki results do not count with
182  // regular search results. We may want to reconsider this
183  // if we ever return a lot of interwiki results or want pagination
184  // for them.
185  // Interwiki results inside main result set
186  $canAddInterwiki = (bool)$params['enablerewrites'] && ( $resultPageSet === null );
187  if ( $canAddInterwiki ) {
188  $this->addInterwikiResults( $matches, $apiResult, $prop, 'additional',
190  }
191 
192  // Interwiki results outside main result set
193  if ( $interwiki && $resultPageSet === null ) {
194  $this->addInterwikiResults( $matches, $apiResult, $prop, 'interwiki',
196  }
197 
198  if ( $resultPageSet === null ) {
199  $apiResult->addIndexedTagName( [
200  'query', $this->getModuleName()
201  ], 'p' );
202  } else {
203  $resultPageSet->setRedirectMergePolicy( static function ( $current, $new ) {
204  if ( !isset( $current['index'] ) || $new['index'] < $current['index'] ) {
205  $current['index'] = $new['index'];
206  }
207  return $current;
208  } );
209  $resultPageSet->populateFromTitles( $titles );
210  $offset = $params['offset'] + 1;
211  foreach ( $titles as $index => $title ) {
212  $resultPageSet->setGeneratorData(
213  $title,
214  $data[ $index ] + [ 'index' => $index + $offset ]
215  );
216  }
217  }
218  }
219 
226  private function getSearchResultData( SearchResult $result, $prop ) {
227  // Silently skip broken and missing titles
228  if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
229  return null;
230  }
231 
232  $vals = [];
233 
234  $title = $result->getTitle();
236  $vals['pageid'] = $title->getArticleID();
237 
238  if ( isset( $prop['size'] ) ) {
239  $vals['size'] = $result->getByteSize();
240  }
241  if ( isset( $prop['wordcount'] ) ) {
242  $vals['wordcount'] = $result->getWordCount();
243  }
244  if ( isset( $prop['snippet'] ) ) {
245  $vals['snippet'] = $result->getTextSnippet();
246  }
247  if ( isset( $prop['timestamp'] ) ) {
248  $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $result->getTimestamp() );
249  }
250  if ( isset( $prop['titlesnippet'] ) ) {
251  $vals['titlesnippet'] = $result->getTitleSnippet();
252  }
253  if ( isset( $prop['categorysnippet'] ) ) {
254  $vals['categorysnippet'] = $result->getCategorySnippet();
255  }
256  if ( $result->getRedirectTitle() !== null ) {
257  if ( isset( $prop['redirecttitle'] ) ) {
258  $vals['redirecttitle'] = $result->getRedirectTitle()->getPrefixedText();
259  }
260  if ( isset( $prop['redirectsnippet'] ) ) {
261  $vals['redirectsnippet'] = $result->getRedirectSnippet();
262  }
263  }
264  if ( $result->getSectionTitle() !== null ) {
265  if ( isset( $prop['sectiontitle'] ) ) {
266  $vals['sectiontitle'] = $result->getSectionTitle()->getFragment();
267  }
268  if ( isset( $prop['sectionsnippet'] ) ) {
269  $vals['sectionsnippet'] = $result->getSectionSnippet();
270  }
271  }
272  if ( isset( $prop['isfilematch'] ) ) {
273  $vals['isfilematch'] = $result->isFileMatch();
274  }
275 
276  if ( isset( $prop['extensiondata'] ) ) {
277  $extra = $result->getExtensionData();
278  // Add augmented data to the result. The data would be organized as a map:
279  // augmentorName => data
280  if ( $extra ) {
281  $vals['extensiondata'] = ApiResult::addMetadataToResultVars( $extra );
282  }
283  }
284 
285  return $vals;
286  }
287 
297  private function addInterwikiResults(
298  ISearchResultSet $matches, ApiResult $apiResult, $prop,
299  $section, $type
300  ) {
301  $totalhits = null;
302  if ( $matches->hasInterwikiResults( $type ) ) {
303  foreach ( $matches->getInterwikiResults( $type ) as $interwikiMatches ) {
304  // Include number of results if requested
305  $totalhits += $interwikiMatches->getTotalHits();
306 
307  foreach ( $interwikiMatches as $result ) {
308  $title = $result->getTitle();
309  $vals = $this->getSearchResultData( $result, $prop );
310 
311  $vals['namespace'] = $result->getInterwikiNamespaceText();
312  $vals['title'] = $title->getText();
313  $vals['url'] = $title->getFullURL();
314 
315  // Add item to results and see whether it fits
316  $fit = $apiResult->addValue( [
317  'query',
318  $section . $this->getModuleName(),
319  $result->getInterwikiPrefix()
320  ], null, $vals );
321 
322  if ( !$fit ) {
323  // We hit the limit. We can't really provide any meaningful
324  // pagination info so just bail out
325  break;
326  }
327  }
328  }
329  if ( $totalhits !== null ) {
330  $apiResult->addValue( [ 'query', $section . 'searchinfo' ], 'totalhits', $totalhits );
331  $apiResult->addIndexedTagName( [
332  'query', $section . $this->getModuleName()
333  ], 'p' );
334  }
335  }
336  return $totalhits;
337  }
338 
339  public function getCacheMode( $params ) {
340  return 'public';
341  }
342 
343  public function getAllowedParams() {
344  if ( $this->allowedParams !== null ) {
345  return $this->allowedParams;
346  }
347 
348  $this->allowedParams = $this->buildCommonApiParams() + [
349  'what' => [
351  'title',
352  'text',
353  'nearmatch',
354  ]
355  ],
356  'info' => [
357  ApiBase::PARAM_DFLT => 'totalhits|suggestion|rewrittenquery',
359  'totalhits',
360  'suggestion',
361  'rewrittenquery',
362  ],
363  ApiBase::PARAM_ISMULTI => true,
364  ],
365  'prop' => [
366  ApiBase::PARAM_DFLT => 'size|wordcount|timestamp|snippet',
368  'size',
369  'wordcount',
370  'timestamp',
371  'snippet',
372  'titlesnippet',
373  'redirecttitle',
374  'redirectsnippet',
375  'sectiontitle',
376  'sectionsnippet',
377  'isfilematch',
378  'categorysnippet',
379  'score', // deprecated
380  'hasrelated', // deprecated
381  'extensiondata',
382  ],
383  ApiBase::PARAM_ISMULTI => true,
386  'score' => true,
387  'hasrelated' => true
388  ],
389  ],
390  'interwiki' => false,
391  'enablerewrites' => false,
392  ];
393 
394  // Generators only add info/properties if explicitly requested. T263841
395  if ( $this->isInGeneratorMode() ) {
396  $this->allowedParams['prop'][ApiBase::PARAM_DFLT] = '';
397  $this->allowedParams['info'][ApiBase::PARAM_DFLT] = '';
398  }
399 
400  // If we have more than one engine the list of available sorts is
401  // difficult to represent. For now don't expose it.
403  $alternatives = $services
404  ->getSearchEngineConfig()
405  ->getSearchTypes();
406  if ( count( $alternatives ) == 1 ) {
407  $this->allowedParams['sort'] = [
408  ApiBase::PARAM_DFLT => 'relevance',
409  ApiBase::PARAM_TYPE => $services
410  ->newSearchEngine()
411  ->getValidSorts(),
412  ];
413  }
414 
415  return $this->allowedParams;
416  }
417 
418  public function getSearchProfileParams() {
419  return [
420  'qiprofile' => [
422  'help-message' => 'apihelp-query+search-param-qiprofile',
423  ],
424  ];
425  }
426 
427  protected function getExamplesMessages() {
428  return [
429  'action=query&list=search&srsearch=meaning'
430  => 'apihelp-query+search-example-simple',
431  'action=query&list=search&srwhat=text&srsearch=meaning'
432  => 'apihelp-query+search-example-text',
433  'action=query&generator=search&gsrsearch=meaning&prop=info'
434  => 'apihelp-query+search-example-generator',
435  ];
436  }
437 
438  public function getHelpUrls() {
439  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Search';
440  }
441 }
ApiQuerySearch\run
run( $resultPageSet=null)
Definition: ApiQuerySearch.php:54
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:71
ApiResult\addIndexedTagName
addIndexedTagName( $path, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:620
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:1384
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:1832
ApiBase\PARAM_TYPE
const PARAM_TYPE
Definition: ApiBase.php:72
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:561
SearchEngine\FT_QUERY_INDEP_PROFILE_TYPE
const FT_QUERY_INDEP_PROFILE_TYPE
Profile type for query independent ranking features.
Definition: SearchEngine.php:75
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:297
ApiBase\PARAM_DEPRECATED_VALUES
const PARAM_DEPRECATED_VALUES
Definition: ApiBase.php:84
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:46
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:234
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:343
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1066
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:1143
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:153
ApiQuerySearch\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiQuerySearch.php:42
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:720
ApiQuerySearch\getSearchProfileParams
getSearchProfileParams()
Definition: ApiQuerySearch.php:418
$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:397
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\getCacheMode
getCacheMode( $params)
Get the cache mode for the data generated by this module.
Definition: ApiQuerySearch.php:339
ApiQueryGeneratorBase
Definition: ApiQueryGeneratorBase.php:28
ApiQuerySearch
Query module to perform full text search within wiki titles and content.
Definition: ApiQuerySearch.php:28
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\__construct
__construct(ApiQuery $query, $moduleName)
Definition: ApiQuerySearch.php:38
ApiQuerySearch\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiQuerySearch.php:427
ApiQuerySearch\getSearchResultData
getSearchResultData(SearchResult $result, $prop)
Assemble search result data.
Definition: ApiQuerySearch.php:226
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:438
SearchApi
trait SearchApi
Traits for API components that use a SearchEngine.
Definition: SearchApi.php:29
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:47
$type
$type
Definition: testCompression.php:52