Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
18.52% |
10 / 54 |
|
20.83% |
5 / 24 |
CRAP | |
0.00% |
0 / 1 |
ResultSet | |
18.52% |
10 / 54 |
|
20.83% |
5 / 24 |
697.69 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
setSuggestionQuery | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getTotalHits | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
preCacheContainedTitles | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
hasSuggestion | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSuggestionQuery | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSuggestionSnippet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
extractResults | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
30 | |||
transformOneResult | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addInterwikiResults | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getInterwikiResults | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasInterwikiResults | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setRewrittenQuery | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
hasRewrittenQuery | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQueryAfterRewrite | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQueryAfterRewriteSnippet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getElasticResponse | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getElasticaResultSet | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
count | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
numRows | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
searchContainedSyntax | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasMoreResults | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
shrink | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
extractTitles | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Search; |
4 | |
5 | use BaseSearchResultSet; |
6 | use HtmlArmor; |
7 | use ISearchResultSet; |
8 | use MediaWiki\MediaWikiServices; |
9 | use MediaWiki\Title\Title; |
10 | use SearchResult; |
11 | use SearchResultSetTrait; |
12 | use Wikimedia\Assert\Assert; |
13 | |
14 | /** |
15 | * A set of results from Elasticsearch. |
16 | * Extending this class from another extension is not supported, use BaseCirrusSearchResultSet |
17 | * |
18 | * This program is free software; you can redistribute it and/or modify |
19 | * it under the terms of the GNU General Public License as published by |
20 | * the Free Software Foundation; either version 2 of the License, or |
21 | * (at your option) any later version. |
22 | * |
23 | * This program is distributed in the hope that it will be useful, |
24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
26 | * GNU General Public License for more details. |
27 | * |
28 | * You should have received a copy of the GNU General Public License along |
29 | * with this program; if not, write to the Free Software Foundation, Inc., |
30 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
31 | * http://www.gnu.org/copyleft/gpl.html |
32 | * @deprecated use a subclass of BaseCirrusSearchResultSet |
33 | */ |
34 | class ResultSet extends BaseSearchResultSet implements CirrusSearchResultSet { |
35 | use SearchResultSetTrait; |
36 | |
37 | /** |
38 | * @var \Elastica\ResultSet |
39 | */ |
40 | private $result; |
41 | |
42 | /** |
43 | * @var string|null |
44 | */ |
45 | private $suggestionQuery; |
46 | |
47 | /** |
48 | * @var HtmlArmor|string|null |
49 | */ |
50 | private $suggestionSnippet; |
51 | |
52 | /** |
53 | * @var array |
54 | */ |
55 | private $interwikiResults = []; |
56 | |
57 | /** |
58 | * @var string|null |
59 | */ |
60 | private $rewrittenQuery; |
61 | |
62 | /** |
63 | * @var HtmlArmor|string|null |
64 | */ |
65 | private $rewrittenQuerySnippet; |
66 | /** |
67 | * @var SearchResult[] |
68 | */ |
69 | protected $results; |
70 | |
71 | /** |
72 | * @var bool |
73 | */ |
74 | private $hasMoreResults = false; |
75 | |
76 | /** |
77 | * @var bool |
78 | */ |
79 | private $searchContainedSyntax; |
80 | |
81 | /** |
82 | * @var FullTextCirrusSearchResultBuilder |
83 | */ |
84 | private $resultBuilder; |
85 | |
86 | /** |
87 | * @var TitleHelper |
88 | */ |
89 | private $titleHelper; |
90 | |
91 | /** |
92 | * @param bool $searchContainedSyntax |
93 | * @param \Elastica\ResultSet|null $elasticResultSet |
94 | * @param TitleHelper|null $titleHelper |
95 | * @deprecated use a subclass of BaseCirrusSearchResultSet |
96 | */ |
97 | public function __construct( |
98 | $searchContainedSyntax = false, |
99 | ?\Elastica\ResultSet $elasticResultSet = null, |
100 | ?TitleHelper $titleHelper = null |
101 | ) { |
102 | $this->searchContainedSyntax = $searchContainedSyntax; |
103 | $this->result = $elasticResultSet; |
104 | $this->titleHelper = $titleHelper ?: new TitleHelper(); |
105 | $this->resultBuilder = new FullTextCirrusSearchResultBuilder( $this->titleHelper, [] ); |
106 | } |
107 | |
108 | /** |
109 | * @param string $suggestionQuery |
110 | * @param HtmlArmor|string|null $suggestionSnippet |
111 | */ |
112 | public function setSuggestionQuery( string $suggestionQuery, $suggestionSnippet = null ) { |
113 | $this->suggestionQuery = $suggestionQuery; |
114 | $this->suggestionSnippet = $suggestionSnippet ?? $suggestionQuery; |
115 | } |
116 | |
117 | /** |
118 | * Some search modes return a total hit count for the query |
119 | * in the entire article database. This may include pages |
120 | * in namespaces that would not be matched on the given |
121 | * settings. |
122 | * |
123 | * Return null if no total hits number is supported. |
124 | * |
125 | * @return int|null |
126 | */ |
127 | public function getTotalHits() { |
128 | $elasticaResultSet = $this->getElasticaResultSet(); |
129 | if ( $elasticaResultSet !== null ) { |
130 | return $elasticaResultSet->getTotalHits(); |
131 | } |
132 | return 0; |
133 | } |
134 | |
135 | /** |
136 | * Loads the result set into the mediawiki LinkCache via a |
137 | * batch query. By pre-caching this we ensure methods such as |
138 | * Result::isMissingRevision() don't trigger a query for each and |
139 | * every search result. |
140 | * |
141 | * @param \Elastica\ResultSet $resultSet Result set from which the titles come |
142 | */ |
143 | protected function preCacheContainedTitles( \Elastica\ResultSet $resultSet ) { |
144 | // We can only pull in information about the local wiki |
145 | $lb = MediaWikiServices::getInstance()->getLinkBatchFactory()->newLinkBatch(); |
146 | foreach ( $resultSet->getResults() as $result ) { |
147 | if ( !$this->titleHelper->isExternal( $result ) ) { |
148 | $lb->add( $result->namespace, $result->title ); |
149 | } |
150 | } |
151 | if ( !$lb->isEmpty() ) { |
152 | $lb->setCaller( __METHOD__ ); |
153 | $lb->execute(); |
154 | } |
155 | } |
156 | |
157 | /** |
158 | * @return bool |
159 | */ |
160 | public function hasSuggestion() { |
161 | return $this->suggestionQuery !== null; |
162 | } |
163 | |
164 | /** |
165 | * @return string|null |
166 | */ |
167 | public function getSuggestionQuery() { |
168 | return $this->suggestionQuery; |
169 | } |
170 | |
171 | /** |
172 | * @return HtmlArmor|string|null Null is only returned if suggestion query is also null |
173 | */ |
174 | public function getSuggestionSnippet() { |
175 | return $this->suggestionSnippet; |
176 | } |
177 | |
178 | public function extractResults() { |
179 | if ( $this->results === null ) { |
180 | $this->results = []; |
181 | if ( $this->result !== null ) { |
182 | $this->preCacheContainedTitles( $this->result ); |
183 | foreach ( $this->result->getResults() as $result ) { |
184 | $transformed = $this->transformOneResult( $result ); |
185 | if ( $transformed != null ) { |
186 | $this->augmentResult( $transformed ); |
187 | $this->results[] = $transformed; |
188 | } |
189 | } |
190 | } |
191 | } |
192 | return $this->results; |
193 | } |
194 | |
195 | /** |
196 | * @param \Elastica\Result $result Result from search engine |
197 | * @return CirrusSearchResult Elasticsearch result transformed into mediawiki |
198 | * search result object. |
199 | */ |
200 | protected function transformOneResult( \Elastica\Result $result ) { |
201 | return $this->resultBuilder->build( $result ); |
202 | } |
203 | |
204 | /** |
205 | * @param CirrusSearchResultSet $res |
206 | * @param int $type One of SearchResultSet::* constants |
207 | * @param string $interwiki |
208 | */ |
209 | public function addInterwikiResults( CirrusSearchResultSet $res, $type, $interwiki ) { |
210 | $this->interwikiResults[$type][$interwiki] = $res; |
211 | } |
212 | |
213 | /** |
214 | * @param int $type |
215 | * @return ISearchResultSet[] |
216 | */ |
217 | public function getInterwikiResults( $type = self::SECONDARY_RESULTS ) { |
218 | return $this->interwikiResults[$type] ?? []; |
219 | } |
220 | |
221 | /** |
222 | * @param int $type |
223 | * @return bool |
224 | */ |
225 | public function hasInterwikiResults( $type = self::SECONDARY_RESULTS ) { |
226 | return isset( $this->interwikiResults[$type] ); |
227 | } |
228 | |
229 | /** |
230 | * @param string $newQuery |
231 | * @param HtmlArmor|string|null $newQuerySnippet |
232 | */ |
233 | public function setRewrittenQuery( string $newQuery, $newQuerySnippet = null ) { |
234 | $this->rewrittenQuery = $newQuery; |
235 | $this->rewrittenQuerySnippet = $newQuerySnippet ?? $newQuery; |
236 | } |
237 | |
238 | /** |
239 | * @return bool |
240 | */ |
241 | public function hasRewrittenQuery() { |
242 | return $this->rewrittenQuery !== null; |
243 | } |
244 | |
245 | /** |
246 | * @return string|null |
247 | */ |
248 | public function getQueryAfterRewrite() { |
249 | return $this->rewrittenQuery; |
250 | } |
251 | |
252 | /** |
253 | * @return HtmlArmor|string|null |
254 | */ |
255 | public function getQueryAfterRewriteSnippet() { |
256 | return $this->rewrittenQuerySnippet; |
257 | } |
258 | |
259 | /** |
260 | * @return \Elastica\Response|null |
261 | */ |
262 | public function getElasticResponse() { |
263 | return $this->result != null ? $this->result->getResponse() : null; |
264 | } |
265 | |
266 | /** |
267 | * @return \Elastica\ResultSet|null |
268 | */ |
269 | public function getElasticaResultSet() { |
270 | return $this->result; |
271 | } |
272 | |
273 | /** |
274 | * Count elements of an object |
275 | * @link https://php.net/manual/en/countable.count.php |
276 | * @return int The custom count as an integer. |
277 | * @since 5.1.0 |
278 | */ |
279 | public function count(): int { |
280 | return count( $this->extractResults() ); |
281 | } |
282 | |
283 | /** |
284 | * @return int |
285 | */ |
286 | public function numRows() { |
287 | return $this->count(); |
288 | } |
289 | |
290 | /** |
291 | * Did the search contain search syntax? If so, Special:Search won't offer |
292 | * the user a link to a create a page named by the search string because the |
293 | * name would contain the search syntax. |
294 | * @return bool |
295 | */ |
296 | public function searchContainedSyntax() { |
297 | return $this->searchContainedSyntax; |
298 | } |
299 | |
300 | /** |
301 | * @return bool True when there are more pages of search results available. |
302 | */ |
303 | public function hasMoreResults() { |
304 | return $this->hasMoreResults; |
305 | } |
306 | |
307 | /** |
308 | * @param int $limit Shrink result set to $limit and flag |
309 | * if more results are available. |
310 | */ |
311 | public function shrink( $limit ) { |
312 | if ( $this->count() > $limit ) { |
313 | Assert::precondition( $this->results !== null, "results not initialized" ); |
314 | $this->results = array_slice( $this->results, 0, $limit ); |
315 | $this->hasMoreResults = true; |
316 | } |
317 | } |
318 | |
319 | /** |
320 | * Extract all the titles in the result set. |
321 | * @return Title[] |
322 | */ |
323 | public function extractTitles() { |
324 | return array_map( |
325 | static function ( SearchResult $result ) { |
326 | return $result->getTitle(); |
327 | }, |
328 | $this->extractResults() ); |
329 | } |
330 | } |