Bump and prep 1.34.1
[lhc/web/wiklou.git] / includes / search / SearchSuggestionSet.php
1 <?php
2
3 /**
4 * Search suggestion sets
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 */
21
22 /**
23 * A set of search suggestions.
24 * The set is always ordered by score, with the best match first.
25 */
26 class SearchSuggestionSet {
27 /**
28 * @var SearchSuggestion[]
29 */
30 private $suggestions = [];
31
32 /**
33 *
34 * @var array
35 */
36 private $pageMap = [];
37
38 /**
39 * @var bool Are more results available?
40 */
41 private $hasMoreResults;
42
43 /**
44 * Builds a new set of suggestions.
45 *
46 * NOTE: the array should be sorted by score (higher is better),
47 * in descending order.
48 * SearchSuggestionSet will not try to re-order this input array.
49 * Providing an unsorted input array is a mistake and will lead to
50 * unexpected behaviors.
51 *
52 * @param SearchSuggestion[] $suggestions (must be sorted by score)
53 * @param bool $hasMoreResults Are more results available?
54 */
55 public function __construct( array $suggestions, $hasMoreResults = false ) {
56 $this->hasMoreResults = $hasMoreResults;
57 foreach ( $suggestions as $suggestion ) {
58 $pageID = $suggestion->getSuggestedTitleID();
59 if ( $pageID && empty( $this->pageMap[$pageID] ) ) {
60 $this->pageMap[$pageID] = true;
61 }
62 $this->suggestions[] = $suggestion;
63 }
64 }
65
66 /**
67 * @return bool Are more results available?
68 */
69 public function hasMoreResults() {
70 return $this->hasMoreResults;
71 }
72
73 /**
74 * Get the list of suggestions.
75 * @return SearchSuggestion[]
76 */
77 public function getSuggestions() {
78 return $this->suggestions;
79 }
80
81 /**
82 * Call array_map on the suggestions array
83 * @param callable $callback
84 * @return array
85 */
86 public function map( $callback ) {
87 return array_map( $callback, $this->suggestions );
88 }
89
90 /**
91 * Filter the suggestions array
92 * @param callable $callback Callable accepting single SearchSuggestion
93 * instance returning bool false to remove the item.
94 * @return int The number of suggestions removed
95 */
96 public function filter( $callback ) {
97 $before = count( $this->suggestions );
98 $this->suggestions = array_values( array_filter( $this->suggestions, $callback ) );
99 return $before - count( $this->suggestions );
100 }
101
102 /**
103 * Add a new suggestion at the end.
104 * If the score of the new suggestion is greater than the worst one,
105 * the new suggestion score will be updated (worst - 1).
106 *
107 * @param SearchSuggestion $suggestion
108 */
109 public function append( SearchSuggestion $suggestion ) {
110 $pageID = $suggestion->getSuggestedTitleID();
111 if ( $pageID && isset( $this->pageMap[$pageID] ) ) {
112 return;
113 }
114 if ( $this->getSize() > 0 && $suggestion->getScore() >= $this->getWorstScore() ) {
115 $suggestion->setScore( $this->getWorstScore() - 1 );
116 }
117 $this->suggestions[] = $suggestion;
118 if ( $pageID ) {
119 $this->pageMap[$pageID] = true;
120 }
121 }
122
123 /**
124 * Add suggestion set to the end of the current one.
125 * @param SearchSuggestionSet $set
126 */
127 public function appendAll( SearchSuggestionSet $set ) {
128 foreach ( $set->getSuggestions() as $sugg ) {
129 $this->append( $sugg );
130 }
131 }
132
133 /**
134 * Move the suggestion at index $key to the first position
135 * @param string $key
136 */
137 public function rescore( $key ) {
138 $removed = array_splice( $this->suggestions, $key, 1 );
139 unset( $this->pageMap[$removed[0]->getSuggestedTitleID()] );
140 $this->prepend( $removed[0] );
141 }
142
143 /**
144 * Add a new suggestion at the top. If the new suggestion score
145 * is lower than the best one its score will be updated (best + 1)
146 * @param SearchSuggestion $suggestion
147 */
148 public function prepend( SearchSuggestion $suggestion ) {
149 $pageID = $suggestion->getSuggestedTitleID();
150 if ( $pageID && isset( $this->pageMap[$pageID] ) ) {
151 return;
152 }
153 if ( $this->getSize() > 0 && $suggestion->getScore() <= $this->getBestScore() ) {
154 $suggestion->setScore( $this->getBestScore() + 1 );
155 }
156 array_unshift( $this->suggestions, $suggestion );
157 if ( $pageID ) {
158 $this->pageMap[$pageID] = true;
159 }
160 }
161
162 /**
163 * @return float the best score in this suggestion set
164 */
165 public function getBestScore() {
166 if ( empty( $this->suggestions ) ) {
167 return 0;
168 }
169 return $this->suggestions[0]->getScore();
170 }
171
172 /**
173 * @return float the worst score in this set
174 */
175 public function getWorstScore() {
176 if ( empty( $this->suggestions ) ) {
177 return 0;
178 }
179 return end( $this->suggestions )->getScore();
180 }
181
182 /**
183 * @return int the number of suggestion in this set
184 */
185 public function getSize() {
186 return count( $this->suggestions );
187 }
188
189 /**
190 * Remove any extra elements in the suggestions set
191 * @param int $limit the max size of this set.
192 */
193 public function shrink( $limit ) {
194 if ( count( $this->suggestions ) > $limit ) {
195 $this->suggestions = array_slice( $this->suggestions, 0, $limit );
196 $this->hasMoreResults = true;
197 }
198 }
199
200 /**
201 * Builds a new set of suggestion based on a title array.
202 * Useful when using a backend that supports only Titles.
203 *
204 * NOTE: Suggestion scores will be generated.
205 *
206 * @param Title[] $titles
207 * @param bool $hasMoreResults Are more results available?
208 * @return SearchSuggestionSet
209 */
210 public static function fromTitles( array $titles, $hasMoreResults = false ) {
211 $score = count( $titles );
212 $suggestions = array_map( function ( $title ) use ( &$score ) {
213 return SearchSuggestion::fromTitle( $score--, $title );
214 }, $titles );
215 return new SearchSuggestionSet( $suggestions, $hasMoreResults );
216 }
217
218 /**
219 * Builds a new set of suggestion based on a string array.
220 *
221 * NOTE: Suggestion scores will be generated.
222 *
223 * @param string[] $titles
224 * @param bool $hasMoreResults Are more results available?
225 * @return SearchSuggestionSet
226 */
227 public static function fromStrings( array $titles, $hasMoreResults = false ) {
228 $score = count( $titles );
229 $suggestions = array_map( function ( $title ) use ( &$score ) {
230 return SearchSuggestion::fromText( $score--, $title );
231 }, $titles );
232 return new SearchSuggestionSet( $suggestions, $hasMoreResults );
233 }
234
235 /**
236 * @return SearchSuggestionSet an empty suggestion set
237 */
238 public static function emptySuggestionSet() {
239 return new SearchSuggestionSet( [] );
240 }
241 }