Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 182
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Translation
0.00% covered (danger)
0.00%
0 / 182
0.00% covered (danger)
0.00%
0 / 14
992
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isNew
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIsNew
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStats
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
6
 getDeletionTrend
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
30
 getTrendByStatus
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
72
 getTranslationId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSourceTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSourceLanguage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTargetLanguage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPublishedCondition
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getAllPublishedTranslations
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
30
 newFromRow
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace ContentTranslation;
4
5use MediaWiki\MediaWikiServices;
6use MediaWiki\Storage\NameTableAccessException;
7use Wikimedia\Rdbms\IDatabase;
8
9class Translation {
10    private bool $isNew = false;
11
12    /** @var array */
13    public $translation;
14
15    public function __construct( $translation ) {
16        $this->translation = $translation;
17    }
18
19    /**
20     * @return bool Whether the last CRUD operation on this translation was "create"
21     */
22    public function isNew(): bool {
23        return $this->isNew;
24    }
25
26    public function setIsNew( bool $isNew ): void {
27        $this->isNew = $isNew;
28    }
29
30    /**
31     * Get the stats for all translations in draft or published status.
32     * @return array[]
33     */
34    public static function getStats(): array {
35        /** @var LoadBalancer $lb */
36        $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' );
37        $dbr = $lb->getConnection( DB_REPLICA );
38
39        $statusCase = $dbr->conditional(
40            self::getPublishedCondition( $dbr ),
41            $dbr->addQuotes( 'published' ),
42            $dbr->addQuotes( 'draft' )
43        );
44
45        $rows = $dbr->newSelectQueryBuilder()
46            ->select( [
47                'sourceLanguage' => 'translation_source_language',
48                'targetLanguage' => 'translation_target_language',
49                'status' => $statusCase,
50                'count' => 'COUNT(*)',
51                'translators' => 'COUNT(DISTINCT translation_started_by)',
52            ] )
53            ->from( 'cx_translations' )
54            ->where( [ 'translation_status' => [ 'draft', 'published' ] ] )
55            ->groupBy( [
56                'translation_source_language',
57                'translation_target_language',
58                'status',
59            ] )
60            ->caller( __METHOD__ )
61            ->fetchResultSet();
62
63        $result = [];
64
65        foreach ( $rows as $row ) {
66            $result[] = (array)$row;
67        }
68
69        return $result;
70    }
71
72    /**
73     * Get time-wise cumulative number of deletions for given
74     * language pairs, with given interval.
75     *
76     * @param string $interval
77     * @return array<string,array>
78     */
79    public static function getDeletionTrend( $interval ): array {
80        $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
81        $dbr = $lb->getConnection( DB_REPLICA );
82
83        $conditions = [];
84
85        $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
86        try {
87            $conditions['ct_tag_id'] = $changeTagDefStore->getId( 'contenttranslation' );
88        } catch ( NameTableAccessException $exception ) {
89            // No translations published yet, so can skip query
90            return [];
91        }
92
93        $groupBy = [];
94        if ( $interval === 'week' ) {
95            $groupBy = [
96                'YEARWEEK(ar_timestamp, 3)',
97            ];
98        } elseif ( $interval === 'month' ) {
99            $groupBy = [
100                'YEAR(ar_timestamp)',
101                'MONTH(ar_timestamp)',
102            ];
103        }
104
105        $rows = $dbr->newSelectQueryBuilder()
106            ->select( [
107                'date' => 'MAX(ar_timestamp)',
108                'count' => 'COUNT(ar_page_id)'
109            ] )
110            ->from( 'change_tag' )
111            ->join( 'archive', null, 'ar_rev_id = ct_rev_id' )
112            ->where( $conditions )
113            ->groupBy( $groupBy )
114            ->caller( __METHOD__ )
115            ->fetchResultSet();
116
117        $count = 0;
118        $result = [];
119        $dm = new DateManipulator( $interval );
120        foreach ( $rows as $row ) {
121            $count += (int)$row->count;
122            $time = $dm->getIntervalIdentifier( $row->date )->format( 'U' );
123            $result[$time] = [
124                'count' => $count,
125                'delta' => (int)$row->count,
126            ];
127        }
128
129        return $result;
130    }
131
132    /**
133     * Get time-wise cumulative number of translations for given
134     * language pairs, with given interval.
135     *
136     * @param string|null $source Source language code
137     * @param string|null $target Target language code
138     * @param string $status Status of translation. Either 'published' or 'draft'
139     * @param string $interval 'weekly' or 'monthly' trend
140     * @param int|null $translatorId
141     * @return array
142     */
143    public static function getTrendByStatus(
144        $source, $target, $status, $interval, $translatorId
145    ) {
146        /** @var LoadBalancer $lb */
147        $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' );
148        $dbr = $lb->getConnection( DB_REPLICA );
149
150        $conditions = [];
151        if ( $status === 'published' ) {
152            $conditions[] = self::getPublishedCondition( $dbr );
153        } else {
154            $conditions[] = $dbr->makeList(
155                [
156                    'translation_status' => 'draft',
157                    'translation_target_url' => null,
158                ],
159                LIST_AND
160            );
161        }
162
163        if ( $source !== null ) {
164            $conditions['translation_source_language'] = $source;
165        }
166        if ( $target !== null ) {
167            $conditions['translation_target_language'] = $target;
168        }
169        if ( $translatorId !== null ) {
170            $conditions['translation_last_update_by'] = $translatorId;
171        }
172        $groupBy = [];
173        if ( $interval === 'week' ) {
174            $groupBy = [
175                'YEARWEEK(translation_last_updated_timestamp, 3)',
176            ];
177        } elseif ( $interval === 'month' ) {
178            $groupBy = [
179                'YEAR(translation_last_updated_timestamp)',
180                'MONTH(translation_last_updated_timestamp)',
181            ];
182        }
183
184        $rows = $dbr->newSelectQueryBuilder()
185            ->select( [
186                'date' => 'MAX(translation_last_updated_timestamp)',
187                'count' => 'COUNT(translation_id)'
188            ] )
189            ->from( 'cx_translations' )
190            ->where( $conditions )
191            ->groupBy( $groupBy )
192            ->caller( __METHOD__ )
193            ->fetchResultSet();
194
195        $count = 0;
196        $result = [];
197        $dm = new DateManipulator( $interval );
198        foreach ( $rows as $row ) {
199            $count += (int)$row->count;
200            $time = $dm->getIntervalIdentifier( $row->date )->format( 'U' );
201            $result[$time] = [
202                'count' => $count,
203                'delta' => (int)$row->count,
204            ];
205        }
206
207        return $result;
208    }
209
210    public function getTranslationId() {
211        return $this->translation['id'];
212    }
213
214    public function getSourceTitle(): string {
215        return $this->translation['sourceTitle'];
216    }
217
218    public function getSourceLanguage(): string {
219        return $this->translation['sourceLanguage'];
220    }
221
222    public function getTargetLanguage(): string {
223        return $this->translation['targetLanguage'];
224    }
225
226    /**
227     * Return the underlying data fields.
228     *
229     * @return array
230     */
231    public function getData() {
232        return $this->translation;
233    }
234
235    public static function getPublishedCondition( IDatabase $db ) {
236        return $db->expr( 'translation_status', '=', 'published' )
237            ->or( 'translation_target_url', '!=', null );
238    }
239
240    /**
241     * Get all published translation records.
242     *
243     * @param string $from Source language code
244     * @param string $to Target language code
245     * @param int $limit Number of records to fetch atmost
246     * @param int $offset Offset from which at most $limit records to fetch
247     * @return array
248     */
249    public static function getAllPublishedTranslations( $from, $to, $limit, $offset ) {
250        /** @var LoadBalancer $lb */
251        $lb = MediaWikiServices::getInstance()->getService( 'ContentTranslation.LoadBalancer' );
252        $dbr = $lb->getConnection( DB_REPLICA );
253        $conditions = [];
254        $conditions[] = self::getPublishedCondition( $dbr );
255
256        if ( $from ) {
257            $conditions['translation_source_language'] = $from;
258        }
259
260        if ( $to ) {
261            $conditions['translation_target_language'] = $to;
262        }
263
264        $queryBuilder = $dbr->newSelectQueryBuilder()
265            ->select( [
266                'translationId' => 'translation_id',
267                'sourceTitle' => 'translation_source_title',
268                'targetTitle' => 'translation_target_title',
269                'sourceLanguage' => 'translation_source_language',
270                'sourceRevisionId' => 'translation_source_revision_id',
271                'targetRevisionId' => 'translation_target_revision_id',
272                'targetLanguage' => 'translation_target_language',
273                'sourceURL' => 'translation_source_url',
274                'targetURL' => 'translation_target_url',
275                'publishedDate' => 'translation_last_updated_timestamp',
276                'stats' => 'translation_progress',
277            ] )
278            ->from( 'cx_translations' )
279            ->where( $conditions )
280            ->limit( $limit )
281            ->caller( __METHOD__ );
282
283        if ( $offset ) {
284            $queryBuilder->offset( $offset );
285        }
286
287        $rows = $queryBuilder->fetchResultSet();
288
289        $result = [];
290
291        foreach ( $rows as $row ) {
292            $translation = (array)$row;
293            $translation['stats'] = json_decode( $translation['stats'] );
294            $result[] = $translation;
295        }
296
297        return $result;
298    }
299
300    /**
301     * @param \stdClass $row
302     * @return Translation
303     */
304    public static function newFromRow( $row ) {
305        $fields = [
306            'id' => (int)$row->translation_id,
307            'sourceTitle' => $row->translation_source_title,
308            'targetTitle' => $row->translation_target_title,
309            'sourceLanguage' => $row->translation_source_language,
310            'targetLanguage' => $row->translation_target_language,
311            'sourceRevisionId' => $row->translation_source_revision_id,
312            'targetRevisionId' => $row->translation_target_revision_id,
313            'sourceURL' => $row->translation_source_url,
314            'targetURL' => $row->translation_target_url,
315            'status' => $row->translation_status,
316            'startTimestamp' => $row->translation_start_timestamp,
317            'lastUpdateTimestamp' => $row->translation_last_updated_timestamp,
318            'progress' => $row->translation_progress,
319            'startedTranslator' => $row->translation_started_by,
320            'lastUpdatedTranslator' => $row->translation_last_update_by,
321            'cxVersion' => 1,
322        ];
323
324        // BC code to gracefully handle lack of schema change
325        if ( isset( $row->translation_cx_version ) ) {
326            $fields['cxVersion'] = (int)$row->translation_cx_version;
327        }
328
329        return new Translation( $fields );
330    }
331}