Completed
Branch master (939199)
by
unknown
39:35
created

includes/api/ApiQuerySearch.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 *
4
 *
5
 * Created on July 30, 2007
6
 *
7
 * Copyright © 2007 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License along
20
 * with this program; if not, write to the Free Software Foundation, Inc.,
21
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
 * http://www.gnu.org/copyleft/gpl.html
23
 *
24
 * @file
25
 */
26
27
/**
28
 * Query module to perform full text search within wiki titles and content
29
 *
30
 * @ingroup API
31
 */
32
class ApiQuerySearch extends ApiQueryGeneratorBase {
33
	use SearchApi;
34
35
	/** @var array list of api allowed params */
36
	private $allowedParams;
37
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
50
	/**
51
	 * @param ApiPageSet $resultPageSet
52
	 * @return void
53
	 */
54
	private function run( $resultPageSet = null ) {
55
		global $wgContLang;
56
		$params = $this->extractRequestParams();
57
58
		// Extract parameters
59
		$query = $params['search'];
60
		$what = $params['what'];
61
		$interwiki = $params['interwiki'];
62
		$searchInfo = array_flip( $params['info'] );
63
		$prop = array_flip( $params['prop'] );
64
65
		// Deprecated parameters
66
		if ( isset( $prop['hasrelated'] ) ) {
67
			$this->logFeatureUsage( 'action=search&srprop=hasrelated' );
68
			$this->setWarning( 'srprop=hasrelated has been deprecated' );
69
		}
70
		if ( isset( $prop['score'] ) ) {
71
			$this->logFeatureUsage( 'action=search&srprop=score' );
72
			$this->setWarning( 'srprop=score has been deprecated' );
73
		}
74
75
		// Create search engine instance and set options
76
		$search = $this->buildSearchEngine( $params );
77
		$search->setFeatureData( 'rewrite', (bool)$params['enablerewrites'] );
78
		$search->setFeatureData( 'interwiki', (bool)$interwiki );
79
80
		$query = $search->transformSearchTerm( $query );
81
		$query = $search->replacePrefixes( $query );
82
83
		// Perform the actual search
84
		if ( $what == 'text' ) {
85
			$matches = $search->searchText( $query );
86
		} elseif ( $what == 'title' ) {
87
			$matches = $search->searchTitle( $query );
88
		} elseif ( $what == 'nearmatch' ) {
89
			// near matches must receive the user input as provided, otherwise
90
			// the near matches within namespaces are lost.
91
			$matches = $search->getNearMatcher( $this->getConfig() )
92
				->getNearMatchResultSet( $params['search'] );
93
		} else {
94
			// We default to title searches; this is a terrible legacy
95
			// of the way we initially set up the MySQL fulltext-based
96
			// search engine with separate title and text fields.
97
			// In the future, the default should be for a combined index.
98
			$what = 'title';
99
			$matches = $search->searchTitle( $query );
100
101
			// Not all search engines support a separate title search,
102
			// for instance the Lucene-based engine we use on Wikipedia.
103
			// In this case, fall back to full-text search (which will
104
			// include titles in it!)
105
			if ( is_null( $matches ) ) {
106
				$what = 'text';
107
				$matches = $search->searchText( $query );
108
			}
109
		}
110
		if ( is_null( $matches ) ) {
111
			$this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" );
112
		} elseif ( $matches instanceof Status && !$matches->isGood() ) {
113
			$this->dieUsage( $matches->getWikiText( false, false, 'en' ), 'search-error' );
114
		}
115
116
		if ( $resultPageSet === null ) {
117
			$apiResult = $this->getResult();
118
			// Add search meta data to result
119
			if ( isset( $searchInfo['totalhits'] ) ) {
120
				$totalhits = $matches->getTotalHits();
121
				if ( $totalhits !== null ) {
122
					$apiResult->addValue( [ 'query', 'searchinfo' ],
123
						'totalhits', $totalhits );
124
				}
125
			}
126
			if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) {
127
				$apiResult->addValue( [ 'query', 'searchinfo' ],
128
					'suggestion', $matches->getSuggestionQuery() );
129
				$apiResult->addValue( [ 'query', 'searchinfo' ],
130
					'suggestionsnippet', $matches->getSuggestionSnippet() );
0 ignored issues
show
The method getSuggestionSnippet does only exist in SearchResultSet, but not in Status.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
131
			}
132
			if ( isset( $searchInfo['rewrittenquery'] ) && $matches->hasRewrittenQuery() ) {
0 ignored issues
show
The method hasRewrittenQuery does only exist in SearchResultSet, but not in Status.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
133
				$apiResult->addValue( [ 'query', 'searchinfo' ],
134
					'rewrittenquery', $matches->getQueryAfterRewrite() );
0 ignored issues
show
The method getQueryAfterRewrite does only exist in SearchResultSet, but not in Status.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
135
				$apiResult->addValue( [ 'query', 'searchinfo' ],
136
					'rewrittenquerysnippet', $matches->getQueryAfterRewriteSnippet() );
0 ignored issues
show
The method getQueryAfterRewriteSnippet does only exist in SearchResultSet, but not in Status.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
137
			}
138
		}
139
140
		// Add the search results to the result
141
		$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
142
		$titles = [];
143
		$count = 0;
144
		$result = $matches->next();
145
		$limit = $params['limit'];
146
147
		while ( $result ) {
148 View Code Duplication
			if ( ++$count > $limit ) {
149
				// We've reached the one extra which shows that there are
150
				// additional items to be had. Stop here...
151
				$this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] );
152
				break;
153
			}
