Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.55% |
68 / 69 |
|
91.67% |
11 / 12 |
CRAP | |
0.00% |
0 / 1 |
FetchPhaseConfigBuilder | |
98.55% |
68 / 69 |
|
91.67% |
11 / 12 |
23 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
newHighlightField | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
addNewRegexHLField | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
supportsRegexFields | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newRegexField | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
addHLField | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getHLField | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
buildHLConfig | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
4 | |||
withConfig | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHLFieldsPerTargetAndPriority | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
configureDefaultFullTextFields | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
clearSkipIfLastMatched | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Search\Fetch; |
4 | |
5 | use CirrusSearch\SearchConfig; |
6 | use CirrusSearch\Searcher; |
7 | use Elastica\Query\AbstractQuery; |
8 | use Wikimedia\Assert\Assert; |
9 | |
10 | /** |
11 | * Class holding the building state of the fetch phase elements of |
12 | * an elasticsearch query. |
13 | * Currently only supports the highlight section but can be extended to support |
14 | * source filtering and stored field. |
15 | */ |
16 | class FetchPhaseConfigBuilder implements HighlightFieldGenerator { |
17 | |
18 | /** @var HighlightedField[] */ |
19 | private $highlightedFields = []; |
20 | |
21 | /** @var SearchConfig */ |
22 | private $config; |
23 | |
24 | /** |
25 | * @var string |
26 | */ |
27 | private $factoryGroup; |
28 | |
29 | /** |
30 | * @var bool |
31 | */ |
32 | private $provideAllSnippets; |
33 | |
34 | /** |
35 | * @param SearchConfig $config |
36 | * @param string|null $factoryGroup |
37 | * @param bool $provideAllSnippets |
38 | */ |
39 | public function __construct( |
40 | SearchConfig $config, |
41 | $factoryGroup = null, |
42 | bool $provideAllSnippets = false |
43 | ) { |
44 | $this->config = $config; |
45 | $this->factoryGroup = $factoryGroup; |
46 | $this->provideAllSnippets = $provideAllSnippets; |
47 | } |
48 | |
49 | /** |
50 | * @inheritDoc |
51 | */ |
52 | public function newHighlightField( |
53 | $name, |
54 | $target, |
55 | $priority = HighlightedField::DEFAULT_TARGET_PRIORITY |
56 | ): BaseHighlightedField { |
57 | $useExp = $this->config->get( 'CirrusSearchUseExperimentalHighlighter' ); |
58 | if ( $useExp ) { |
59 | $factories = ExperimentalHighlightedFieldBuilder::getFactories(); |
60 | } else { |
61 | $factories = BaseHighlightedField::getFactories(); |
62 | } |
63 | if ( $this->factoryGroup !== null && isset( $factories[$this->factoryGroup][$name] ) ) { |
64 | return ( $factories[$this->factoryGroup][$name] )( $this->config, $name, $target, $priority ); |
65 | } |
66 | if ( $useExp ) { |
67 | return new ExperimentalHighlightedFieldBuilder( $name, $target, $priority ); |
68 | } else { |
69 | return new BaseHighlightedField( $name, BaseHighlightedField::FVH_HL_TYPE, $target, $priority ); |
70 | } |
71 | } |
72 | |
73 | /** |
74 | * @param string $name |
75 | * @param string $target |
76 | * @param string $pattern |
77 | * @param bool $caseInsensitive |
78 | * @param int $priority |
79 | */ |
80 | public function addNewRegexHLField( |
81 | $name, |
82 | $target, |
83 | $pattern, |
84 | $caseInsensitive, |
85 | $priority = HighlightedField::COSTLY_EXPERT_SYNTAX_PRIORITY |
86 | ) { |
87 | if ( !$this->supportsRegexFields() ) { |
88 | return; |
89 | } |
90 | $this->addHLField( $this->newRegexField( $name, $target, $pattern, $caseInsensitive, $priority ) ); |
91 | } |
92 | |
93 | /** |
94 | * Whether this builder can generate regex fields |
95 | * @return bool |
96 | */ |
97 | public function supportsRegexFields() { |
98 | return (bool)$this->config->get( 'CirrusSearchUseExperimentalHighlighter' ); |
99 | } |
100 | |
101 | /** |
102 | * @inheritDoc |
103 | */ |
104 | public function newRegexField( |
105 | $name, |
106 | $target, |
107 | $pattern, |
108 | $caseInsensitive, |
109 | $priority = HighlightedField::COSTLY_EXPERT_SYNTAX_PRIORITY |
110 | ): BaseHighlightedField { |
111 | Assert::precondition( $this->supportsRegexFields(), 'Regex fields not supported' ); |
112 | return ExperimentalHighlightedFieldBuilder::newRegexField( |
113 | $this->config, $name, $target, $pattern, $caseInsensitive, $priority ); |
114 | } |
115 | |
116 | /** |
117 | * @param HighlightedField $field |
118 | */ |
119 | public function addHLField( HighlightedField $field ) { |
120 | $prev = $this->highlightedFields[$field->getFieldName()] ?? null; |
121 | if ( $prev === null ) { |
122 | $this->highlightedFields[$field->getFieldName()] = $field; |
123 | } else { |
124 | $this->highlightedFields[$field->getFieldName()] = $prev->merge( $field ); |
125 | } |
126 | } |
127 | |
128 | /** |
129 | * @param string $field |
130 | * @return HighlightedField|null |
131 | */ |
132 | public function getHLField( $field ) { |
133 | return $this->highlightedFields[$field] ?? null; |
134 | } |
135 | |
136 | /** |
137 | * @param AbstractQuery|null $mainHLQuery |
138 | * @return array |
139 | */ |
140 | public function buildHLConfig( ?AbstractQuery $mainHLQuery = null ): array { |
141 | $fields = []; |
142 | foreach ( $this->highlightedFields as $field ) { |
143 | $arr = $field->toArray(); |
144 | if ( $this->provideAllSnippets ) { |
145 | $arr = $this->clearSkipIfLastMatched( $arr ); |
146 | } |
147 | $fields[$field->getFieldName()] = $arr; |
148 | } |
149 | $config = [ |
150 | 'pre_tags' => [ Searcher::HIGHLIGHT_PRE_MARKER ], |
151 | 'post_tags' => [ Searcher::HIGHLIGHT_POST_MARKER ], |
152 | 'fields' => $fields, |
153 | ]; |
154 | |
155 | if ( $mainHLQuery !== null ) { |
156 | $config['highlight_query'] = $mainHLQuery->toArray(); |
157 | } |
158 | |
159 | return $config; |
160 | } |
161 | |
162 | /** |
163 | * @param SearchConfig $config |
164 | * @return FetchPhaseConfigBuilder |
165 | */ |
166 | public function withConfig( SearchConfig $config ): self { |
167 | return new self( $config, $this->factoryGroup ); |
168 | } |
169 | |
170 | /** |
171 | * Return the list of highlighted fields indexed per target |
172 | * and ordered by priority (reverse natural order) |
173 | * @return HighlightedField[][] |
174 | */ |
175 | public function getHLFieldsPerTargetAndPriority(): array { |
176 | $fields = []; |
177 | foreach ( $this->highlightedFields as $f ) { |
178 | $fields[$f->getTarget()][] = $f; |
179 | } |
180 | return array_map( |
181 | static function ( array $v ) { |
182 | usort( $v, static function ( HighlightedField $g1, HighlightedField $g2 ) { |
183 | return $g2->getPriority() <=> $g1->getPriority(); |
184 | } ); |
185 | return $v; |
186 | }, |
187 | $fields |
188 | ); |
189 | } |
190 | |
191 | public function configureDefaultFullTextFields() { |
192 | // TODO: find a better place for this |
193 | // Title/redir/category/template |
194 | $field = $this->newHighlightField( 'title', HighlightedField::TARGET_TITLE_SNIPPET ); |
195 | $this->addHLField( $field ); |
196 | $field = $this->newHighlightField( 'redirect.title', HighlightedField::TARGET_REDIRECT_SNIPPET ); |
197 | $this->addHLField( $field->skipIfLastMatched() ); |
198 | $field = $this->newHighlightField( 'category', HighlightedField::TARGET_CATEGORY_SNIPPET ); |
199 | $this->addHLField( $field->skipIfLastMatched() ); |
200 | |
201 | $field = $this->newHighlightField( 'heading', HighlightedField::TARGET_SECTION_SNIPPET ); |
202 | $this->addHLField( $field->skipIfLastMatched() ); |
203 | |
204 | // content |
205 | $field = $this->newHighlightField( 'text', HighlightedField::TARGET_MAIN_SNIPPET ); |
206 | $this->addHLField( $field ); |
207 | |
208 | $field = $this->newHighlightField( 'auxiliary_text', HighlightedField::TARGET_MAIN_SNIPPET ); |
209 | $this->addHLField( $field->skipIfLastMatched() ); |
210 | |
211 | $field = $this->newHighlightField( 'file_text', HighlightedField::TARGET_MAIN_SNIPPET ); |
212 | $this->addHLField( $field->skipIfLastMatched() ); |
213 | } |
214 | |
215 | private function clearSkipIfLastMatched( array $arr ) { |
216 | unset( $arr['options']['skip_if_last_matched'] ); |
217 | if ( empty( $arr['options'] ) ) { |
218 | unset( $arr['options'] ); |
219 | } |
220 | return $arr; |
221 | } |
222 | } |