42 use \MediaWiki\Api\SearchApi;
53 parent::__construct( $query, $moduleName,
'sr' );
55 $this->searchEngineConfig = $searchEngineConfig;
56 $this->searchEngineFactory = $searchEngineFactory;
57 $this->titleMatcher = $titleMatcher;
65 $this->
run( $resultPageSet );
72 private function run( $resultPageSet =
null ) {
78 $interwiki =
$params[
'interwiki'];
79 $searchInfo = array_fill_keys(
$params[
'info'],
true );
80 $prop = array_fill_keys(
$params[
'prop'],
true );
84 if ( isset(
$params[
'sort'] ) ) {
85 $search->setSort(
$params[
'sort'] );
87 $search->setFeatureData(
'rewrite', (
bool)
$params[
'enablerewrites'] );
88 $search->setFeatureData(
'interwiki', (
bool)$interwiki );
90 $search->setFeatureData(
'snippets', $this->decideSnippets( $prop ) );
92 $nquery = $search->replacePrefixes( $query );
93 if ( $nquery !== $query ) {
96 get_class( $search ) .
', this was deprecated in MediaWiki 1.32',
100 if ( $what ==
'text' ) {
101 $matches = $search->searchText( $query );
102 } elseif ( $what ==
'title' ) {
103 $matches = $search->searchTitle( $query );
104 } elseif ( $what ==
'nearmatch' ) {
107 $matches = $this->titleMatcher->getNearMatchResultSet(
$params[
'search'] );
114 $matches = $search->searchTitle( $query );
122 $matches = $search->searchText( $query );
134 if ( $status->isOK() ) {
135 $this->
getMain()->getErrorFormatter()->addMessagesFromStatus(
143 $this->
dieWithError( [
'apierror-searchdisabled', $what ],
"search-{$what}-disabled" );
148 if ( isset( $searchInfo[
'totalhits'] ) ) {
149 $totalhits =
$matches->getTotalHits();
150 if ( $totalhits !==
null ) {
151 $apiResult->addValue( [
'query',
'searchinfo' ],
152 'totalhits', $totalhits );
153 if (
$matches->isApproximateTotalHits() ) {
154 $apiResult->addValue( [
'query',
'searchinfo' ],
155 'approximate_totalhits',
$matches->isApproximateTotalHits() );
159 if ( isset( $searchInfo[
'suggestion'] ) &&
$matches->hasSuggestion() ) {
160 $apiResult->addValue( [
'query',
'searchinfo' ],
161 'suggestion',
$matches->getSuggestionQuery() );
162 $apiResult->addValue( [
'query',
'searchinfo' ],
163 'suggestionsnippet', HtmlArmor::getHtml(
$matches->getSuggestionSnippet() ) );
165 if ( isset( $searchInfo[
'rewrittenquery'] ) &&
$matches->hasRewrittenQuery() ) {
166 $apiResult->addValue( [
'query',
'searchinfo' ],
167 'rewrittenquery',
$matches->getQueryAfterRewrite() );
168 $apiResult->addValue( [
'query',
'searchinfo' ],
169 'rewrittenquerysnippet', HtmlArmor::getHtml(
$matches->getQueryAfterRewriteSnippet() ) );
183 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
187 $vals = $this->getSearchResultData( $result, $prop );
189 if ( $resultPageSet ===
null ) {
192 $fit = $apiResult->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
199 $titles[] = $result->getTitle();
200 $data[] = $vals ?: [];
209 $canAddInterwiki = (bool)
$params[
'enablerewrites'] && ( $resultPageSet ===
null );
210 if ( $canAddInterwiki ) {
211 $this->addInterwikiResults(
$matches, $apiResult, $prop,
'additional',
216 if ( $interwiki && $resultPageSet ===
null ) {
217 $this->addInterwikiResults(
$matches, $apiResult, $prop,
'interwiki',
221 if ( $resultPageSet ===
null ) {
222 $apiResult->addIndexedTagName( [
226 $resultPageSet->setRedirectMergePolicy(
static function ( $current, $new ) {
227 if ( !isset( $current[
'index'] ) || $new[
'index'] < $current[
'index'] ) {
228 $current[
'index'] = $new[
'index'];
232 $resultPageSet->populateFromTitles( $titles );
233 $offset =
$params[
'offset'] + 1;
234 foreach ( $titles as $index => $title ) {
235 $resultPageSet->setGeneratorData(
237 $data[ $index ] + [
'index' => $index + $offset ]
249 private function getSearchResultData(
SearchResult $result, $prop ) {
251 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
257 $title = $result->getTitle();
259 $vals[
'pageid'] = $title->getArticleID();
261 if ( isset( $prop[
'size'] ) ) {
262 $vals[
'size'] = $result->getByteSize();
264 if ( isset( $prop[
'wordcount'] ) ) {
265 $vals[
'wordcount'] = $result->getWordCount();
267 if ( isset( $prop[
'snippet'] ) ) {
268 $vals[
'snippet'] = $result->getTextSnippet();
270 if ( isset( $prop[
'timestamp'] ) ) {
271 $vals[
'timestamp'] =
wfTimestamp( TS_ISO_8601, $result->getTimestamp() );
273 if ( isset( $prop[
'titlesnippet'] ) ) {
274 $vals[
'titlesnippet'] = $result->getTitleSnippet();
276 if ( isset( $prop[
'categorysnippet'] ) ) {
277 $vals[
'categorysnippet'] = $result->getCategorySnippet();
279 if ( $result->getRedirectTitle() !==
null ) {
280 if ( isset( $prop[
'redirecttitle'] ) ) {
281 $vals[
'redirecttitle'] = $result->getRedirectTitle()->getPrefixedText();
283 if ( isset( $prop[
'redirectsnippet'] ) ) {
284 $vals[
'redirectsnippet'] = $result->getRedirectSnippet();
287 if ( $result->getSectionTitle() !==
null ) {
288 if ( isset( $prop[
'sectiontitle'] ) ) {
289 $vals[
'sectiontitle'] = $result->getSectionTitle()->getFragment();
291 if ( isset( $prop[
'sectionsnippet'] ) ) {
292 $vals[
'sectionsnippet'] = $result->getSectionSnippet();
295 if ( isset( $prop[
'isfilematch'] ) ) {
296 $vals[
'isfilematch'] = $result->isFileMatch();
299 if ( isset( $prop[
'extensiondata'] ) ) {
300 $extra = $result->getExtensionData();
320 private function addInterwikiResults(
325 $approximateTotalHits =
false;
326 if (
$matches->hasInterwikiResults( $type ) ) {
327 foreach (
$matches->getInterwikiResults( $type ) as $interwikiMatches ) {
329 $interwikiTotalHits = $interwikiMatches->getTotalHits();
330 if ( $interwikiTotalHits !==
null ) {
331 $totalhits += $interwikiTotalHits;
332 $approximateTotalHits = $approximateTotalHits || $interwikiMatches->isApproximateTotalHits();
335 foreach ( $interwikiMatches as $result ) {
336 $title = $result->getTitle();
337 $vals = $this->getSearchResultData( $result, $prop );
339 $vals[
'namespace'] = $result->getInterwikiNamespaceText();
340 $vals[
'title'] = $title->getText();
341 $vals[
'url'] = $title->getFullURL();
344 $fit = $apiResult->addValue( [
347 $result->getInterwikiPrefix()
357 if ( $totalhits !==
null ) {
358 $apiResult->addValue( [
'query', $section .
'searchinfo' ],
'totalhits', $totalhits );
359 if ( $approximateTotalHits ) {
360 $apiResult->addValue( [
'query', $section .
'searchinfo' ],
'approximate_totalhits',
true );
362 $apiResult->addIndexedTagName( [
370 private function decideSnippets( array $prop ): array {
375 if ( isset( $prop[
'titlesnippet'] ) ) {
381 if ( isset( $prop[
'redirectsnippet'] ) || isset( $prop[
'redirecttitle'] ) ) {
382 $fields[] =
'redirect';
384 if ( isset( $prop[
'categorysnippet'] ) ) {
385 $fields[] =
'category';
387 if ( isset( $prop[
'sectionsnippet'] ) || isset( $prop[
'sectiontitle'] ) ) {
388 $fields[] =
'heading';
400 ParamValidator::PARAM_TYPE => [
407 ParamValidator::PARAM_DEFAULT =>
'totalhits|suggestion|rewrittenquery',
408 ParamValidator::PARAM_TYPE => [
413 ParamValidator::PARAM_ISMULTI =>
true,
416 ParamValidator::PARAM_DEFAULT =>
'size|wordcount|timestamp|snippet',
417 ParamValidator::PARAM_TYPE => [
433 ParamValidator::PARAM_ISMULTI =>
true,
435 EnumDef::PARAM_DEPRECATED_VALUES => [
440 'interwiki' =>
false,
441 'enablerewrites' =>
false,
445 if ( $this->isInGeneratorMode() ) {
446 $allowedParams[
'prop'][ParamValidator::PARAM_DEFAULT] =
'';
447 $allowedParams[
'info'][ParamValidator::PARAM_DEFAULT] =
'';
452 $alternatives = $this->searchEngineConfig->getSearchTypes();
453 if ( count( $alternatives ) == 1 ) {
454 $allowedParams[
'sort'] = [
455 ParamValidator::PARAM_DEFAULT => SearchEngine::DEFAULT_SORT,
456 ParamValidator::PARAM_TYPE => $this->searchEngineFactory->create()->getValidSorts(),
460 return $allowedParams;
466 'profile-type' => SearchEngine::FT_QUERY_INDEP_PROFILE_TYPE,
467 'help-message' =>
'apihelp-query+search-param-qiprofile',
474 'action=query&list=search&srsearch=meaning'
475 =>
'apihelp-query+search-example-simple',
476 'action=query&list=search&srwhat=text&srsearch=meaning'
477 =>
'apihelp-query+search-example-text',
478 'action=query&generator=search&gsrsearch=meaning&prop=info'
479 =>
'apihelp-query+search-example-generator',
484 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Search';