154
155
			// Silently skip broken and missing titles
156
			if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
157
				$result = $matches->next();
158
				continue;
159
			}
160
161
			$title = $result->getTitle();
162
			if ( $resultPageSet === null ) {
163
				$vals = [];
164
				ApiQueryBase::addTitleInfo( $vals, $title );
165
166
				if ( isset( $prop['snippet'] ) ) {
167
					$vals['snippet'] = $result->getTextSnippet( $terms );
168
				}
169
				if ( isset( $prop['size'] ) ) {
170
					$vals['size'] = $result->getByteSize();
171
				}
172
				if ( isset( $prop['wordcount'] ) ) {
173
					$vals['wordcount'] = $result->getWordCount();
174
				}
175
				if ( isset( $prop['timestamp'] ) ) {
176
					$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $result->getTimestamp() );
177
				}
178
				if ( isset( $prop['titlesnippet'] ) ) {
179
					$vals['titlesnippet'] = $result->getTitleSnippet();
180
				}
181
				if ( isset( $prop['categorysnippet'] ) ) {
182
					$vals['categorysnippet'] = $result->getCategorySnippet();
183
				}
184
				if ( !is_null( $result->getRedirectTitle() ) ) {
185
					if ( isset( $prop['redirecttitle'] ) ) {
186
						$vals['redirecttitle'] = $result->getRedirectTitle()->getPrefixedText();
187
					}
188
					if ( isset( $prop['redirectsnippet'] ) ) {
189
						$vals['redirectsnippet'] = $result->getRedirectSnippet();
190
					}
191
				}
192
				if ( !is_null( $result->getSectionTitle() ) ) {
193
					if ( isset( $prop['sectiontitle'] ) ) {
194
						$vals['sectiontitle'] = $result->getSectionTitle()->getFragment();
195
					}
196
					if ( isset( $prop['sectionsnippet'] ) ) {
197
						$vals['sectionsnippet'] = $result->getSectionSnippet();
198
					}
199
				}
200
				if ( isset( $prop['isfilematch'] ) ) {
201
					$vals['isfilematch'] = $result->isFileMatch();
202
				}
203
204
				// Add item to results and see whether it fits
205
				$fit = $apiResult->addValue( [ 'query', $this->getModuleName() ],
206
					null, $vals );
207
				if ( !$fit ) {
208
					$this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 );
209
					break;
210
				}
211
			} else {
212
				$titles[] = $title;
213
			}
214
215
			$result = $matches->next();
216
		}
217
218
		$hasInterwikiResults = false;
219
		$totalhits = null;
