Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.16% covered (warning)
63.16%
24 / 38
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
PrefixSearchQueryBuilder
63.16% covered (warning)
63.16%
24 / 38
0.00% covered (danger)
0.00%
0 / 3
13.05
0.00% covered (danger)
0.00%
0 / 1
 build
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 wordPrefixQuery
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 keywordPrefixQuery
83.33% covered (warning)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
3.04
1<?php
2
3namespace CirrusSearch\Query;
4
5use CirrusSearch\Profile\SearchProfileService;
6use CirrusSearch\Search\SearchContext;
7use Elastica\Query\BoolQuery;
8use Elastica\Query\MatchQuery;
9use Elastica\Query\MultiMatch;
10
11/**
12 * Build a query suited for autocomplete on titles+redirects
13 */
14class 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}