Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 213 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
SuggestionListManager | |
0.00% |
0 / 213 |
|
0.00% |
0 / 15 |
992 | |
0.00% |
0 / 1 |
insertList | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
12 | |||
deleteList | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
removeTitles | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
getListByConds | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getListByName | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getListById | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getDiscardedSuggestions | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getFavoriteSuggestions | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getSuggestionsByListName | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
20 | |||
addSuggestions | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
12 | |||
removeSuggestions | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
6 | |||
doesSuggestionExist | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
getPublicSuggestions | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
getSuggestionsByType | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
12 | |||
getSuggestionsInList | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace ContentTranslation; |
4 | |
5 | use MediaWiki\MediaWikiServices; |
6 | use MediaWiki\Title\Title; |
7 | |
8 | class SuggestionListManager { |
9 | /** |
10 | * @param SuggestionList $list |
11 | * @return int Id of the list. |
12 | */ |
13 | public function insertList( SuggestionList $list ) { |
14 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
15 | $dbw = $lb->getConnection( DB_PRIMARY ); |
16 | $values = [ |
17 | 'cxl_id' => $list->getId(), |
18 | 'cxl_owner' => $list->getOwner(), |
19 | 'cxl_public' => (int)$list->isPublic(), |
20 | 'cxl_name' => $list->getName(), |
21 | 'cxl_info' => $list->getInfo(), |
22 | 'cxl_type' => $list->getType(), |
23 | ]; |
24 | |
25 | if ( $list->getStartTime() !== null ) { |
26 | $values['cxl_start_time'] = $dbw->timestamp( $list->getStartTime() ); |
27 | } |
28 | |
29 | if ( $list->getEndTime() !== null ) { |
30 | $values['cxl_end_time'] = $dbw->timestamp( $list->getEndTime() ); |
31 | } |
32 | |
33 | $dbw->newInsertQueryBuilder() |
34 | ->insertInto( 'cx_lists' ) |
35 | ->row( $values ) |
36 | ->caller( __METHOD__ ) |
37 | ->execute(); |
38 | |
39 | return $dbw->insertId(); |
40 | } |
41 | |
42 | public function deleteList( $id ) { |
43 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
44 | $dbw = $lb->getConnection( DB_PRIMARY ); |
45 | $dbw->newDeleteQueryBuilder() |
46 | ->deleteFrom( 'cx_suggestions' ) |
47 | ->where( [ |
48 | 'cxs_list_id' => $id, |
49 | ] ) |
50 | ->caller( __METHOD__ ) |
51 | ->execute(); |
52 | $dbw->newDeleteQueryBuilder() |
53 | ->deleteFrom( 'cx_lists' ) |
54 | ->where( [ |
55 | 'cxl_id' => $id |
56 | ] ) |
57 | ->caller( __METHOD__ ) |
58 | ->execute(); |
59 | } |
60 | |
61 | public function removeTitles( $sourceLanguage, array $titles ) { |
62 | if ( $titles === [] ) { |
63 | return; |
64 | } |
65 | |
66 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
67 | $dbw = $lb->getConnection( DB_PRIMARY ); |
68 | $dbw->newDeleteQueryBuilder() |
69 | ->deleteFrom( 'cx_suggestions' ) |
70 | ->where( [ |
71 | 'cxs_title' => $titles, |
72 | 'cxs_source_language' => $sourceLanguage, |
73 | ] ) |
74 | ->caller( __METHOD__ ) |
75 | ->execute(); |
76 | } |
77 | |
78 | protected function getListByConds( array $conds ) { |
79 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
80 | $dbr = $lb->getConnection( DB_REPLICA ); |
81 | $row = $dbr->selectRow( 'cx_lists', '*', $conds, __METHOD__ ); |
82 | |
83 | if ( $row ) { |
84 | return SuggestionList::newFromRow( $row ); |
85 | } |
86 | |
87 | return null; |
88 | } |
89 | |
90 | public function getListByName( $name, $owner = 0 ) { |
91 | $conds = [ |
92 | 'cxl_name' => $name, |
93 | 'cxl_owner' => $owner, |
94 | ]; |
95 | |
96 | return $this->getListByConds( $conds ); |
97 | } |
98 | |
99 | public function getListById( $id ) { |
100 | $conds = [ |
101 | 'cxl_id' => $id, |
102 | 'cxl_owner' => 0, |
103 | ]; |
104 | |
105 | return $this->getListByConds( $conds ); |
106 | } |
107 | |
108 | /** |
109 | * Get the titles discarded by the user between a language pair |
110 | * |
111 | * @param int $owner Owner's global user id. |
112 | * @param string $from Source language code. |
113 | * @param string $to Target language code. |
114 | * @return Title[] |
115 | */ |
116 | public function getDiscardedSuggestions( $owner, $from, $to ) { |
117 | $titles = []; |
118 | $listName = 'cx-suggestionlist-discarded'; |
119 | |
120 | $suggestions = $this->getSuggestionsByListName( $owner, $listName, $from, $to ); |
121 | |
122 | foreach ( $suggestions as $suggestion ) { |
123 | $titles[] = $suggestion->getTitle(); |
124 | } |
125 | |
126 | return $titles; |
127 | } |
128 | |
129 | /** |
130 | * Get suggestions markes as favorite by the translator. |
131 | * |
132 | * @param int $owner Owner's global user id. |
133 | * @return array Lists and suggestions |
134 | */ |
135 | public function getFavoriteSuggestions( $owner ) { |
136 | $lists = []; |
137 | $listName = 'cx-suggestionlist-favorite'; |
138 | $favoriteList = $this->getListByName( $listName, $owner ); |
139 | $suggestions = $this->getSuggestionsByListName( $owner, $listName ); |
140 | |
141 | if ( $favoriteList ) { |
142 | $lists[] = $favoriteList; |
143 | } |
144 | |
145 | return [ |
146 | 'lists' => $lists, |
147 | 'suggestions' => $suggestions, |
148 | ]; |
149 | } |
150 | |
151 | /** |
152 | * Get the suggestions by list name for the given owner. |
153 | * |
154 | * @param int $owner Owner's global user id. |
155 | * @param string $listName |
156 | * @param string|null $from Source language code. |
157 | * @param string|null $to Target language code. |
158 | * @return Suggestion[] Suggestions |
159 | */ |
160 | private function getSuggestionsByListName( $owner, $listName, $from = null, $to = null ) { |
161 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
162 | $dbr = $lb->getConnection( DB_REPLICA ); |
163 | $suggestions = []; |
164 | $conds = [ |
165 | 'cxl_name' => $listName, |
166 | 'cxl_owner' => $owner, |
167 | 'cxs_list_id = cxl_id', |
168 | ]; |
169 | |
170 | if ( $from !== null ) { |
171 | $conds[ 'cxs_source_language' ] = $from; |
172 | } |
173 | if ( $to !== null ) { |
174 | $conds[ 'cxs_target_language' ] = $to; |
175 | } |
176 | |
177 | $res = $dbr->select( |
178 | [ 'cx_suggestions', 'cx_lists' ], |
179 | [ 'cxs_list_id', 'cxs_title', 'cxs_source_language', 'cxs_target_language' ], |
180 | $conds, |
181 | __METHOD__ |
182 | ); |
183 | |
184 | foreach ( $res as $row ) { |
185 | $suggestions[] = Suggestion::newFromRow( $row ); |
186 | } |
187 | |
188 | return $suggestions; |
189 | } |
190 | |
191 | /** |
192 | * Add suggestions to database. |
193 | * |
194 | * @param Suggestion[] $suggestions |
195 | */ |
196 | public function addSuggestions( array $suggestions ) { |
197 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
198 | $dbw = $lb->getConnection( DB_PRIMARY ); |
199 | |
200 | $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); |
201 | |
202 | $batchSize = 100; |
203 | while ( count( $suggestions ) > 0 ) { |
204 | $batch = array_splice( $suggestions, 0, $batchSize ); |
205 | |
206 | $values = []; |
207 | foreach ( $batch as $suggestion ) { |
208 | $values[] = [ |
209 | 'cxs_list_id' => $suggestion->getListId(), |
210 | 'cxs_title' => $suggestion->getTitle()->getPrefixedText(), |
211 | 'cxs_source_language' => $suggestion->getSourceLanguage(), |
212 | 'cxs_target_language' => $suggestion->getTargetLanguage(), |
213 | ]; |
214 | } |
215 | |
216 | $dbw->newInsertQueryBuilder() |
217 | ->insertInto( 'cx_suggestions' ) |
218 | ->ignore() |
219 | ->rows( $values ) |
220 | ->caller( __METHOD__ ) |
221 | ->execute(); |
222 | |
223 | // TODO: This should really wait for replication on the |
224 | // Database returned by Database::getConnection( DB_PRIMARY ); |
225 | $lbFactory->waitForReplication(); |
226 | } |
227 | } |
228 | |
229 | /** |
230 | * Remove each suggestions from the list it belongs to. |
231 | * |
232 | * @param Suggestion[] $suggestions |
233 | */ |
234 | public function removeSuggestions( array $suggestions ) { |
235 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
236 | $dbw = $lb->getConnection( DB_PRIMARY ); |
237 | |
238 | foreach ( $suggestions as $suggestion ) { |
239 | $values = [ |
240 | 'cxs_list_id' => $suggestion->getListId(), |
241 | 'cxs_title' => $suggestion->getTitle()->getPrefixedText(), |
242 | 'cxs_source_language' => $suggestion->getSourceLanguage(), |
243 | 'cxs_target_language' => $suggestion->getTargetLanguage(), |
244 | ]; |
245 | $dbw->newDeleteQueryBuilder() |
246 | ->deleteFrom( 'cx_suggestions' ) |
247 | ->where( $values ) |
248 | ->caller( __METHOD__ ) |
249 | ->execute(); |
250 | } |
251 | } |
252 | |
253 | /** |
254 | * Check if suggestion exist in a list |
255 | * |
256 | * @param Suggestion $suggestion |
257 | * @return bool |
258 | */ |
259 | public function doesSuggestionExist( Suggestion $suggestion ) { |
260 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
261 | $dbr = $lb->getConnection( DB_REPLICA ); |
262 | |
263 | $conds = [ |
264 | 'cxs_list_id' => $suggestion->getListId(), |
265 | 'cxs_title' => $suggestion->getTitle()->getPrefixedText(), |
266 | 'cxs_source_language' => $suggestion->getSourceLanguage(), |
267 | 'cxs_target_language' => $suggestion->getTargetLanguage(), |
268 | ]; |
269 | $row = $dbr->selectRow( 'cx_suggestions', '1', $conds, __METHOD__ ); |
270 | |
271 | // If there is no result, `selectRow` returns `false` |
272 | return $row !== false; |
273 | } |
274 | |
275 | /** |
276 | * Get public (non-personalized) suggestions. |
277 | * |
278 | * @param string $from Source language code. |
279 | * @param string $to Target language code. |
280 | * @param int $limit How many suggestions to fetch. |
281 | * @param int $offset Offset from the beginning to fetch. |
282 | * @param int $seed Seed to use with randomizing of results. |
283 | * @return array Lists and suggestions |
284 | */ |
285 | public function getPublicSuggestions( $from, $to, $limit, $offset, $seed ) { |
286 | return $this->getSuggestionsByType( |
287 | [ |
288 | SuggestionList::TYPE_CATEGORY, |
289 | SuggestionList::TYPE_FEATURED |
290 | ], |
291 | $from, |
292 | $to, |
293 | $limit, |
294 | $offset, |
295 | $seed |
296 | ); |
297 | } |
298 | |
299 | /** |
300 | * Get public suggestions by list type |
301 | * |
302 | * @param int|int[] $type List type. |
303 | * @param string $from Source language code. |
304 | * @param string $to Target language code. |
305 | * @param int $limit How many suggestions to fetch. |
306 | * @param int|null $offset Offset from the beginning to fetch. |
307 | * @param int|null $seed Seed to use with randomizing of results. |
308 | * @return array Lists and suggestions |
309 | */ |
310 | public function getSuggestionsByType( $type, $from, $to, $limit, $offset = null, $seed = null ) { |
311 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
312 | $dbr = $lb->getConnection( DB_REPLICA ); |
313 | |
314 | $lists = []; |
315 | $suggestions = []; |
316 | |
317 | $res = $dbr->select( |
318 | 'cx_lists', |
319 | '*', |
320 | [ |
321 | 'cxl_type' => $type, |
322 | 'cxl_public' => true, |
323 | ], |
324 | __METHOD__, |
325 | [ |
326 | 'ORDER BY' => 'cxl_type desc' |
327 | ] |
328 | ); |
329 | |
330 | foreach ( $res as $row ) { |
331 | $list = SuggestionList::newFromRow( $row ); |
332 | $suggestionsInList = $this->getSuggestionsInList( |
333 | $list->getId(), $from, $to, $limit, $offset, $seed |
334 | ); |
335 | if ( !count( $suggestionsInList ) ) { |
336 | continue; |
337 | } |
338 | $lists[$list->getId()] = $list; |
339 | $suggestions = array_merge( |
340 | $suggestions, |
341 | $suggestionsInList |
342 | ); |
343 | } |
344 | |
345 | return [ |
346 | 'lists' => $lists, |
347 | 'suggestions' => $suggestions, |
348 | ]; |
349 | } |
350 | |
351 | /** |
352 | * Get the suggestions by list id |
353 | * |
354 | * @param int $listId |
355 | * @param string $from Source language code. |
356 | * @param string $to Target language code. |
357 | * @param int $limit How many suggestions to fetch. |
358 | * @param int $offset Offset from the beginning to fetch. |
359 | * @param int $seed Seed to use with randomizing of results. |
360 | * @return Suggestion[] Suggestions |
361 | */ |
362 | public function getSuggestionsInList( $listId, $from, $to, $limit, $offset, $seed ) { |
363 | $suggestions = []; |
364 | $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' ); |
365 | $dbr = $lb->getConnection( DB_REPLICA ); |
366 | |
367 | $seed = (int)$seed; |
368 | |
369 | $options = [ |
370 | 'LIMIT' => $limit, |
371 | 'ORDER BY' => "RAND( $seed )" |
372 | ]; |
373 | |
374 | if ( $offset ) { |
375 | $options['OFFSET'] = $offset; |
376 | } |
377 | |
378 | $res = $dbr->select( |
379 | [ 'cx_suggestions' ], |
380 | '*', |
381 | [ |
382 | 'cxs_source_language' => $from, |
383 | 'cxs_target_language' => $to, |
384 | 'cxs_list_id' => $listId |
385 | ], |
386 | __METHOD__, |
387 | $options |
388 | ); |
389 | |
390 | foreach ( $res as $row ) { |
391 | $suggestions[] = Suggestion::newFromRow( $row ); |
392 | } |
393 | |
394 | return $suggestions; |
395 | } |
396 | } |