Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
57.78% |
26 / 45 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
PrefixSearchQueryBuilder | |
57.78% |
26 / 45 |
|
0.00% |
0 / 3 |
15.10 | |
0.00% |
0 / 1 |
build | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
4.02 | |||
wordPrefixQuery | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
keywordPrefixQuery | |
85.00% |
17 / 20 |
|
0.00% |
0 / 1 |
3.03 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Query; |
4 | |
5 | use CirrusSearch\Profile\SearchProfileService; |
6 | use CirrusSearch\Search\SearchContext; |
7 | use Elastica\Query\BoolQuery; |
8 | use Elastica\Query\MatchQuery; |
9 | use Elastica\Query\MultiMatch; |
10 | |
11 | /** |
12 | * Build a query suited for autocomplete on titles+redirects |
13 | */ |
14 | class PrefixSearchQueryBuilder { |
15 | use QueryBuilderTraits; |
16 | |
17 | /** |
18 | * @param SearchContext $searchContext |
19 | * @param string $term the original search term |
20 | * @param array|null $variants list of variants |
21 | */ |
22 | public function build( SearchContext $searchContext, $term, $variants = null ) { |
23 | if ( !$this->checkTitleSearchRequestLength( $term, $searchContext ) ) { |
24 | return; |
25 | } |
26 | $searchContext->setOriginalSearchTerm( $term ); |
27 | $searchContext->setProfileContext( SearchProfileService::CONTEXT_PREFIXSEARCH ); |
28 | $searchContext->addSyntaxUsed( 'prefix' ); |
29 | if ( strlen( $term ) > 0 ) { |
30 | if ( $searchContext->getConfig()->get( 'CirrusSearchPrefixSearchStartsWithAnyWord' ) ) { |
31 | $searchContext->addFilter( $this->wordPrefixQuery( $term, $variants ) ); |
32 | } else { |
33 | // TODO: weights should be a profile? |
34 | $weights = $searchContext->getConfig()->get( 'CirrusSearchPrefixWeights' ); |
35 | $searchContext->setMainQuery( $this->keywordPrefixQuery( $term, $variants, $weights ) ); |
36 | } |
37 | } |
38 | } |
39 | |
40 | private function wordPrefixQuery( $term, $variants ) { |
41 | $buildMatch = static function ( $searchTerm ) { |
42 | $match = new MatchQuery(); |
43 | // TODO: redirect.title? |
44 | $match->setField( 'title.word_prefix', [ |
45 | 'query' => $searchTerm, |
46 | 'analyzer' => 'plain', |
47 | 'operator' => 'and', |
48 | ] ); |
49 | return $match; |
50 | }; |
51 | $query = new BoolQuery(); |
52 | $query->setMinimumShouldMatch( 1 ); |
53 | $query->addShould( $buildMatch( $term ) ); |
54 | foreach ( $variants as $variant ) { |
55 | // This is a filter we don't really care about |
56 | // discounting variant matches. |
57 | $query->addShould( $buildMatch( $variant ) ); |
58 | } |
59 | return $query; |
60 | } |
61 | |
62 | private function keywordPrefixQuery( $term, $variants, $weights ) { |
63 | // Elasticsearch seems to have trouble extracting the proper terms to highlight |
64 | // from the default query we make so we feed it exactly the right query to highlight. |
65 | $buildMatch = static function ( $searchTerm, $weight ) use ( $weights ) { |
66 | $query = new MultiMatch(); |
67 | $query->setQuery( $searchTerm ); |
68 | $query->setFields( [ |
69 | 'title.prefix^' . ( $weights['title'] * $weight ), |
70 | 'redirect.title.prefix^' . ( $weights['redirect'] * $weight ), |
71 | 'title.prefix_asciifolding^' . ( $weights['title_asciifolding'] * $weight ), |
72 | 'redirect.title.prefix_asciifolding^' . ( $weights['redirect_asciifolding'] * $weight ), |
73 | ] ); |
74 | return $query; |
75 | }; |
76 | $query = new BoolQuery(); |
77 | $query->setMinimumShouldMatch( 1 ); |
78 | $weight = 1; |
79 | $query->addShould( $buildMatch( $term, $weight ) ); |
80 | if ( $variants ) { |
81 | foreach ( $variants as $variant ) { |
82 | $weight *= 0.2; |
83 | $query->addShould( $buildMatch( $variant, $weight ) ); |
84 | } |
85 | } |
86 | return $query; |
87 | } |
88 | } |