Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
66.52% |
147 / 221 |
|
48.57% |
17 / 35 |
CRAP | |
0.00% |
0 / 1 |
TranslatablePage | |
66.82% |
147 / 220 |
|
48.57% |
17 / 35 |
231.00 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
newFromText | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
newFromRevision | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
2.01 | |||
newFromTitle | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPageIdentity | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getText | |
76.19% |
16 / 21 |
|
0.00% |
0 / 1 |
7.66 | |||
getRevision | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSourceLanguageCode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMessageGroupId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMessageGroupIdFromTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMessageGroup | |
50.00% |
4 / 8 |
|
0.00% |
0 / 1 |
4.12 | |||
hasPageDisplayTitle | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getPageDisplayTitle | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
getStrippedSourcePageText | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getTranslationPageFromTitle | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getTranslationPage | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
1 | |||
addMarkedTag | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addReadyTag | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getMarkedTag | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getReadyTag | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTranslationUrl | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
getTranslationPages | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
5.01 | |||
getTranslationUnitPages | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTranslationPercentages | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
30 | |||
supportsTransclusion | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getRevisionRecordWithFallback | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
isMoveable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isDeletable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isTranslationPage | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
3.01 | |||
parseTranslationUnit | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
isSourcePage | |
96.00% |
24 / 25 |
|
0.00% |
0 / 1 |
2 | |||
clearSourcePageCache | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
determineStatus | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
getCacheValue | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\PageTranslation; |
5 | |
6 | use LogicException; |
7 | use MediaWiki\Content\TextContent; |
8 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups; |
9 | use MediaWiki\Extension\Translate\MessageGroupProcessing\RevTagStore; |
10 | use MediaWiki\Extension\Translate\MessageGroupProcessing\TranslatableBundle; |
11 | use MediaWiki\Extension\Translate\MessageLoading\MessageHandle; |
12 | use MediaWiki\Extension\Translate\Services; |
13 | use MediaWiki\Extension\Translate\Statistics\MessageGroupStats; |
14 | use MediaWiki\Extension\Translate\Utilities\Utilities; |
15 | use MediaWiki\Languages\LanguageNameUtils; |
16 | use MediaWiki\Linker\LinkTarget; |
17 | use MediaWiki\MediaWikiServices; |
18 | use MediaWiki\Page\PageIdentity; |
19 | use MediaWiki\Page\PageReference; |
20 | use MediaWiki\Revision\RevisionRecord; |
21 | use MediaWiki\Revision\SlotRecord; |
22 | use MediaWiki\SpecialPage\SpecialPage; |
23 | use MediaWiki\Title\Title; |
24 | use RuntimeException; |
25 | use Wikimedia\Rdbms\Database; |
26 | use Wikimedia\Rdbms\IDBAccessObject; |
27 | use WikiPageMessageGroup; |
28 | |
29 | /** |
30 | * Mixed bag of methods related to translatable pages. |
31 | * @author Niklas Laxström |
32 | * @license GPL-2.0-or-later |
33 | * @ingroup PageTranslation |
34 | */ |
35 | class TranslatablePage extends TranslatableBundle { |
36 | /** |
37 | * List of keys in the metadata table that need to be handled for moves and deletions |
38 | * @phpcs-require-sorted-array |
39 | */ |
40 | public const METADATA_KEYS = [ |
41 | 'maxid', |
42 | 'priorityforce', |
43 | 'prioritylangs', |
44 | 'priorityreason', |
45 | 'transclusion', |
46 | 'version' |
47 | ]; |
48 | /** @var string Name of the section which contains the translated page title. */ |
49 | public const DISPLAY_TITLE_UNIT_ID = 'Page display title'; |
50 | |
51 | protected PageIdentity $title; |
52 | protected RevTagStore $revTagStore; |
53 | /** @var ?string Text contents of the page. */ |
54 | protected $text; |
55 | /** @var ?int Revision of the page, if applicable. */ |
56 | protected $revision; |
57 | /** @var string From which source this object was constructed: text, revision or title */ |
58 | protected $source; |
59 | /** @var ?bool Whether the title should be translated */ |
60 | protected $pageDisplayTitle; |
61 | /** @var ?string */ |
62 | private $targetLanguage; |
63 | |
64 | protected function __construct( PageIdentity $title ) { |
65 | $this->title = $title; |
66 | $this->revTagStore = Services::getInstance()->getRevTagStore(); |
67 | } |
68 | |
69 | /** |
70 | * Constructs a translatable page from given text. |
71 | * Some functions will fail unless you set revision |
72 | * parameter manually. |
73 | */ |
74 | public static function newFromText( Title $title, string $text ): self { |
75 | $obj = new self( $title ); |
76 | $obj->text = $text; |
77 | $obj->source = 'text'; |
78 | |
79 | return $obj; |
80 | } |
81 | |
82 | /** |
83 | * Constructs a translatable page from given revision. |
84 | * The revision must belong to the title given or unspecified |
85 | * behavior will happen. |
86 | */ |
87 | public static function newFromRevision( PageIdentity $title, int $revision ): self { |
88 | $rev = MediaWikiServices::getInstance() |
89 | ->getRevisionLookup() |
90 | ->getRevisionByTitle( $title, $revision ); |
91 | if ( $rev === null ) { |
92 | throw new RuntimeException( 'Revision is null' ); |
93 | } |
94 | |
95 | $obj = new self( $title ); |
96 | $obj->source = 'revision'; |
97 | $obj->revision = $revision; |
98 | |
99 | return $obj; |
100 | } |
101 | |
102 | /** |
103 | * Constructs a translatable page from title. |
104 | * The text of last marked revision is loaded when needed. |
105 | */ |
106 | public static function newFromTitle( PageIdentity $title ): self { |
107 | $obj = new self( $title ); |
108 | $obj->source = 'title'; |
109 | |
110 | return $obj; |
111 | } |
112 | |
113 | /** @inheritDoc */ |
114 | public function getTitle(): Title { |
115 | return Title::castFromPageIdentity( $this->title ); |
116 | } |
117 | |
118 | public function getPageIdentity(): PageIdentity { |
119 | return $this->title; |
120 | } |
121 | |
122 | /** Returns the text for this translatable page. */ |
123 | public function getText(): string { |
124 | if ( $this->text !== null ) { |
125 | return $this->text; |
126 | } |
127 | |
128 | if ( $this->source === 'title' ) { |
129 | $revision = $this->getMarkedTag(); |
130 | if ( !is_int( $revision ) ) { |
131 | throw new LogicException( |
132 | "Trying to load a text for {$this->getPageIdentity()} which is not marked for translation" |
133 | ); |
134 | } |
135 | $this->revision = $revision; |
136 | } |
137 | |
138 | $flags = Utilities::shouldReadFromPrimary() |
139 | ? IDBAccessObject::READ_LATEST |
140 | : IDBAccessObject::READ_NORMAL; |
141 | $rev = MediaWikiServices::getInstance() |
142 | ->getRevisionLookup() |
143 | ->getRevisionByTitle( $this->getPageIdentity(), $this->revision, $flags ); |
144 | $content = $rev->getContent( SlotRecord::MAIN ); |
145 | $text = ( $content instanceof TextContent ) ? $content->getText() : null; |
146 | |
147 | if ( !is_string( $text ) ) { |
148 | throw new RuntimeException( "Failed to load text for {$this->getPageIdentity()}" ); |
149 | } |
150 | |
151 | $this->text = $text; |
152 | |
153 | return $this->text; |
154 | } |
155 | |
156 | /** |
157 | * Revision is null if object was constructed using newFromText. |
158 | * @return null|int |
159 | */ |
160 | public function getRevision(): ?int { |
161 | return $this->revision; |
162 | } |
163 | |
164 | /** |
165 | * Returns the source language of this translatable page. In other words |
166 | * the language in which the page without language code is written. |
167 | * @since 2013-01-28 |
168 | */ |
169 | public function getSourceLanguageCode(): string { |
170 | return $this->getTitle()->getPageLanguage()->getCode(); |
171 | } |
172 | |
173 | /** @inheritDoc */ |
174 | public function getMessageGroupId(): string { |
175 | return self::getMessageGroupIdFromTitle( $this->getPageIdentity() ); |
176 | } |
177 | |
178 | /** Constructs MessageGroup id for any title. */ |
179 | public static function getMessageGroupIdFromTitle( PageReference $page ): string { |
180 | return 'page-' . MediaWikiServices::getInstance()->getTitleFormatter()->getPrefixedText( $page ); |
181 | } |
182 | |
183 | /** |
184 | * Returns MessageGroup used for translating this page. It may still be empty |
185 | * if the page has not been ever marked. |
186 | */ |
187 | public function getMessageGroup(): ?WikiPageMessageGroup { |
188 | $groupId = $this->getMessageGroupId(); |
189 | $group = MessageGroups::getGroup( $groupId ); |
190 | if ( !$group || $group instanceof WikiPageMessageGroup ) { |
191 | return $group; |
192 | } |
193 | |
194 | throw new RuntimeException( |
195 | "Expected $groupId to be of type WikiPageMessageGroup; got " . |
196 | get_class( $group ) |
197 | ); |
198 | } |
199 | |
200 | /** Check whether title is marked for translation */ |
201 | public function hasPageDisplayTitle(): bool { |
202 | // Cached value |
203 | if ( $this->pageDisplayTitle !== null ) { |
204 | return $this->pageDisplayTitle; |
205 | } |
206 | |
207 | // Check if title section exists in list of sections |
208 | $factory = Services::getInstance()->getTranslationUnitStoreFactory(); |
209 | $store = $factory->getReader( $this->getPageIdentity() ); |
210 | $this->pageDisplayTitle = in_array( self::DISPLAY_TITLE_UNIT_ID, $store->getNames() ); |
211 | |
212 | return $this->pageDisplayTitle; |
213 | } |
214 | |
215 | /** Get translated page title. */ |
216 | public function getPageDisplayTitle( string $languageCode ): ?string { |
217 | // Return null if title not marked for translation |
218 | if ( !$this->hasPageDisplayTitle() ) { |
219 | return null; |
220 | } |
221 | |
222 | // Display title from DB |
223 | $section = str_replace( ' ', '_', self::DISPLAY_TITLE_UNIT_ID ); |
224 | $page = MediaWikiServices::getInstance()->getTitleFormatter()->getPrefixedDBkey( $this->getPageIdentity() ); |
225 | |
226 | try { |
227 | $group = $this->getMessageGroup(); |
228 | } catch ( RuntimeException $e ) { |
229 | return null; |
230 | } |
231 | |
232 | // Sanity check, seems to happen during moves |
233 | if ( !$group ) { |
234 | return null; |
235 | } |
236 | |
237 | return $group->getMessage( "$page/$section", $languageCode, IDBAccessObject::READ_NORMAL ); |
238 | } |
239 | |
240 | public function getStrippedSourcePageText(): string { |
241 | $parser = Services::getInstance()->getTranslatablePageParser(); |
242 | $text = $parser->cleanupTags( $this->getText() ); |
243 | $text = preg_replace( '~<languages\s*/>\n?~s', '', $text ); |
244 | |
245 | return $text; |
246 | } |
247 | |
248 | public static function getTranslationPageFromTitle( Title $title ): ?TranslationPage { |
249 | $self = self::isTranslationPage( $title ); |
250 | return $self ? $self->getTranslationPage( $self->targetLanguage ) : null; |
251 | } |
252 | |
253 | public function getTranslationPage( string $targetLanguage ): TranslationPage { |
254 | $mwServices = MediaWikiServices::getInstance(); |
255 | $config = $mwServices->getMainConfig(); |
256 | $services = Services::getInstance(); |
257 | $parser = $services->getTranslatablePageParser(); |
258 | $parserOutput = $parser->parse( $this->getText() ); |
259 | $pageVersion = (int)$services->getMessageGroupMetadata() |
260 | ->get( $this->getMessageGroupId(), 'version' ); |
261 | $wrapUntranslated = $pageVersion >= 2; |
262 | $languageFactory = $mwServices->getLanguageFactory(); |
263 | |
264 | return new TranslationPage( |
265 | $parserOutput, |
266 | $this->getMessageGroup(), |
267 | $languageFactory->getLanguage( $targetLanguage ), |
268 | $languageFactory->getLanguage( $this->getSourceLanguageCode() ), |
269 | $config->get( 'TranslateKeepOutdatedTranslations' ), |
270 | $wrapUntranslated, |
271 | $this->getTitle() |
272 | ); |
273 | } |
274 | |
275 | /** Adds a tag which indicates that this page is suitable for translation. */ |
276 | public function addMarkedTag( int $revision, ?array $value = null ) { |
277 | $this->revTagStore->replaceTag( $this->getPageIdentity(), RevTagStore::TP_MARK_TAG, $revision, $value ); |
278 | self::clearSourcePageCache(); |
279 | } |
280 | |
281 | /** Adds a tag which indicates that this page source is ready for marking for translation. */ |
282 | public function addReadyTag( int $revision ): void { |
283 | $this->revTagStore->replaceTag( $this->getPageIdentity(), RevTagStore::TP_READY_TAG, $revision ); |
284 | if ( !self::isSourcePage( $this->getPageIdentity() ) ) { |
285 | self::clearSourcePageCache(); |
286 | } |
287 | } |
288 | |
289 | /** Returns the latest revision which has marked tag, if any. */ |
290 | public function getMarkedTag(): ?int { |
291 | return $this->revTagStore->getLatestRevisionWithTag( $this->getPageIdentity(), RevTagStore::TP_MARK_TAG ); |
292 | } |
293 | |
294 | /** Returns the latest revision which has ready tag, if any. */ |
295 | public function getReadyTag(): ?int { |
296 | return $this->revTagStore->getLatestRevisionWithTag( $this->getPageIdentity(), RevTagStore::TP_READY_TAG ); |
297 | } |
298 | |
299 | /** |
300 | * Produces a link to translation view of a translation page. |
301 | * @param string|bool $code MediaWiki language code. Default: false. |
302 | * @return string Relative url |
303 | */ |
304 | public function getTranslationUrl( $code = false ): string { |
305 | $params = [ |
306 | 'group' => $this->getMessageGroupId(), |
307 | 'action' => 'page', |
308 | 'filter' => '', |
309 | 'language' => $code, |
310 | ]; |
311 | |
312 | $translate = SpecialPage::getTitleFor( 'Translate' ); |
313 | |
314 | return $translate->getLocalURL( $params ); |
315 | } |
316 | |
317 | /** @inheritDoc */ |
318 | public function getTranslationPages(): array { |
319 | $mwServices = MediaWikiServices::getInstance(); |
320 | |
321 | $messageGroup = $this->getMessageGroup(); |
322 | $knownLanguageCodes = $messageGroup ? $messageGroup->getTranslatableLanguages() : null; |
323 | $knownLanguageCodes ??= Utilities::getLanguageNames( LanguageNameUtils::AUTONYMS ); |
324 | |
325 | $prefixedDbTitleKey = $this->getPageIdentity()->getDBkey() . '/'; |
326 | $baseNamespace = $this->getPageIdentity()->getNamespace(); |
327 | |
328 | // Build a link batch query for all translation pages |
329 | $linkBatch = $mwServices->getLinkBatchFactory()->newLinkBatch(); |
330 | foreach ( array_keys( $knownLanguageCodes ) as $code ) { |
331 | $linkBatch->add( $baseNamespace, $prefixedDbTitleKey . $code ); |
332 | } |
333 | |
334 | $translationPages = []; |
335 | foreach ( $linkBatch->getPageIdentities() as $pageIdentity ) { |
336 | if ( $pageIdentity->exists() ) { |
337 | $translationPages[] = Title::castFromPageIdentity( $pageIdentity ); |
338 | } |
339 | } |
340 | |
341 | return $translationPages; |
342 | } |
343 | |
344 | /** @inheritDoc */ |
345 | public function getTranslationUnitPages( ?string $code = null ): array { |
346 | return $this->getTranslationUnitPagesByTitle( $this->title, $code ); |
347 | } |
348 | |
349 | public function getTranslationPercentages(): array { |
350 | // Calculate percentages for the available translations |
351 | try { |
352 | $group = $this->getMessageGroup(); |
353 | } catch ( RuntimeException $e ) { |
354 | return []; |
355 | } |
356 | |
357 | if ( !$group ) { |
358 | return []; |
359 | } |
360 | |
361 | $titles = $this->getTranslationPages(); |
362 | $temp = MessageGroupStats::forGroup( $this->getMessageGroupId(), MessageGroupStats::FLAG_CACHE_ONLY ); |
363 | $stats = []; |
364 | |
365 | foreach ( $titles as $t ) { |
366 | $handle = new MessageHandle( $t ); |
367 | $code = $handle->getCode(); |
368 | |
369 | // Sometimes we want to display 0.00 for pages for which translation |
370 | // hasn't started yet. |
371 | $stats[$code] = 0.00; |
372 | if ( ( $temp[$code][MessageGroupStats::TOTAL] ?? 0 ) > 0 ) { |
373 | $total = $temp[$code][MessageGroupStats::TOTAL]; |
374 | $translated = $temp[$code][MessageGroupStats::TRANSLATED]; |
375 | $percentage = $translated / $total; |
376 | $stats[$code] = sprintf( '%.2f', $percentage ); |
377 | } |
378 | } |
379 | |
380 | // Content language is always up-to-date |
381 | $stats[$this->getSourceLanguageCode()] = 1.00; |
382 | |
383 | return $stats; |
384 | } |
385 | |
386 | public function supportsTransclusion(): ?bool { |
387 | $transclusion = Services::getInstance() |
388 | ->getMessageGroupMetadata() |
389 | ->get( $this->getMessageGroupId(), 'transclusion' ); |
390 | if ( $transclusion === false ) { |
391 | return null; |
392 | } |
393 | |
394 | return $transclusion === '1'; |
395 | } |
396 | |
397 | public function getRevisionRecordWithFallback(): ?RevisionRecord { |
398 | $title = $this->getTitle(); |
399 | $store = MediaWikiServices::getInstance()->getRevisionStore(); |
400 | $revRecord = $store->getRevisionByTitle( $title->getSubpage( $this->targetLanguage ) ); |
401 | if ( $revRecord ) { |
402 | return $revRecord; |
403 | } |
404 | |
405 | // Fetch the source fallback |
406 | return $store->getRevisionByTitle( $title->getSubpage( $this->getSourceLanguageCode() ) ); |
407 | } |
408 | |
409 | /** @inheritDoc */ |
410 | public function isMoveable(): bool { |
411 | return $this->getMarkedTag() !== null; |
412 | } |
413 | |
414 | /** @inheritDoc */ |
415 | public function isDeletable(): bool { |
416 | return $this->getMarkedTag() !== null; |
417 | } |
418 | |
419 | /** @return bool|self */ |
420 | public static function isTranslationPage( Title $title ) { |
421 | $handle = new MessageHandle( $title ); |
422 | if ( !Utilities::isTranslationPage( $handle ) ) { |
423 | return false; |
424 | } |
425 | |
426 | $languageCode = $handle->getCode(); |
427 | $newTitle = $handle->getTitleForBase(); |
428 | |
429 | $page = self::newFromTitle( $newTitle ); |
430 | |
431 | if ( $page->getMarkedTag() === null ) { |
432 | return false; |
433 | } |
434 | |
435 | $page->targetLanguage = $languageCode; |
436 | |
437 | return $page; |
438 | } |
439 | |
440 | /** Helper to guess translation page from translation unit. */ |
441 | public static function parseTranslationUnit( LinkTarget $translationUnit ): array { |
442 | // Format is Translations:SourcePageNamespace:SourcePageName/SectionName/LanguageCode. |
443 | // We will drop the namespace immediately here. |
444 | $parts = explode( '/', $translationUnit->getText() ); |
445 | |
446 | // LanguageCode and SectionName are guaranteed to not have '/'. |
447 | $language = array_pop( $parts ); |
448 | $section = array_pop( $parts ); |
449 | $sourcepage = implode( '/', $parts ); |
450 | |
451 | return [ |
452 | 'sourcepage' => $sourcepage, |
453 | 'section' => $section, |
454 | 'language' => $language |
455 | ]; |
456 | } |
457 | |
458 | public static function isSourcePage( PageIdentity $page ): bool { |
459 | if ( !$page->exists() ) { |
460 | // No point in loading all translatable pages if the page |
461 | // doesn’t exist. This also avoids PreconditionExceptions |
462 | // if $page is a Title pointing to a non-proper page like |
463 | // a special page. |
464 | return false; |
465 | } |
466 | |
467 | $localCache = MediaWikiServices::getInstance()->getLocalServerObjectCache(); |
468 | $localKey = $localCache->makeKey( 'pagetranslation', 'sourcepages', 'local' ); |
469 | // Store the value in the local cache for a short duration to reduce the number of |
470 | // times we hit the WAN cache. See: T366455 |
471 | $translatablePageIds = $localCache->getWithSetCallback( |
472 | $localKey, |
473 | $localCache::TTL_SECOND * 8, |
474 | static function () { |
475 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
476 | $cacheKey = $cache->makeKey( 'pagetranslation', 'sourcepages' ); |
477 | |
478 | return $cache->getWithSetCallback( |
479 | $cacheKey, |
480 | $cache::TTL_HOUR * 2, |
481 | [ TranslatablePage::class, 'getCacheValue' ], |
482 | [ |
483 | 'checkKeys' => [ $cacheKey ], |
484 | 'pcTTL' => $cache::TTL_PROC_SHORT, |
485 | 'pcGroup' => __CLASS__ . ':1', |
486 | 'version' => 3, |
487 | ] |
488 | ); |
489 | }, |
490 | $localCache::READ_LATEST |
491 | ); |
492 | |
493 | return str_contains( $translatablePageIds, ( ',' . $page->getId() . ',' ) ); |
494 | } |
495 | |
496 | /** Clears the source page cache */ |
497 | public static function clearSourcePageCache(): void { |
498 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
499 | $cache->touchCheckKey( $cache->makeKey( 'pagetranslation', 'sourcepages' ) ); |
500 | } |
501 | |
502 | public static function determineStatus( |
503 | ?int $readyRevisionId, |
504 | ?int $markRevisionId, |
505 | int $latestRevisionId |
506 | ): ?TranslatablePageStatus { |
507 | $status = null; |
508 | if ( $markRevisionId === null ) { |
509 | // Never marked, check that the latest version is ready |
510 | if ( $readyRevisionId === $latestRevisionId ) { |
511 | $status = TranslatablePageStatus::PROPOSED; |
512 | } else { |
513 | // Otherwise, ignore such pages |
514 | return null; |
515 | } |
516 | } elseif ( $readyRevisionId === $latestRevisionId ) { |
517 | if ( $markRevisionId === $readyRevisionId ) { |
518 | // Marked and latest version is fine |
519 | $status = TranslatablePageStatus::ACTIVE; |
520 | } else { |
521 | $status = TranslatablePageStatus::OUTDATED; |
522 | } |
523 | } else { |
524 | // Marked but latest version is not fine |
525 | $status = TranslatablePageStatus::BROKEN; |
526 | } |
527 | |
528 | return new TranslatablePageStatus( $status ); |
529 | } |
530 | |
531 | /** |
532 | * Get list of translatable page ids to be stored in the cache |
533 | * @internal |
534 | * @param mixed $oldValue |
535 | * @param int &$ttl |
536 | * @param array &$setOpts |
537 | * @return string |
538 | */ |
539 | public static function getCacheValue( $oldValue, &$ttl, array &$setOpts ): string { |
540 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
541 | $setOpts += Database::getCacheSetOptions( $dbr ); |
542 | |
543 | $ids = RevTagStore::getTranslatableBundleIds( |
544 | RevTagStore::TP_MARK_TAG, |
545 | RevTagStore::TP_READY_TAG |
546 | ); |
547 | |
548 | // Adding a comma at the end and beginning so that we can check for page ID |
549 | // existence with the "," delimiters |
550 | return ',' . implode( ',', $ids ) . ','; |
551 | } |
552 | } |
553 | |
554 | class_alias( TranslatablePage::class, 'TranslatablePage' ); |