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