Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 182 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
Translation | |
0.00% |
0 / 182 |
|
0.00% |
0 / 14 |
992 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isNew | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setIsNew | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getStats | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
6 | |||
getDeletionTrend | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
30 | |||
getTrendByStatus | |
0.00% |
0 / 49 |
|
0.00% |
0 / 1 |
72 | |||
getTranslationId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSourceTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSourceLanguage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTargetLanguage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getData | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPublishedCondition | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getAllPublishedTranslations | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
30 | |||
newFromRow | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace ContentTranslation; |
4 | |
5 | use MediaWiki\MediaWikiServices; |
6 | use MediaWiki\Storage\NameTableAccessException; |
7 | use Wikimedia\Rdbms\IDatabase; |
8 | |
9 | class 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 | } |