220
		if ( $interwiki && $resultPageSet === null && $matches->hasInterwikiResults() ) {
221
			foreach ( $matches->getInterwikiResults() as $interwikiMatches ) {
0 ignored issues
show
The method getInterwikiResults does only exist in SearchResultSet, but not in Status.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
222
				$hasInterwikiResults = true;
223
224
				// Include number of results if requested
225
				if ( $resultPageSet === null && isset( $searchInfo['totalhits'] ) ) {
226
					$totalhits += $interwikiMatches->getTotalHits();
227
				}
228
229
				$result = $interwikiMatches->next();
230
				while ( $result ) {
231
					$title = $result->getTitle();
232
233
					if ( $resultPageSet === null ) {
234
						$vals = [
235
							'namespace' => $result->getInterwikiNamespaceText(),
236
							'title' => $title->getText(),
237
							'url' => $title->getFullURL(),
238
						];
239
240
						// Add item to results and see whether it fits
241
						$fit = $apiResult->addValue(
242
							[ 'query', 'interwiki' . $this->getModuleName(), $result->getInterwikiPrefix() ],
243
							null,
244
							$vals
245
						);
246
247
						if ( !$fit ) {
248
							// We hit the limit. We can't really provide any meaningful
249
							// pagination info so just bail out
250
							break;
251
						}
252
					} else {
253
						$titles[] = $title;
254
					}
255
256
					$result = $interwikiMatches->next();
257
				}
258
			}
259
			if ( $totalhits !== null ) {
260
				$apiResult->addValue( [ 'query', 'interwikisearchinfo' ],
261
					'totalhits', $totalhits );
262
			}
263
		}
264
265
		if ( $resultPageSet === null ) {
266
			$apiResult->addIndexedTagName( [
267
				'query', $this->getModuleName()
268
			], 'p' );
269
			if ( $hasInterwikiResults ) {
270
				$apiResult->addIndexedTagName( [
271
					'query', 'interwiki' . $this->getModuleName()
272
				], 'p' );
273
			}
274
		} else {
275 View Code Duplication
			$resultPageSet->setRedirectMergePolicy( function ( $current, $new ) {
276
				if ( !isset( $current['index'] ) || $new['index'] < $current['index'] ) {
277
					$current['index'] = $new['index'];
278
				}
279
				return $current;
280
			} );
281
			$resultPageSet->populateFromTitles( $titles );
282
			$offset = $params['offset'] + 1;
283
			foreach ( $titles as $index => $title ) {
284
				$resultPageSet->setGeneratorData( $title, [ 'index' => $index + $offset ] );
285
			}
286
		}
287
	}
288
289
	public function getCacheMode( $params ) {
290
		return 'public';
291
	}
292
293
	public function getAllowedParams() {
294
		if ( $this->allowedParams !== null ) {
295
			return $this->allowedParams;
296
		}
297
298
		$this->allowedParams = $this->buildCommonApiParams() + [
299
			'what' => [
300
				ApiBase::PARAM_TYPE => [
301
					'title',
302
					'text',
303
					'nearmatch',
304
				]
305
			],
306
			'info' => [
307
				ApiBase::PARAM_DFLT => 'totalhits|suggestion|rewrittenquery',
308
				ApiBase::PARAM_TYPE => [
309
					'totalhits',
310
					'suggestion',
311
					'rewrittenquery',
312
				],
313
				ApiBase::PARAM_ISMULTI => true,
314
			],
315
			'prop' => [
316
				ApiBase::PARAM_DFLT => 'size|wordcount|timestamp|snippet',
317
				ApiBase::PARAM_TYPE => [
318
					'size',
319
					'wordcount',
320
					'timestamp',
321
					'snippet',
322
					'titlesnippet',
323
					'redirecttitle',
324
					'redirectsnippet',
325
					'sectiontitle',
326
					'sectionsnippet',
327
					'isfilematch',
328
					'categorysnippet',
329
					'score', // deprecated
330
					'hasrelated', // deprecated
331
				],
332
				ApiBase::PARAM_ISMULTI => true,
333
				ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
334
			],
335
			'interwiki' => false,
336
			'enablerewrites' => false,
337
		];
338
339
		return $this->allowedParams;
340
	}
341
342
	public function getSearchProfileParams() {
343
		return [
344
			'qiprofile' => [
345
				'profile-type' => SearchEngine::FT_QUERY_INDEP_PROFILE_TYPE,
346
				'help-message' => 'apihelp-query+search-param-qiprofile',
347
			],
348
		];
349
	}
350
351
	protected function getExamplesMessages() {
352
		return [
353
			'action=query&list=search&srsearch=meaning'
354
				=> 'apihelp-query+search-example-simple',
355
			'action=query&list=search&srwhat=text&srsearch=meaning'
356
				=> 'apihelp-query+search-example-text',
357
			'action=query&generator=search&gsrsearch=meaning&prop=info'
358
				=> 'apihelp-query+search-example-generator',
359
		];
360
	}
361
362
	public function getHelpUrls() {
363
		return 'https://www.mediawiki.org/wiki/API:Search';
364
	}
365
}
366