Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 213
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
SuggestionListManager
0.00% covered (danger)
0.00%
0 / 213
0.00% covered (danger)
0.00%
0 / 15
992
0.00% covered (danger)
0.00%
0 / 1
 insertList
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
12
 deleteList
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 removeTitles
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getListByConds
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getListByName
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getListById
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getDiscardedSuggestions
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getFavoriteSuggestions
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getSuggestionsByListName
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
 addSuggestions
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
12
 removeSuggestions
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 doesSuggestionExist
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 getPublicSuggestions
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 getSuggestionsByType
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
12
 getSuggestionsInList
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace ContentTranslation;
4
5use MediaWiki\MediaWikiServices;
6use MediaWiki\Title\Title;
7
8class 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}