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