Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
FormSearchEntity
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 3
56
0.00% covered (danger)
0.00%
0 / 1
 getElasticSearchQuery
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
30
 makeResultType
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getRankedSearchResults
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace Wikibase\Lexeme\Search\Elastic;
3
4use CirrusSearch\Search\ResultsType;
5use CirrusSearch\Search\SearchContext;
6use Elastica\Query\AbstractQuery;
7use Elastica\Query\BoolQuery;
8use Elastica\Query\DisMax;
9use Elastica\Query\MatchNone;
10use Elastica\Query\MatchQuery;
11use Elastica\Query\Term;
12use Wikibase\Lexeme\MediaWiki\Content\LexemeContent;
13use Wikibase\Search\Elastic\EntitySearchElastic;
14use Wikibase\Search\Elastic\EntitySearchUtils;
15
16/**
17 * Search for entity of type form.
18 *
19 * @license GPL-2.0-or-later
20 * @author Stas Malyshev
21 */
22class FormSearchEntity extends LexemeSearchEntity {
23    /**
24     * Search limit.
25     * @var int
26     */
27    private $limit;
28
29    /**
30     * Produce ES query that matches the arguments.
31     * This is search for forms - matches only form representations
32     * but not lexemes.
33     *
34     * @param string $text
35     * @param string $entityType
36     * @param SearchContext $context
37     *
38     * @return AbstractQuery
39     */
40    protected function getElasticSearchQuery(
41        $text,
42        $entityType,
43        SearchContext $context
44    ) {
45        $context->setOriginalSearchTerm( $text );
46        // TODO consider using Form::ENTITY_TYPE
47        if ( $entityType !== 'form' ) {
48            $context->setResultsPossible( false );
49            $context->addWarning( 'wikibase-search-bad-entity-type', $entityType );
50            return new MatchNone();
51        }
52        // Drop only leading spaces for exact matches, and all spaces for the rest
53        $textExact = ltrim( $text );
54        $text = trim( $text );
55
56        $labelsFilter = new MatchQuery( 'lexeme_forms.representation.prefix', $text );
57
58        $profile = $context->getConfig()
59            ->getProfileService()
60            ->loadProfile( EntitySearchElastic::WIKIBASE_PREFIX_QUERY_BUILDER,
61                self::CONTEXT_LEXEME_PREFIX );
62
63        $dismax = new DisMax();
64        $dismax->setTieBreaker( $profile['tie-breaker'] ?? 0 );
65
66        $fields = [
67            [
68                "lexeme_forms.representation.near_match",
69                $profile['exact'] * $profile['form-discount'],
70            ],
71            [
72                "lexeme_forms.representation.near_match_folded",
73                $profile['folded'] * $profile['form-discount'],
74            ],
75        ];
76        // Fields to which query applies exactly as stated, without trailing space trimming
77        $fieldsExact = [];
78        if ( $textExact !== $text ) {
79            $fields[] =
80                [
81                    "lexeme_forms.representation.prefix",
82                    $profile['prefix'] * $profile['space-discount'] * $profile['form-discount'],
83                ];
84            $fieldsExact[] =
85                [
86                    "lexeme_forms.representation.prefix",
87                    $profile['prefix'] * $profile['form-discount'],
88                ];
89        } else {
90            $fields[] =
91                [
92                    "lexeme_forms.representation.prefix",
93                    $profile['prefix'] * $profile['form-discount'],
94                ];
95        }
96
97        foreach ( $fields as $field ) {
98            $dismax->addQuery( EntitySearchUtils::makeConstScoreQuery( $field[0], $field[1],
99                $text ) );
100        }
101
102        foreach ( $fieldsExact as $field ) {
103            $dismax->addQuery( EntitySearchUtils::makeConstScoreQuery( $field[0], $field[1],
104                $textExact ) );
105        }
106
107        $labelsQuery = new BoolQuery();
108        $labelsQuery->addFilter( $labelsFilter );
109        $labelsQuery->addShould( $dismax );
110        // lexeme_forms.id is a lowercase_keyword so use Match to apply the analyzer
111        $titleMatch = new MatchQuery( 'lexeme_forms.id',
112            EntitySearchUtils::normalizeId( $text, $this->idParser ) );
113
114        $query = new BoolQuery();
115        // Match either labels or exact match to title
116        $query->addShould( $labelsQuery );
117        $query->addShould( $titleMatch );
118        $query->setMinimumShouldMatch( 1 );
119
120        // Filter to fetch only given entity type
121        $query->addFilter( new Term( [ 'content_model' => LexemeContent::CONTENT_MODEL_ID ] ) );
122
123        return $query;
124    }
125
126    /**
127     * Get results type object for this search.
128     * @return ResultsType
129     */
130    protected function makeResultType() {
131        return new FormTermResult(
132            $this->idParser,
133            $this->userLanguage,
134            $this->lookupFactory,
135            $this->limit
136        );
137    }
138
139    /**
140     * @inheritDoc
141     */
142    public function getRankedSearchResults(
143        $text,
144        $languageCode,
145        $entityType,
146        $limit,
147        $strictLanguage,
148        ?string $profileContext = null
149    ) {
150        // We need to keep the limit since one document can produce several matches.
151        $this->limit = $limit;
152        return parent::getRankedSearchResults( $text, $languageCode, $entityType, $limit,
153            $strictLanguage, $profileContext );
154    }
155
156}