b8e40d5c5b0be58565ced390c2ad219169f47b2e
[lhc/web/wiklou.git] / includes / search / SearchResultSet.php
1 <?php
2 /**
3 * Search result sets
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Search
22 */
23
24 /**
25 * @ingroup Search
26 */
27 class SearchResultSet extends BaseSearchResultSet {
28
29 protected $containedSyntax = false;
30
31 /**
32 * Cache of titles.
33 * Lists titles of the result set, in the same order as results.
34 * @var Title[]
35 */
36 private $titles;
37
38 /**
39 * Cache of results - serialization of the result iterator
40 * as an array.
41 * @var SearchResult[]
42 */
43 protected $results;
44
45 /**
46 * @var boolean True when there are more pages of search results available.
47 */
48 private $hasMoreResults;
49
50 /**
51 * @param bool $containedSyntax True when query is not requesting a simple
52 * term match
53 * @param bool $hasMoreResults True when there are more pages of search
54 * results available.
55 */
56 public function __construct( $containedSyntax = false, $hasMoreResults = false ) {
57 if ( static::class === self::class ) {
58 // This class will eventually be abstract. SearchEngine implementations
59 // already have to extend this class anyways to provide the actual
60 // search results.
61 wfDeprecated( __METHOD__, '1.32' );
62 }
63 $this->containedSyntax = $containedSyntax;
64 $this->hasMoreResults = $hasMoreResults;
65 }
66
67 public function numRows() {
68 return $this->count();
69 }
70
71 final public function count() {
72 return count( $this->extractResults() );
73 }
74
75 /**
76 * Some search modes return a total hit count for the query
77 * in the entire article database. This may include pages
78 * in namespaces that would not be matched on the given
79 * settings.
80 *
81 * Return null if no total hits number is supported.
82 *
83 * @return int
84 */
85 public function getTotalHits() {
86 return null;
87 }
88
89 /**
90 * Some search modes will run an alternative query that it thinks gives
91 * a better result than the provided search. Returns true if this has
92 * occurred.
93 *
94 * @return bool
95 */
96 public function hasRewrittenQuery() {
97 return false;
98 }
99
100 /**
101 * @return string|null The search the query was internally rewritten to,
102 * or null when the result of the original query was returned.
103 */
104 public function getQueryAfterRewrite() {
105 return null;
106 }
107
108 /**
109 * @return string|null Same as self::getQueryAfterRewrite(), but in HTML
110 * and with changes highlighted. Null when the query was not rewritten.
111 */
112 public function getQueryAfterRewriteSnippet() {
113 return null;
114 }
115
116 /**
117 * Some search modes return a suggested alternate term if there are
118 * no exact hits. Returns true if there is one on this set.
119 *
120 * @return bool
121 */
122 public function hasSuggestion() {
123 return false;
124 }
125
126 /**
127 * @return string|null Suggested query, null if none
128 */
129 public function getSuggestionQuery() {
130 return null;
131 }
132
133 /**
134 * @return string HTML highlighted suggested query, '' if none
135 */
136 public function getSuggestionSnippet() {
137 return '';
138 }
139
140 /**
141 * Return a result set of hits on other (multiple) wikis associated with this one
142 *
143 * @param int $type
144 * @return ISearchResultSet[]
145 */
146 public function getInterwikiResults( $type = self::SECONDARY_RESULTS ) {
147 return null;
148 }
149
150 /**
151 * Check if there are results on other wikis
152 *
153 * @param int $type
154 * @return bool
155 */
156 public function hasInterwikiResults( $type = self::SECONDARY_RESULTS ) {
157 return false;
158 }
159
160 /**
161 * Did the search contain search syntax? If so, Special:Search won't offer
162 * the user a link to a create a page named by the search string because the
163 * name would contain the search syntax.
164 * @return bool
165 */
166 public function searchContainedSyntax() {
167 return $this->containedSyntax;
168 }
169
170 /**
171 * @return bool True when there are more pages of search results available.
172 */
173 public function hasMoreResults() {
174 return $this->hasMoreResults;
175 }
176
177 /**
178 * @param int $limit Shrink result set to $limit and flag
179 * if more results are available.
180 */
181 public function shrink( $limit ) {
182 if ( $this->count() > $limit ) {
183 $this->hasMoreResults = true;
184 // shrinking result set for implementations that
185 // have not implemented extractResults and use
186 // the default cache location. Other implementations
187 // must override this as well.
188 if ( is_array( $this->results ) ) {
189 $this->results = array_slice( $this->results, 0, $limit );
190 } else {
191 throw new \UnexpectedValueException(
192 "When overriding result store extending classes must "
193 . " also override " . __METHOD__ );
194 }
195 }
196 }
197
198 /**
199 * Extract all the results in the result set as array.
200 * @return SearchResult[]
201 */
202 public function extractResults() {
203 if ( is_null( $this->results ) ) {
204 $this->results = [];
205 if ( $this->numRows() == 0 ) {
206 // Don't bother if we've got empty result
207 return $this->results;
208 }
209 $this->rewind();
210 while ( ( $result = $this->next() ) != false ) {
211 $this->results[] = $result;
212 }
213 $this->rewind();
214 }
215 return $this->results;
216 }
217
218 /**
219 * Extract all the titles in the result set.
220 * @return Title[]
221 */
222 public function extractTitles() {
223 if ( is_null( $this->titles ) ) {
224 if ( $this->numRows() == 0 ) {
225 // Don't bother if we've got empty result
226 $this->titles = [];
227 } else {
228 $this->titles = array_map(
229 function ( SearchResult $result ) {
230 return $result->getTitle();
231 },
232 $this->extractResults() );
233 }
234 }
235 return $this->titles;
236 }
237
238 /**
239 * Sets augmented data for result set.
240 * @param string $name Extra data item name
241 * @param array[] $data Extra data as PAGEID => data
242 */
243 public function setAugmentedData( $name, $data ) {
244 foreach ( $data as $id => $resultData ) {
245 $this->extraData[$id][$name] = $resultData;
246 }
247 }
248
249 /**
250 * Returns extra data for specific result and store it in SearchResult object.
251 * @param SearchResult $result
252 */
253 public function augmentResult( SearchResult $result ) {
254 $id = $result->getTitle()->getArticleID();
255 if ( $id === -1 ) {
256 return;
257 }
258 $result->setExtensionData( function () use ( $id ) {
259 return $this->extraData[$id] ?? [];
260 } );
261 }
262
263 /**
264 * @return int|null The offset the current page starts at. Typically
265 * this should be null to allow the UI to decide on its own, but in
266 * special cases like interleaved AB tests specifying explicitly is
267 * necessary.
268 */
269 public function getOffset() {
270 return null;
271 }
272
273 final public function getIterator() {
274 return new ArrayIterator( $this->extractResults() );
275 }
276 }