Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.67% |
29 / 30 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
FallbackMethodTrait | |
96.67% |
29 / 30 |
|
66.67% |
2 / 3 |
15 | |
0.00% |
0 / 1 |
resultsThreshold | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
resultContainsFullyHighlightedMatch | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
maybeSearchAndRewrite | |
94.12% |
16 / 17 |
|
0.00% |
0 / 1 |
7.01 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Fallbacks; |
4 | |
5 | use CirrusSearch\Parser\QueryStringRegex\SearchQueryParseException; |
6 | use CirrusSearch\Search\CirrusSearchResultSet; |
7 | use CirrusSearch\Search\SearchQuery; |
8 | use CirrusSearch\Search\SearchQueryBuilder; |
9 | use CirrusSearch\Searcher; |
10 | use Elastica\ResultSet as ElasticaResultSet; |
11 | use HtmlArmor; |
12 | use ISearchResultSet; |
13 | use MediaWiki\Logger\LoggerFactory; |
14 | |
15 | trait FallbackMethodTrait { |
16 | |
17 | /** |
18 | * Check the number of total hits stored in $resultSet |
19 | * and return true if it's greater or equals than $threshold |
20 | * NOTE: inter wiki results are check |
21 | * |
22 | * @param CirrusSearchResultSet $resultSet |
23 | * @param int $threshold (defaults to 1). |
24 | * |
25 | * @see \ISearchResultSet::getInterwikiResults() |
26 | * @see \ISearchResultSet::SECONDARY_RESULTS |
27 | * @return bool |
28 | */ |
29 | public function resultsThreshold( CirrusSearchResultSet $resultSet, $threshold = 1 ) { |
30 | if ( $resultSet->getTotalHits() >= $threshold ) { |
31 | return true; |
32 | } |
33 | foreach ( $resultSet->getInterwikiResults( ISearchResultSet::SECONDARY_RESULTS ) as $resultSet ) { |
34 | if ( $resultSet->getTotalHits() >= $threshold ) { |
35 | return true; |
36 | } |
37 | } |
38 | return false; |
39 | } |
40 | |
41 | /** |
42 | * Check if any result in the response is fully highlighted on the title field |
43 | * @param \Elastica\ResultSet $results |
44 | * @return bool true if a result has its title fully highlighted |
45 | */ |
46 | public function resultContainsFullyHighlightedMatch( ElasticaResultSet $results ) { |
47 | foreach ( $results as $result ) { |
48 | $highlights = $result->getHighlights(); |
49 | // TODO: Should we check redirects as well? |
50 | // If the whole string is highlighted then return true |
51 | $regex = '/' . Searcher::HIGHLIGHT_PRE_MARKER . '.*?' . Searcher::HIGHLIGHT_POST_MARKER . '/'; |
52 | if ( isset( $highlights[ 'title' ] ) && |
53 | !trim( preg_replace( $regex, '', $highlights[ 'title' ][ 0 ] ) ) ) { |
54 | return true; |
55 | } |
56 | } |
57 | return false; |
58 | } |
59 | |
60 | /** |
61 | * If all conditions are met execute a search query using the $suggestedQuery and returns its results. |
62 | * Conditions are: |
63 | * - SearchQuery::isAllowRewrite() must be true on the original query |
64 | * - The original query must be a simple bag of words |
65 | * - FallbackRunnerContext::costlyCallAllowed() must be true |
66 | * - number of displayable results must not exceed $resultsThreshold |
67 | * |
68 | * @param FallbackRunnerContext $context |
69 | * @param SearchQuery $originalQuery |
70 | * @param string $suggestedQuery |
71 | * @param HtmlArmor|string|null $suggestedQuerySnippet |
72 | * @param int $resultsThreshold |
73 | * @return FallbackStatus |
74 | * @throws \CirrusSearch\Parser\ParsedQueryClassifierException |
75 | * @see SearchQuery::isAllowRewrite() |
76 | * @see FallbackRunnerContext::costlyCallAllowed() |
77 | * @see FallbackMethodTrait::resultsThreshold() |
78 | */ |
79 | public function maybeSearchAndRewrite( |
80 | FallbackRunnerContext $context, |
81 | SearchQuery $originalQuery, |
82 | string $suggestedQuery, |
83 | $suggestedQuerySnippet = null, |
84 | int $resultsThreshold = 1 |
85 | ): FallbackStatus { |
86 | $previousSet = $context->getPreviousResultSet(); |
87 | if ( !$originalQuery->isAllowRewrite() |
88 | || !$context->costlyCallAllowed() |
89 | || $this->resultsThreshold( $previousSet, $resultsThreshold ) |
90 | ) { |
91 | // Only provide the suggestion, not the results of the suggestion. |
92 | return FallbackStatus::suggestQuery( $suggestedQuery, $suggestedQuerySnippet ); |
93 | } |
94 | |
95 | try { |
96 | $rewrittenQuery = SearchQueryBuilder::forRewrittenQuery( $originalQuery, $suggestedQuery, |
97 | $context->getNamespacePrefixParser(), $context->getCirrusSearchHookRunner() )->build(); |
98 | } catch ( SearchQueryParseException $e ) { |
99 | LoggerFactory::getInstance( 'CirrusSearch' ) |
100 | ->warning( "Cannot parse rewritten query", [ 'exception' => $e ] ); |
101 | // Suggest the user submits the suggested query directly |
102 | return FallbackStatus::suggestQuery( $suggestedQuery, $suggestedQuerySnippet ); |
103 | } |
104 | $searcher = $context->makeSearcher( $rewrittenQuery ); |
105 | $status = $searcher->search( $rewrittenQuery ); |
106 | if ( $status->isOK() && $status->getValue() instanceof CirrusSearchResultSet ) { |
107 | /** |
108 | * @var CirrusSearchResultSet $newResults |
109 | */ |
110 | $newResults = $status->getValue(); |
111 | return FallbackStatus::replaceLocalResults( $newResults, $suggestedQuery, $suggestedQuerySnippet ); |
112 | } else { |
113 | // The suggested query returned no results, there doesn't seem to be any benefit |
114 | // to sugesting it to the user. |
115 | return FallbackStatus::noSuggestion(); |
116 | } |
117 | } |
118 | } |