38 parent::__construct( $query, $moduleName,
'sr' );
40 $this->searchEngineConfig = $searchEngineConfig;
41 $this->searchEngineFactory = $searchEngineFactory;
50 $this->run( $resultPageSet );
57 private function run( $resultPageSet =
null ) {
61 $query = $params[
'search'];
62 $what = $params[
'what'];
63 $interwiki = $params[
'interwiki'];
64 $searchInfo = array_fill_keys( $params[
'info'],
true );
65 $prop = array_fill_keys( $params[
'prop'],
true );
69 if ( isset( $params[
'sort'] ) ) {
70 $search->setSort( $params[
'sort'] );
72 $search->setFeatureData(
'rewrite', (
bool)$params[
'enablerewrites'] );
73 $search->setFeatureData(
'interwiki', (
bool)$interwiki );
75 $search->setFeatureData(
'snippets', $this->decideSnippets( $prop ) );
78 if ( $what ==
'text' ) {
79 $matches = $search->searchText( $query );
80 } elseif ( $what ==
'title' ) {
81 $matches = $search->searchTitle( $query );
82 } elseif ( $what ==
'nearmatch' ) {
85 $matches = $this->titleMatcher->getNearMatchResultSet( $params[
'search'] );
92 $matches = $search->searchTitle( $query );
100 $matches = $search->searchText( $query );
112 if ( $status->isOK() ) {
113 $this->
getMain()->getErrorFormatter()->addMessagesFromStatus(
121 $this->
dieWithError( [
'apierror-searchdisabled', $what ],
"search-{$what}-disabled" );
126 if ( isset( $searchInfo[
'totalhits'] ) ) {
127 $totalhits =
$matches->getTotalHits();
128 if ( $totalhits !==
null ) {
129 $apiResult->addValue( [
'query',
'searchinfo' ],
130 'totalhits', $totalhits );
131 if (
$matches->isApproximateTotalHits() ) {
132 $apiResult->addValue( [
'query',
'searchinfo' ],
133 'approximate_totalhits',
$matches->isApproximateTotalHits() );
137 if ( isset( $searchInfo[
'suggestion'] ) &&
$matches->hasSuggestion() ) {
138 $apiResult->addValue( [
'query',
'searchinfo' ],
139 'suggestion',
$matches->getSuggestionQuery() );
140 $apiResult->addValue( [
'query',
'searchinfo' ],
141 'suggestionsnippet', HtmlArmor::getHtml(
$matches->getSuggestionSnippet() ) );
143 if ( isset( $searchInfo[
'rewrittenquery'] ) &&
$matches->hasRewrittenQuery() ) {
144 $apiResult->addValue( [
'query',
'searchinfo' ],
145 'rewrittenquery',
$matches->getQueryAfterRewrite() );
146 $apiResult->addValue( [
'query',
'searchinfo' ],
147 'rewrittenquerysnippet', HtmlArmor::getHtml(
$matches->getQueryAfterRewriteSnippet() ) );
161 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
165 $vals = $this->getSearchResultData( $result, $prop );
167 if ( $resultPageSet ===
null ) {
170 $fit = $apiResult->addValue( [
'query', $this->
getModuleName() ],
null, $vals );
177 $titles[] = $result->getTitle();
178 $data[] = $vals ?: [];
187 $canAddInterwiki = (bool)$params[
'enablerewrites'] && ( $resultPageSet ===
null );
188 if ( $canAddInterwiki ) {
190 $this->addInterwikiResults(
$matches, $apiResult, $prop,
'additional',
195 if ( $interwiki && $resultPageSet ===
null ) {
197 $this->addInterwikiResults(
$matches, $apiResult, $prop,
'interwiki',
201 if ( $resultPageSet ===
null ) {
202 $apiResult->addIndexedTagName( [
206 $resultPageSet->setRedirectMergePolicy(
static function ( $current, $new ) {
207 if ( !isset( $current[
'index'] ) || $new[
'index'] < $current[
'index'] ) {
208 $current[
'index'] = $new[
'index'];
212 $resultPageSet->populateFromTitles( $titles );
213 $offset =
$params[
'offset'] + 1;
214 foreach ( $titles as $index => $title ) {
215 $resultPageSet->setGeneratorData(
217 $data[ $index ] + [
'index' => $index + $offset ]
229 private function getSearchResultData( SearchResult $result, $prop ) {
231 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
237 $title = $result->getTitle();
239 $vals[
'pageid'] = $title->getArticleID();
241 if ( isset( $prop[
'size'] ) ) {
242 $vals[
'size'] = $result->getByteSize();
244 if ( isset( $prop[
'wordcount'] ) ) {
245 $vals[
'wordcount'] = $result->getWordCount();
247 if ( isset( $prop[
'snippet'] ) ) {
248 $vals[
'snippet'] = $result->getTextSnippet();
250 if ( isset( $prop[
'timestamp'] ) ) {
251 $vals[
'timestamp'] =
wfTimestamp( TS::ISO_8601, $result->getTimestamp() );
253 if ( isset( $prop[
'titlesnippet'] ) ) {
254 $vals[
'titlesnippet'] = $result->getTitleSnippet();
256 if ( isset( $prop[
'categorysnippet'] ) ) {
257 $vals[
'categorysnippet'] = $result->getCategorySnippet();
259 if ( $result->getRedirectTitle() !==
null ) {
260 if ( isset( $prop[
'redirecttitle'] ) ) {
261 $vals[
'redirecttitle'] = $result->getRedirectTitle()->getPrefixedText();
263 if ( isset( $prop[
'redirectsnippet'] ) ) {
264 $vals[
'redirectsnippet'] = $result->getRedirectSnippet();
267 if ( $result->getSectionTitle() !==
null ) {
268 if ( isset( $prop[
'sectiontitle'] ) ) {
269 $vals[
'sectiontitle'] = $result->getSectionTitle()->getFragment();
271 if ( isset( $prop[
'sectionsnippet'] ) ) {
272 $vals[
'sectionsnippet'] = $result->getSectionSnippet();
275 if ( isset( $prop[
'isfilematch'] ) ) {
276 $vals[
'isfilematch'] = $result->isFileMatch();
279 if ( isset( $prop[
'extensiondata'] ) ) {
280 $extra = $result->getExtensionData();
300 private function addInterwikiResults(
301 ISearchResultSet
$matches, ApiResult $apiResult, $prop,
305 $approximateTotalHits =
false;
306 if (
$matches->hasInterwikiResults( $type ) ) {
307 foreach (
$matches->getInterwikiResults( $type ) as $interwikiMatches ) {
309 $interwikiTotalHits = $interwikiMatches->getTotalHits();
310 if ( $interwikiTotalHits !==
null ) {
311 $totalhits += $interwikiTotalHits;
312 $approximateTotalHits = $approximateTotalHits || $interwikiMatches->isApproximateTotalHits();
315 foreach ( $interwikiMatches as $result ) {
316 $title = $result->getTitle();
317 $vals = $this->getSearchResultData( $result, $prop );
319 $vals[
'namespace'] = $result->getInterwikiNamespaceText();
320 $vals[
'title'] = $title->getText();
321 $vals[
'url'] = $title->getFullURL();
324 $fit = $apiResult->addValue( [
327 $result->getInterwikiPrefix()
337 if ( $totalhits !==
null ) {
338 $apiResult->addValue( [
'query', $section .
'searchinfo' ],
'totalhits', $totalhits );
339 if ( $approximateTotalHits ) {
340 $apiResult->addValue( [
'query', $section .
'searchinfo' ],
'approximate_totalhits',
true );
342 $apiResult->addIndexedTagName( [
350 private function decideSnippets( array $prop ): array {
355 if ( isset( $prop[
'titlesnippet'] ) ) {
361 if ( isset( $prop[
'redirectsnippet'] ) || isset( $prop[
'redirecttitle'] ) ) {
362 $fields[] =
'redirect';
364 if ( isset( $prop[
'categorysnippet'] ) ) {
365 $fields[] =
'category';
367 if ( isset( $prop[
'sectionsnippet'] ) || isset( $prop[
'sectiontitle'] ) ) {
368 $fields[] =
'heading';
382 ParamValidator::PARAM_TYPE => [
389 ParamValidator::PARAM_DEFAULT =>
'totalhits|suggestion|rewrittenquery',
390 ParamValidator::PARAM_TYPE => [
395 ParamValidator::PARAM_ISMULTI =>
true,
398 ParamValidator::PARAM_DEFAULT =>
'size|wordcount|timestamp|snippet',
399 ParamValidator::PARAM_TYPE => [
415 ParamValidator::PARAM_ISMULTI =>
true,
417 EnumDef::PARAM_DEPRECATED_VALUES => [
422 'interwiki' =>
false,
423 'enablerewrites' =>
false,
427 if ( $this->isInGeneratorMode() ) {
428 $allowedParams[
'prop'][ParamValidator::PARAM_DEFAULT] =
'';
429 $allowedParams[
'info'][ParamValidator::PARAM_DEFAULT] =
'';
434 $alternatives = $this->searchEngineConfig->getSearchTypes();
435 if ( count( $alternatives ) == 1 ) {
436 $allowedParams[
'sort'] = [
437 ParamValidator::PARAM_DEFAULT => SearchEngine::DEFAULT_SORT,
438 ParamValidator::PARAM_TYPE => $this->searchEngineFactory->create()->getValidSorts(),
442 return $allowedParams;
450 'profile-type' => SearchEngine::FT_QUERY_INDEP_PROFILE_TYPE,
451 'help-message' =>
'apihelp-query+search-param-qiprofile',
455 'profile-type' => SearchEngine::FT_QUERY_DEP_PROFILE_TYPE,
456 'help-message' =>
'apihelp-query+search-param-qdprofile',
464 'action=query&list=search&srsearch=meaning'
465 =>
'apihelp-query+search-example-simple',
466 'action=query&list=search&srwhat=text&srsearch=meaning'
467 =>
'apihelp-query+search-example-text',
468 'action=query&generator=search&gsrsearch=meaning&prop=info'
469 =>
'apihelp-query+search-example-generator',
475 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Search';