This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Basic search engine |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup Search |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * @defgroup Search Search |
||
26 | */ |
||
27 | |||
28 | use MediaWiki\MediaWikiServices; |
||
29 | |||
30 | /** |
||
31 | * Contain a class for special pages |
||
32 | * @ingroup Search |
||
33 | */ |
||
34 | abstract class SearchEngine { |
||
35 | /** @var string */ |
||
36 | public $prefix = ''; |
||
37 | |||
38 | /** @var int[]|null */ |
||
39 | public $namespaces = [ NS_MAIN ]; |
||
40 | |||
41 | /** @var int */ |
||
42 | protected $limit = 10; |
||
43 | |||
44 | /** @var int */ |
||
45 | protected $offset = 0; |
||
46 | |||
47 | /** @var array|string */ |
||
48 | protected $searchTerms = []; |
||
49 | |||
50 | /** @var bool */ |
||
51 | protected $showSuggestion = true; |
||
52 | private $sort = 'relevance'; |
||
53 | |||
54 | /** @var array Feature values */ |
||
55 | protected $features = []; |
||
56 | |||
57 | /** @const string profile type for completionSearch */ |
||
58 | const COMPLETION_PROFILE_TYPE = 'completionSearchProfile'; |
||
59 | |||
60 | /** @const string profile type for query independent ranking features */ |
||
61 | const FT_QUERY_INDEP_PROFILE_TYPE = 'fulltextQueryIndepProfile'; |
||
62 | |||
63 | /** |
||
64 | * Perform a full text search query and return a result set. |
||
65 | * If full text searches are not supported or disabled, return null. |
||
66 | * STUB |
||
67 | * |
||
68 | * @param string $term Raw search term |
||
69 | * @return SearchResultSet|Status|null |
||
70 | */ |
||
71 | function searchText( $term ) { |
||
72 | return null; |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Perform a title-only search query and return a result set. |
||
77 | * If title searches are not supported or disabled, return null. |
||
78 | * STUB |
||
79 | * |
||
80 | * @param string $term Raw search term |
||
81 | * @return SearchResultSet|null |
||
82 | */ |
||
83 | function searchTitle( $term ) { |
||
84 | return null; |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * @since 1.18 |
||
89 | * @param string $feature |
||
90 | * @return bool |
||
91 | */ |
||
92 | public function supports( $feature ) { |
||
93 | switch ( $feature ) { |
||
94 | case 'search-update': |
||
95 | return true; |
||
96 | case 'title-suffix-filter': |
||
97 | default: |
||
98 | return false; |
||
99 | } |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * Way to pass custom data for engines |
||
104 | * @since 1.18 |
||
105 | * @param string $feature |
||
106 | * @param mixed $data |
||
107 | * @return bool |
||
108 | */ |
||
109 | public function setFeatureData( $feature, $data ) { |
||
110 | $this->features[$feature] = $data; |
||
111 | } |
||
112 | |||
113 | /** |
||
114 | * When overridden in derived class, performs database-specific conversions |
||
115 | * on text to be used for searching or updating search index. |
||
116 | * Default implementation does nothing (simply returns $string). |
||
117 | * |
||
118 | * @param string $string String to process |
||
119 | * @return string |
||
120 | */ |
||
121 | public function normalizeText( $string ) { |
||
122 | global $wgContLang; |
||
123 | |||
124 | // Some languages such as Chinese require word segmentation |
||
125 | return $wgContLang->segmentByWord( $string ); |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Transform search term in cases when parts of the query came as different |
||
130 | * GET params (when supported), e.g. for prefix queries: |
||
131 | * search=test&prefix=Main_Page/Archive -> test prefix:Main Page/Archive |
||
132 | * @param string $term |
||
133 | * @return string |
||
134 | */ |
||
135 | public function transformSearchTerm( $term ) { |
||
136 | return $term; |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * Get service class to finding near matches. |
||
141 | * @param Config $config Configuration to use for the matcher. |
||
142 | * @return SearchNearMatcher |
||
143 | */ |
||
144 | public function getNearMatcher( Config $config ) { |
||
145 | global $wgContLang; |
||
146 | return new SearchNearMatcher( $config, $wgContLang ); |
||
147 | } |
||
148 | |||
149 | /** |
||
150 | * Get near matcher for default SearchEngine. |
||
151 | * @return SearchNearMatcher |
||
152 | */ |
||
153 | protected static function defaultNearMatcher() { |
||
154 | $config = MediaWikiServices::getInstance()->getMainConfig(); |
||
155 | return MediaWikiServices::getInstance()->newSearchEngine()->getNearMatcher( $config ); |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * If an exact title match can be found, or a very slightly close match, |
||
160 | * return the title. If no match, returns NULL. |
||
161 | * @deprecated since 1.27; Use SearchEngine::getNearMatcher() |
||
162 | * @param string $searchterm |
||
163 | * @return Title |
||
164 | */ |
||
165 | public static function getNearMatch( $searchterm ) { |
||
166 | return static::defaultNearMatcher()->getNearMatch( $searchterm ); |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * Do a near match (see SearchEngine::getNearMatch) and wrap it into a |
||
171 | * SearchResultSet. |
||
172 | * @deprecated since 1.27; Use SearchEngine::getNearMatcher() |
||
173 | * @param string $searchterm |
||
174 | * @return SearchResultSet |
||
175 | */ |
||
176 | public static function getNearMatchResultSet( $searchterm ) { |
||
177 | return static::defaultNearMatcher()->getNearMatchResultSet( $searchterm ); |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Get chars legal for search. |
||
182 | * NOTE: usage as static is deprecated and preserved only as BC measure |
||
183 | * @return string |
||
184 | */ |
||
185 | public static function legalSearchChars() { |
||
186 | return "A-Za-z_'.0-9\\x80-\\xFF\\-"; |
||
187 | } |
||
188 | |||
189 | /** |
||
190 | * Set the maximum number of results to return |
||
191 | * and how many to skip before returning the first. |
||
192 | * |
||
193 | * @param int $limit |
||
194 | * @param int $offset |
||
195 | */ |
||
196 | function setLimitOffset( $limit, $offset = 0 ) { |
||
197 | $this->limit = intval( $limit ); |
||
198 | $this->offset = intval( $offset ); |
||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Set which namespaces the search should include. |
||
203 | * Give an array of namespace index numbers. |
||
204 | * |
||
205 | * @param int[]|null $namespaces |
||
206 | */ |
||
207 | function setNamespaces( $namespaces ) { |
||
208 | if ( $namespaces ) { |
||
209 | // Filter namespaces to only keep valid ones |
||
210 | $validNs = $this->searchableNamespaces(); |
||
211 | $namespaces = array_filter( $namespaces, function( $ns ) use( $validNs ) { |
||
212 | return $ns < 0 || isset( $validNs[$ns] ); |
||
213 | } ); |
||
214 | } else { |
||
215 | $namespaces = []; |
||
216 | } |
||
217 | $this->namespaces = $namespaces; |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Set whether the searcher should try to build a suggestion. Note: some searchers |
||
222 | * don't support building a suggestion in the first place and others don't respect |
||
223 | * this flag. |
||
224 | * |
||
225 | * @param bool $showSuggestion Should the searcher try to build suggestions |
||
226 | */ |
||
227 | function setShowSuggestion( $showSuggestion ) { |
||
228 | $this->showSuggestion = $showSuggestion; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Get the valid sort directions. All search engines support 'relevance' but others |
||
233 | * might support more. The default in all implementations should be 'relevance.' |
||
234 | * |
||
235 | * @since 1.25 |
||
236 | * @return array(string) the valid sort directions for setSort |
||
0 ignored issues
–
show
|
|||
237 | */ |
||
238 | public function getValidSorts() { |
||
239 | return [ 'relevance' ]; |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Set the sort direction of the search results. Must be one returned by |
||
244 | * SearchEngine::getValidSorts() |
||
245 | * |
||
246 | * @since 1.25 |
||
247 | * @throws InvalidArgumentException |
||
248 | * @param string $sort sort direction for query result |
||
249 | */ |
||
250 | public function setSort( $sort ) { |
||
251 | if ( !in_array( $sort, $this->getValidSorts() ) ) { |
||
252 | throw new InvalidArgumentException( "Invalid sort: $sort. " . |
||
253 | "Must be one of: " . implode( ', ', $this->getValidSorts() ) ); |
||
254 | } |
||
255 | $this->sort = $sort; |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * Get the sort direction of the search results |
||
260 | * |
||
261 | * @since 1.25 |
||
262 | * @return string |
||
263 | */ |
||
264 | public function getSort() { |
||
265 | return $this->sort; |
||
266 | } |
||
267 | |||
268 | /** |
||
269 | * Parse some common prefixes: all (search everything) |
||
270 | * or namespace names and set the list of namespaces |
||
271 | * of this class accordingly. |
||
272 | * |
||
273 | * @param string $query |
||
274 | * @return string |
||
275 | */ |
||
276 | function replacePrefixes( $query ) { |
||
277 | $queryAndNs = self::parseNamespacePrefixes( $query ); |
||
278 | if ( $queryAndNs === false ) { |
||
279 | return $query; |
||
280 | } |
||
281 | $this->namespaces = $queryAndNs[1]; |
||
282 | return $queryAndNs[0]; |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * Parse some common prefixes: all (search everything) |
||
287 | * or namespace names |
||
288 | * |
||
289 | * @param string $query |
||
290 | * @return false|array false if no namespace was extracted, an array |
||
291 | * with the parsed query at index 0 and an array of namespaces at index |
||
292 | * 1 (or null for all namespaces). |
||
293 | */ |
||
294 | public static function parseNamespacePrefixes( $query ) { |
||
295 | global $wgContLang; |
||
296 | |||
297 | $parsed = $query; |
||
298 | if ( strpos( $query, ':' ) === false ) { // nothing to do |
||
299 | return false; |
||
300 | } |
||
301 | $extractedNamespace = null; |
||
302 | |||
303 | $allkeyword = wfMessage( 'searchall' )->inContentLanguage()->text() . ":"; |
||
304 | if ( strncmp( $query, $allkeyword, strlen( $allkeyword ) ) == 0 ) { |
||
305 | $extractedNamespace = null; |
||
306 | $parsed = substr( $query, strlen( $allkeyword ) ); |
||
307 | } elseif ( strpos( $query, ':' ) !== false ) { |
||
308 | // TODO: should we unify with PrefixSearch::extractNamespace ? |
||
309 | $prefix = str_replace( ' ', '_', substr( $query, 0, strpos( $query, ':' ) ) ); |
||
310 | $index = $wgContLang->getNsIndex( $prefix ); |
||
311 | if ( $index !== false ) { |
||
312 | $extractedNamespace = [ $index ]; |
||
313 | $parsed = substr( $query, strlen( $prefix ) + 1 ); |
||
314 | } else { |
||
315 | return false; |
||
316 | } |
||
317 | } |
||
318 | |||
319 | if ( trim( $parsed ) == '' ) { |
||
320 | $parsed = $query; // prefix was the whole query |
||
321 | } |
||
322 | |||
323 | return [ $parsed, $extractedNamespace ]; |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Find snippet highlight settings for all users |
||
328 | * @return array Contextlines, contextchars |
||
329 | */ |
||
330 | public static function userHighlightPrefs() { |
||
331 | $contextlines = 2; // Hardcode this. Old defaults sucked. :) |
||
332 | $contextchars = 75; // same as above.... :P |
||
333 | return [ $contextlines, $contextchars ]; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Create or update the search index record for the given page. |
||
338 | * Title and text should be pre-processed. |
||
339 | * STUB |
||
340 | * |
||
341 | * @param int $id |
||
342 | * @param string $title |
||
343 | * @param string $text |
||
344 | */ |
||
345 | function update( $id, $title, $text ) { |
||
346 | // no-op |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * Update a search index record's title only. |
||
351 | * Title should be pre-processed. |
||
352 | * STUB |
||
353 | * |
||
354 | * @param int $id |
||
355 | * @param string $title |
||
356 | */ |
||
357 | function updateTitle( $id, $title ) { |
||
358 | // no-op |
||
359 | } |
||
360 | |||
361 | /** |
||
362 | * Delete an indexed page |
||
363 | * Title should be pre-processed. |
||
364 | * STUB |
||
365 | * |
||
366 | * @param int $id Page id that was deleted |
||
367 | * @param string $title Title of page that was deleted |
||
368 | */ |
||
369 | function delete( $id, $title ) { |
||
370 | // no-op |
||
371 | } |
||
372 | |||
373 | /** |
||
374 | * Get OpenSearch suggestion template |
||
375 | * |
||
376 | * @deprecated since 1.25 |
||
377 | * @return string |
||
378 | */ |
||
379 | public static function getOpenSearchTemplate() { |
||
380 | wfDeprecated( __METHOD__, '1.25' ); |
||
381 | return ApiOpenSearch::getOpenSearchTemplate( 'application/x-suggestions+json' ); |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * Get the raw text for updating the index from a content object |
||
386 | * Nicer search backends could possibly do something cooler than |
||
387 | * just returning raw text |
||
388 | * |
||
389 | * @todo This isn't ideal, we'd really like to have content-specific handling here |
||
390 | * @param Title $t Title we're indexing |
||
391 | * @param Content $c Content of the page to index |
||
392 | * @return string |
||
393 | */ |
||
394 | public function getTextFromContent( Title $t, Content $c = null ) { |
||
395 | return $c ? $c->getTextForSearchIndex() : ''; |
||
396 | } |
||
397 | |||
398 | /** |
||
399 | * If an implementation of SearchEngine handles all of its own text processing |
||
400 | * in getTextFromContent() and doesn't require SearchUpdate::updateText()'s |
||
401 | * rather silly handling, it should return true here instead. |
||
402 | * |
||
403 | * @return bool |
||
404 | */ |
||
405 | public function textAlreadyUpdatedForIndex() { |
||
406 | return false; |
||
407 | } |
||
408 | |||
409 | /** |
||
410 | * Makes search simple string if it was namespaced. |
||
411 | * Sets namespaces of the search to namespaces extracted from string. |
||
412 | * @param string $search |
||
413 | * @return string Simplified search string |
||
414 | */ |
||
415 | protected function normalizeNamespaces( $search ) { |
||
416 | // Find a Title which is not an interwiki and is in NS_MAIN |
||
417 | $title = Title::newFromText( $search ); |
||
418 | $ns = $this->namespaces; |
||
419 | if ( $title && !$title->isExternal() ) { |
||
420 | $ns = [ $title->getNamespace() ]; |
||
421 | $search = $title->getText(); |
||
422 | if ( $ns[0] == NS_MAIN ) { |
||
423 | $ns = $this->namespaces; // no explicit prefix, use default namespaces |
||
424 | Hooks::run( 'PrefixSearchExtractNamespace', [ &$ns, &$search ] ); |
||
425 | } |
||
426 | } else { |
||
427 | $title = Title::newFromText( $search . 'Dummy' ); |
||
428 | if ( $title && $title->getText() == 'Dummy' |
||
429 | && $title->getNamespace() != NS_MAIN |
||
430 | && !$title->isExternal() ) |
||
431 | { |
||
432 | $ns = [ $title->getNamespace() ]; |
||
433 | $search = ''; |
||
434 | } else { |
||
435 | Hooks::run( 'PrefixSearchExtractNamespace', [ &$ns, &$search ] ); |
||
436 | } |
||
437 | } |
||
438 | |||
439 | $ns = array_map( function( $space ) { |
||
440 | return $space == NS_MEDIA ? NS_FILE : $space; |
||
441 | }, $ns ); |
||
442 | |||
443 | $this->setNamespaces( $ns ); |
||
444 | return $search; |
||
445 | } |
||
446 | |||
447 | /** |
||
448 | * Perform a completion search. |
||
449 | * Does not resolve namespaces and does not check variants. |
||
450 | * Search engine implementations may want to override this function. |
||
451 | * @param string $search |
||
452 | * @return SearchSuggestionSet |
||
453 | */ |
||
454 | protected function completionSearchBackend( $search ) { |
||
455 | $results = []; |
||
456 | |||
457 | $search = trim( $search ); |
||
458 | |||
459 | if ( !in_array( NS_SPECIAL, $this->namespaces ) && // We do not run hook on Special: search |
||
460 | !Hooks::run( 'PrefixSearchBackend', |
||
461 | [ $this->namespaces, $search, $this->limit, &$results, $this->offset ] |
||
462 | ) ) { |
||
463 | // False means hook worked. |
||
464 | // FIXME: Yes, the API is weird. That's why it is going to be deprecated. |
||
465 | |||
466 | return SearchSuggestionSet::fromStrings( $results ); |
||
467 | } else { |
||
468 | // Hook did not do the job, use default simple search |
||
469 | $results = $this->simplePrefixSearch( $search ); |
||
470 | return SearchSuggestionSet::fromTitles( $results ); |
||
471 | } |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Perform a completion search. |
||
476 | * @param string $search |
||
477 | * @return SearchSuggestionSet |
||
478 | */ |
||
479 | public function completionSearch( $search ) { |
||
480 | if ( trim( $search ) === '' ) { |
||
481 | return SearchSuggestionSet::emptySuggestionSet(); // Return empty result |
||
482 | } |
||
483 | $search = $this->normalizeNamespaces( $search ); |
||
484 | return $this->processCompletionResults( $search, $this->completionSearchBackend( $search ) ); |
||
485 | } |
||
486 | |||
487 | /** |
||
488 | * Perform a completion search with variants. |
||
489 | * @param string $search |
||
490 | * @return SearchSuggestionSet |
||
491 | */ |
||
492 | public function completionSearchWithVariants( $search ) { |
||
493 | if ( trim( $search ) === '' ) { |
||
494 | return SearchSuggestionSet::emptySuggestionSet(); // Return empty result |
||
495 | } |
||
496 | $search = $this->normalizeNamespaces( $search ); |
||
497 | |||
498 | $results = $this->completionSearchBackend( $search ); |
||
499 | $fallbackLimit = $this->limit - $results->getSize(); |
||
500 | if ( $fallbackLimit > 0 ) { |
||
501 | global $wgContLang; |
||
502 | |||
503 | $fallbackSearches = $wgContLang->autoConvertToAllVariants( $search ); |
||
504 | $fallbackSearches = array_diff( array_unique( $fallbackSearches ), [ $search ] ); |
||
505 | |||
506 | foreach ( $fallbackSearches as $fbs ) { |
||
507 | $this->setLimitOffset( $fallbackLimit ); |
||
508 | $fallbackSearchResult = $this->completionSearch( $fbs ); |
||
509 | $results->appendAll( $fallbackSearchResult ); |
||
510 | $fallbackLimit -= count( $fallbackSearchResult ); |
||
511 | if ( $fallbackLimit <= 0 ) { |
||
512 | break; |
||
513 | } |
||
514 | } |
||
515 | } |
||
516 | return $this->processCompletionResults( $search, $results ); |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * Extract titles from completion results |
||
521 | * @param SearchSuggestionSet $completionResults |
||
522 | * @return Title[] |
||
523 | */ |
||
524 | public function extractTitles( SearchSuggestionSet $completionResults ) { |
||
525 | return $completionResults->map( function( SearchSuggestion $sugg ) { |
||
526 | return $sugg->getSuggestedTitle(); |
||
527 | } ); |
||
528 | } |
||
529 | |||
530 | /** |
||
531 | * Process completion search results. |
||
532 | * Resolves the titles and rescores. |
||
533 | * @param SearchSuggestionSet $suggestions |
||
534 | * @return SearchSuggestionSet |
||
535 | */ |
||
536 | protected function processCompletionResults( $search, SearchSuggestionSet $suggestions ) { |
||
537 | $search = trim( $search ); |
||
538 | // preload the titles with LinkBatch |
||
539 | $titles = $suggestions->map( function( SearchSuggestion $sugg ) { |
||
540 | return $sugg->getSuggestedTitle(); |
||
541 | } ); |
||
542 | $lb = new LinkBatch( $titles ); |
||
543 | $lb->setCaller( __METHOD__ ); |
||
544 | $lb->execute(); |
||
545 | |||
546 | $results = $suggestions->map( function( SearchSuggestion $sugg ) { |
||
547 | return $sugg->getSuggestedTitle()->getPrefixedText(); |
||
548 | } ); |
||
549 | |||
550 | if ( $this->offset === 0 ) { |
||
551 | // Rescore results with an exact title match |
||
552 | // NOTE: in some cases like cross-namespace redirects |
||
553 | // (frequently used as shortcuts e.g. WP:WP on huwiki) some |
||
554 | // backends like Cirrus will return no results. We should still |
||
555 | // try an exact title match to workaround this limitation |
||
556 | $rescorer = new SearchExactMatchRescorer(); |
||
557 | $rescoredResults = $rescorer->rescore( $search, $this->namespaces, $results, $this->limit ); |
||
0 ignored issues
–
show
It seems like
$this->namespaces can also be of type null ; however, SearchExactMatchRescorer::rescore() does only seem to accept array<integer,integer> , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
558 | } else { |
||
559 | // No need to rescore if offset is not 0 |
||
560 | // The exact match must have been returned at position 0 |
||
561 | // if it existed. |
||
562 | $rescoredResults = $results; |
||
563 | } |
||
564 | |||
565 | if ( count( $rescoredResults ) > 0 ) { |
||
566 | $found = array_search( $rescoredResults[0], $results ); |
||
567 | if ( $found === false ) { |
||
568 | // If the first result is not in the previous array it |
||
569 | // means that we found a new exact match |
||
570 | $exactMatch = SearchSuggestion::fromTitle( 0, Title::newFromText( $rescoredResults[0] ) ); |
||
0 ignored issues
–
show
It seems like
\Title::newFromText($rescoredResults[0]) can be null ; however, fromTitle() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
571 | $suggestions->prepend( $exactMatch ); |
||
572 | $suggestions->shrink( $this->limit ); |
||
573 | } else { |
||
574 | // if the first result is not the same we need to rescore |
||
575 | if ( $found > 0 ) { |
||
576 | $suggestions->rescore( $found ); |
||
577 | } |
||
578 | } |
||
579 | } |
||
580 | |||
581 | return $suggestions; |
||
582 | } |
||
583 | |||
584 | /** |
||
585 | * Simple prefix search for subpages. |
||
586 | * @param string $search |
||
587 | * @return Title[] |
||
588 | */ |
||
589 | public function defaultPrefixSearch( $search ) { |
||
590 | if ( trim( $search ) === '' ) { |
||
591 | return []; |
||
592 | } |
||
593 | |||
594 | $search = $this->normalizeNamespaces( $search ); |
||
595 | return $this->simplePrefixSearch( $search ); |
||
596 | } |
||
597 | |||
598 | /** |
||
599 | * Call out to simple search backend. |
||
600 | * Defaults to TitlePrefixSearch. |
||
601 | * @param string $search |
||
602 | * @return Title[] |
||
603 | */ |
||
604 | protected function simplePrefixSearch( $search ) { |
||
605 | // Use default database prefix search |
||
606 | $backend = new TitlePrefixSearch; |
||
0 ignored issues
–
show
The class
TitlePrefixSearch has been deprecated with message: Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead. ![]() |
|||
607 | return $backend->defaultSearchBackend( $this->namespaces, $search, $this->limit, $this->offset ); |
||
608 | } |
||
609 | |||
610 | /** |
||
611 | * Make a list of searchable namespaces and their canonical names. |
||
612 | * @deprecated since 1.27; use SearchEngineConfig::searchableNamespaces() |
||
613 | * @return array |
||
614 | */ |
||
615 | public static function searchableNamespaces() { |
||
616 | return MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces(); |
||
617 | } |
||
618 | |||
619 | /** |
||
620 | * Extract default namespaces to search from the given user's |
||
621 | * settings, returning a list of index numbers. |
||
622 | * @deprecated since 1.27; use SearchEngineConfig::userNamespaces() |
||
623 | * @param user $user |
||
624 | * @return array |
||
625 | */ |
||
626 | public static function userNamespaces( $user ) { |
||
627 | return MediaWikiServices::getInstance()->getSearchEngineConfig()->userNamespaces( $user ); |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * An array of namespaces indexes to be searched by default |
||
632 | * @deprecated since 1.27; use SearchEngineConfig::defaultNamespaces() |
||
633 | * @return array |
||
634 | */ |
||
635 | public static function defaultNamespaces() { |
||
636 | return MediaWikiServices::getInstance()->getSearchEngineConfig()->defaultNamespaces(); |
||
637 | } |
||
638 | |||
639 | /** |
||
640 | * Get a list of namespace names useful for showing in tooltips |
||
641 | * and preferences |
||
642 | * @deprecated since 1.27; use SearchEngineConfig::namespacesAsText() |
||
643 | * @param array $namespaces |
||
644 | * @return array |
||
645 | */ |
||
646 | public static function namespacesAsText( $namespaces ) { |
||
647 | return MediaWikiServices::getInstance()->getSearchEngineConfig()->namespacesAsText( $namespaces ); |
||
648 | } |
||
649 | |||
650 | /** |
||
651 | * Load up the appropriate search engine class for the currently |
||
652 | * active database backend, and return a configured instance. |
||
653 | * @deprecated since 1.27; Use SearchEngineFactory::create |
||
654 | * @param string $type Type of search backend, if not the default |
||
655 | * @return SearchEngine |
||
656 | */ |
||
657 | public static function create( $type = null ) { |
||
658 | return MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $type ); |
||
659 | } |
||
660 | |||
661 | /** |
||
662 | * Return the search engines we support. If only $wgSearchType |
||
663 | * is set, it'll be an array of just that one item. |
||
664 | * @deprecated since 1.27; use SearchEngineConfig::getSearchTypes() |
||
665 | * @return array |
||
666 | */ |
||
667 | public static function getSearchTypes() { |
||
668 | return MediaWikiServices::getInstance()->getSearchEngineConfig()->getSearchTypes(); |
||
669 | } |
||
670 | |||
671 | /** |
||
672 | * Get a list of supported profiles. |
||
673 | * Some search engine implementations may expose specific profiles to fine-tune |
||
674 | * its behaviors. |
||
675 | * The profile can be passed as a feature data with setFeatureData( $profileType, $profileName ) |
||
676 | * The array returned by this function contains the following keys: |
||
677 | * - name: the profile name to use with setFeatureData |
||
678 | * - desc-message: the i18n description |
||
679 | * - default: set to true if this profile is the default |
||
680 | * |
||
681 | * @since 1.28 |
||
682 | * @param string $profileType the type of profiles |
||
683 | * @param User|null $user the user requesting the list of profiles |
||
684 | * @return array|null the list of profiles or null if none available |
||
685 | */ |
||
686 | public function getProfiles( $profileType, User $user = null ) { |
||
687 | return null; |
||
688 | } |
||
689 | |||
690 | /** |
||
691 | * Create a search field definition. |
||
692 | * Specific search engines should override this method to create search fields. |
||
693 | * @param string $name |
||
694 | * @param int $type One of the types in SearchIndexField::INDEX_TYPE_* |
||
695 | * @return SearchIndexField |
||
696 | * @since 1.28 |
||
697 | */ |
||
698 | public function makeSearchFieldMapping( $name, $type ) { |
||
699 | return new NullIndexField(); |
||
700 | } |
||
701 | |||
702 | /** |
||
703 | * Get fields for search index |
||
704 | * @since 1.28 |
||
705 | * @return SearchIndexField[] Index field definitions for all content handlers |
||
706 | */ |
||
707 | public function getSearchIndexFields() { |
||
708 | $models = ContentHandler::getContentModels(); |
||
709 | $fields = []; |
||
710 | foreach ( $models as $model ) { |
||
711 | $handler = ContentHandler::getForModelID( $model ); |
||
712 | $handlerFields = $handler->getFieldsForSearchIndex( $this ); |
||
713 | foreach ( $handlerFields as $fieldName => $fieldData ) { |
||
714 | if ( empty( $fields[$fieldName] ) ) { |
||
715 | $fields[$fieldName] = $fieldData; |
||
716 | } else { |
||
717 | // TODO: do we allow some clashes with the same type or reject all of them? |
||
718 | $mergeDef = $fields[$fieldName]->merge( $fieldData ); |
||
719 | if ( !$mergeDef ) { |
||
720 | throw new InvalidArgumentException( "Duplicate field $fieldName for model $model" ); |
||
721 | } |
||
722 | $fields[$fieldName] = $mergeDef; |
||
723 | } |
||
724 | } |
||
725 | } |
||
726 | // Hook to allow extensions to produce search mapping fields |
||
727 | Hooks::run( 'SearchIndexFields', [ &$fields, $this ] ); |
||
728 | return $fields; |
||
729 | } |
||
730 | |||
731 | /** |
||
732 | * Augment search results with extra data. |
||
733 | * |
||
734 | * @param SearchResultSet $resultSet |
||
735 | */ |
||
736 | public function augmentSearchResults( SearchResultSet $resultSet ) { |
||
737 | $setAugmentors = []; |
||
738 | $rowAugmentors = []; |
||
739 | Hooks::run( "SearchResultsAugment", [ &$setAugmentors, &$rowAugmentors ] ); |
||
740 | |||
741 | if ( !$setAugmentors && !$rowAugmentors ) { |
||
742 | // We're done here |
||
743 | return; |
||
744 | } |
||
745 | |||
746 | // Convert row augmentors to set augmentor |
||
747 | foreach ( $rowAugmentors as $name => $row ) { |
||
748 | if ( isset( $setAugmentors[$name] ) ) { |
||
749 | throw new InvalidArgumentException( "Both row and set augmentors are defined for $name" ); |
||
750 | } |
||
751 | $setAugmentors[$name] = new PerRowAugmentor( $row ); |
||
752 | } |
||
753 | |||
754 | foreach ( $setAugmentors as $name => $augmentor ) { |
||
755 | $data = $augmentor->augmentAll( $resultSet ); |
||
756 | if ( $data ) { |
||
757 | $resultSet->setAugmentedData( $name, $data ); |
||
758 | } |
||
759 | } |
||
760 | } |
||
761 | } |
||
762 | |||
763 | /** |
||
764 | * Dummy class to be used when non-supported Database engine is present. |
||
765 | * @todo FIXME: Dummy class should probably try something at least mildly useful, |
||
766 | * such as a LIKE search through titles. |
||
767 | * @ingroup Search |
||
768 | */ |
||
769 | class SearchEngineDummy extends SearchEngine { |
||
770 | // no-op |
||
771 | } |
||
772 |
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.