Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroupStats.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\Statistics;
5
8use MediaWiki\Deferred\DeferredUpdates;
15use MediaWiki\Logger\LoggerFactory;
16use MediaWiki\MediaWikiServices;
17use MessageGroup;
18use stdClass;
19use Wikimedia\ObjectCache\WANObjectCache;
20use Wikimedia\Rdbms\Database;
21use Wikimedia\Rdbms\IDatabase;
22
36 private const TABLE = 'translate_groupstats';
38 private const LANGUAGE_STATS_KEY = 'translate-all-language-stats';
39
40 public const TOTAL = 0;
41 public const TRANSLATED = 1;
42 public const FUZZY = 2;
43 public const PROOFREAD = 3;
44
46 public const FLAG_CACHE_ONLY = 1;
48 public const FLAG_NO_CACHE = 2;
50 public const FLAG_IMMEDIATE_WRITES = 4;
51
53 private static array $updates = [];
55 private static ?array $languages = null;
56
61 public static function getEmptyStats(): array {
62 return [ 0, 0, 0, 0 ];
63 }
64
69 private static function getUnknownStats(): array {
70 return [ null, null, null, null ];
71 }
72
73 private static function isValidLanguage( string $languageCode ): bool {
74 $languages = self::getLanguages();
75 return in_array( $languageCode, $languages );
76 }
77
82 private static function isValidMessageGroup( ?MessageGroup $group ): bool {
83 return $group && !MessageGroups::isDynamic( $group );
84 }
85
93 public static function forItem( string $groupId, string $languageCode, int $flags = 0 ): array {
94 $group = MessageGroups::getGroup( $groupId );
95 if ( !self::isValidMessageGroup( $group ) || !self::isValidLanguage( $languageCode ) ) {
96 return self::getUnknownStats();
97 }
98
99 $res = self::selectRowsIdLang( [ $groupId ], [ $languageCode ], $flags );
100 $stats = self::extractResults( $res, [ $groupId ] );
101
102 if ( !isset( $stats[$groupId][$languageCode] ) ) {
103 $stats[$groupId][$languageCode] = self::forItemInternal( $stats, $group, $languageCode, $flags );
104 }
105
106 self::queueUpdates( $flags );
107
108 return $stats[$groupId][$languageCode];
109 }
110
117 public static function forLanguage( string $languageCode, int $flags = 0 ): array {
118 if ( !self::isValidLanguage( $languageCode ) ) {
119 $stats = [];
120 $groups = MessageGroups::singleton()->getGroups();
121 $ids = array_keys( $groups );
122 foreach ( $ids as $id ) {
123 $stats[$id] = self::getUnknownStats();
124 }
125
126 return $stats;
127 }
128
129 $stats = self::forLanguageInternal( $languageCode, [], $flags );
130 $flattened = [];
131 foreach ( $stats as $group => $languages ) {
132 $flattened[$group] = $languages[$languageCode];
133 }
134
135 self::queueUpdates( $flags );
136
137 return $flattened;
138 }
139
146 public static function forGroup( string $groupId, int $flags = 0 ): array {
147 $group = MessageGroups::getGroup( $groupId );
148 if ( !self::isValidMessageGroup( $group ) ) {
149 return array_fill_keys( self::getLanguages(), self::getUnknownStats() );
150 }
151
152 $stats = self::forGroupInternal( $group, [], $flags );
153
154 self::queueUpdates( $flags );
155
156 return $stats[$groupId];
157 }
158
164 public static function clear( MessageHandle $handle ): void {
165 $code = $handle->getCode();
166 if ( !self::isValidLanguage( $code ) ) {
167 return;
168 }
169 $groups = self::getSortedGroupsForClearing( $handle->getGroupIds() );
170 self::internalClearGroups( $code, $groups, 0 );
171 }
172
179 public static function clearGroup( $id, int $flags = 0 ): void {
180 $languages = self::getLanguages();
181 $groups = self::getSortedGroupsForClearing( (array)$id );
182
183 // Do one language at a time, to save memory
184 foreach ( $languages as $code ) {
185 self::internalClearGroups( $code, $groups, $flags );
186 }
187 }
188
195 public static function getApproximateLanguageStats(): array {
196 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
197 return $cache->getWithSetCallback(
198 self::LANGUAGE_STATS_KEY,
199 WANObjectCache::TTL_INDEFINITE,
200 function ( $oldValue, &$ttl, array &$setOpts ) {
201 $dbr = Utilities::getSafeReadDB();
202 $setOpts += Database::getCacheSetOptions( $dbr );
203
204 return self::getAllLanguageStats();
205 },
206 [
207 'checkKeys' => [ self::LANGUAGE_STATS_KEY ],
208 'pcTTL' => $cache::TTL_PROC_SHORT,
209 ]
210 );
211 }
212
213 private static function getAllLanguageStats(): array {
214 $dbr = Utilities::getSafeReadDB();
215 $res = $dbr->newSelectQueryBuilder()
216 ->table( self::TABLE )
217 ->select( [
218 'tgs_lang',
219 'tgs_translated' => 'SUM(tgs_translated)',
220 'tgs_fuzzy' => 'SUM(tgs_fuzzy)',
221 'tgs_total' => 'SUM(tgs_total)',
222 'tgs_proofread' => 'SUM(tgs_proofread)'
223 ] )
224 ->groupBy( 'tgs_lang' )
225 ->caller( __METHOD__ )
226 ->fetchResultSet();
227
229 $languagesCodes = array_flip( self::getLanguages() );
230
231 $allStats = [];
232 foreach ( $res as $row ) {
233 $allStats[ $row->tgs_lang ] = self::extractNumbers( $row );
234 unset( $languagesCodes[ $row->tgs_lang ] );
235 }
236
237 // Fill empty stats for missing language codes
238 foreach ( array_keys( $languagesCodes ) as $code ) {
239 $allStats[ $code ] = self::getEmptyStats();
240 }
241
242 return $allStats;
243 }
244
251 private static function internalClearGroups( string $code, array $groups, int $flags ): void {
252 $stats = [];
253 foreach ( $groups as $group ) {
254 // $stats is modified by reference
255 self::forItemInternal( $stats, $group, $code, $flags );
256 }
257 self::queueUpdates( 0 );
258 }
259
270 private static function getSortedGroupsForClearing( array $ids ): array {
271 $groups = array_map( [ MessageGroups::class, 'getGroup' ], $ids );
272 // Sanity: Remove any invalid groups
273 $groups = array_filter( $groups );
274
275 $sorted = [];
276 $aggregates = [];
277 foreach ( $groups as $group ) {
278 if ( $group instanceof AggregateMessageGroup ) {
279 $aggregates[$group->getId()] = $group;
280 } else {
281 $sorted[$group->getId()] = $group;
282 }
283 }
284
285 return array_merge( $sorted, $aggregates );
286 }
287
292 public static function getLanguages(): array {
293 if ( self::$languages === null ) {
294 $languages = array_keys( Utilities::getLanguageNames( 'en' ) );
295 sort( $languages );
296 self::$languages = $languages;
297 }
298
299 return self::$languages;
300 }
301
310 private static function extractResults( iterable $res, array $ids, array $stats = [] ): array {
311 // Map the internal ids back to real ids
312 $idMap = array_combine( array_map( [ self::class, 'getDatabaseIdForGroupId' ], $ids ), $ids );
313
314 foreach ( $res as $row ) {
315 if ( !isset( $idMap[$row->tgs_group] ) ) {
316 // Stale entry, ignore for now
317 // TODO: Schedule for purge
318 continue;
319 }
320
321 $realId = $idMap[$row->tgs_group];
322 $stats[$realId][$row->tgs_lang] = self::extractNumbers( $row );
323 }
324
325 return $stats;
326 }
327
329 private static function extractNumbers( stdClass $row ): array {
330 return [
331 self::TOTAL => (int)$row->tgs_total,
332 self::TRANSLATED => (int)$row->tgs_translated,
333 self::FUZZY => (int)$row->tgs_fuzzy,
334 self::PROOFREAD => (int)$row->tgs_proofread,
335 ];
336 }
337
344 private static function forLanguageInternal( string $languageCode, array $stats, int $flags ): array {
345 $groups = MessageGroups::singleton()->getGroups();
346
347 $ids = array_keys( $groups );
348 $res = self::selectRowsIdLang( null, [ $languageCode ], $flags );
349 $stats = self::extractResults( $res, $ids, $stats );
350
351 foreach ( $groups as $id => $group ) {
352 if ( isset( $stats[$id][$languageCode] ) ) {
353 continue;
354 }
355 $stats[$id][$languageCode] = self::forItemInternal( $stats, $group, $languageCode, $flags );
356 }
357
358 return $stats;
359 }
360
362 private static function expandAggregates( AggregateMessageGroup $agg ): array {
363 $flattened = [];
364
365 foreach ( $agg->getGroups() as $group ) {
366 if ( $group instanceof AggregateMessageGroup ) {
367 $flattened += self::expandAggregates( $group );
368 } else {
369 $flattened[$group->getId()] = $group;
370 }
371 }
372
373 return $flattened;
374 }
375
382 private static function forGroupInternal( MessageGroup $group, array $stats, int $flags ): array {
383 $id = $group->getId();
384
385 $res = self::selectRowsIdLang( [ $id ], null, $flags );
386 $stats = self::extractResults( $res, [ $id ], $stats );
387
388 // Go over each language filling missing entries
389 $languages = self::getLanguages();
390 foreach ( $languages as $code ) {
391 if ( isset( $stats[$id][$code] ) ) {
392 continue;
393 }
394 $stats[$id][$code] = self::forItemInternal( $stats, $group, $code, $flags );
395 }
396
397 // This is for sorting the values added later in correct order
398 foreach ( array_keys( $stats ) as $key ) {
399 ksort( $stats[$key] );
400 }
401
402 return $stats;
403 }
404
412 private static function selectRowsIdLang( ?array $ids, ?array $codes, int $flags ): iterable {
413 if ( $flags & self::FLAG_NO_CACHE ) {
414 return [];
415 }
416
417 $conditions = [];
418 if ( $ids !== null ) {
419 $dbids = array_map( [ self::class, 'getDatabaseIdForGroupId' ], $ids );
420 $conditions['tgs_group'] = $dbids;
421 }
422
423 if ( $codes !== null ) {
424 $conditions['tgs_lang'] = $codes;
425 }
426
427 $dbr = Utilities::getSafeReadDB();
428 return $dbr->newSelectQueryBuilder()
429 ->select( '*' )
430 ->from( self::TABLE )
431 ->where( $conditions )
432 ->caller( __METHOD__ )
433 ->fetchResultSet();
434 }
435
443 private static function forItemInternal(
444 array &$stats,
445 MessageGroup $group,
446 string $languageCode,
447 int $flags
448 ): array {
449 $id = $group->getId();
450
451 if ( $flags & self::FLAG_CACHE_ONLY ) {
452 $stats[$id][$languageCode] = self::getUnknownStats();
453 return $stats[$id][$languageCode];
454 }
455
456 // It may happen that caches are requested repeatedly for a group before we get a chance
457 // to write the values to the database. Check for queued updates first. This has the
458 // benefit of avoiding duplicate rows for inserts. Ideally this would be checked before we
459 // query the database for missing values. This code is somewhat ugly as it needs to
460 // reverse engineer the values from the row format.
461 $databaseGroupId = self::getDatabaseIdForGroupId( $id );
462 $uniqueKey = "$databaseGroupId|$languageCode";
463 $queuedValue = self::$updates[$uniqueKey] ?? null;
464 if ( $queuedValue && !( $flags & self::FLAG_NO_CACHE ) ) {
465 return [
466 self::TOTAL => $queuedValue['tgs_total'],
467 self::TRANSLATED => $queuedValue['tgs_translated'],
468 self::FUZZY => $queuedValue['tgs_fuzzy'],
469 self::PROOFREAD => $queuedValue['tgs_proofread'],
470 ];
471 }
472
473 if ( $group instanceof AggregateMessageGroup ) {
474 $aggregates = self::calculateAggregateGroup( $stats, $group, $languageCode, $flags );
475 } else {
476 $aggregates = self::calculateGroup( $group, $languageCode );
477 }
478 // Cache for use in subsequent forItemInternal calls
479 $stats[$id][$languageCode] = $aggregates;
480
481 // Don't add nulls to the database, causes annoying warnings
482 if ( $aggregates[self::TOTAL] === null ) {
483 return $aggregates;
484 }
485
486 self::$updates[$uniqueKey] = [
487 'tgs_group' => $databaseGroupId,
488 'tgs_lang' => $languageCode,
489 'tgs_total' => $aggregates[self::TOTAL],
490 'tgs_translated' => $aggregates[self::TRANSLATED],
491 'tgs_fuzzy' => $aggregates[self::FUZZY],
492 'tgs_proofread' => $aggregates[self::PROOFREAD],
493 ];
494
495 // For big and lengthy updates, attempt some interim saves. This might not have
496 // any effect, because writes to the database may be deferred.
497 if ( count( self::$updates ) % 100 === 0 ) {
498 self::queueUpdates( $flags );
499 }
500
501 return $aggregates;
502 }
503
504 private static function calculateAggregateGroup(
505 array &$stats,
507 string $code,
508 int $flags
509 ): array {
510 $aggregates = self::getEmptyStats();
511
512 $expanded = self::expandAggregates( $group );
513 $subGroupIds = array_keys( $expanded );
514
515 // Performance: if we have per-call cache of stats, do not query them again.
516 foreach ( $subGroupIds as $index => $sid ) {
517 if ( isset( $stats[$sid][$code] ) ) {
518 unset( $subGroupIds[ $index ] );
519 }
520 }
521
522 if ( $subGroupIds !== [] ) {
523 $res = self::selectRowsIdLang( $subGroupIds, [ $code ], $flags );
524 $stats = self::extractResults( $res, $subGroupIds, $stats );
525 }
526
527 $messageGroupMetadata = Services::getInstance()->getMessageGroupMetadata();
528 foreach ( $expanded as $sid => $subgroup ) {
529 // Discouraged groups may belong to another group, usually if there
530 // is an aggregate group for all translatable pages. In that case
531 // calculate and store the statistics, but don't count them as part of
532 // the aggregate group, so that the numbers in Special:LanguageStats
533 // add up. The statistics for discouraged groups can still be viewed
534 // through Special:MessageGroupStats.
535 if ( !isset( $stats[$sid][$code] ) ) {
536 $stats[$sid][$code] = self::forItemInternal( $stats, $subgroup, $code, $flags );
537 }
538
539 if ( !$messageGroupMetadata->isExcluded( $sid, $code ) ) {
540 $aggregates = self::multiAdd( $aggregates, $stats[$sid][$code] );
541 }
542 }
543
544 return $aggregates;
545 }
546
547 public static function multiAdd( array $a, array $b ): array {
548 if ( $a[0] === null || $b[0] === null ) {
549 return array_fill( 0, count( $a ), null );
550 }
551 foreach ( $a as $i => &$v ) {
552 $v += $b[$i];
553 }
554
555 return $a;
556 }
557
563 private static function calculateGroup( MessageGroup $group, string $languageCode ): array {
564 global $wgTranslateDocumentationLanguageCode;
565 // Calculate if missing and store in the db
566 $collection = $group->initCollection( $languageCode );
567
568 if (
569 $languageCode === $wgTranslateDocumentationLanguageCode
570 && $group instanceof FileBasedMessageGroup
571 ) {
572 $cache = $group->getMessageGroupCache( $group->getSourceLanguage() );
573 if ( $cache->exists() ) {
574 $template = $cache->getExtra()['TEMPLATE'] ?? [];
575 $infile = [];
576 foreach ( $template as $key => $data ) {
577 if ( isset( $data['comments']['.'] ) ) {
578 $infile[$key] = '1';
579 }
580 }
581 $collection->setInFile( $infile );
582 }
583 }
584
585 return self::getStatsForCollection( $collection );
586 }
587
588 private static function queueUpdates( int $flags ): void {
589 $mwInstance = MediaWikiServices::getInstance();
590 if ( self::$updates === [] || $mwInstance->getReadOnlyMode()->isReadOnly() ) {
591 return;
592 }
593
594 $dbw = $mwInstance->getConnectionProvider()->getPrimaryDatabase(); // avoid connecting yet
595 $callers = wfGetAllCallers( 50 );
596 $functionName = __METHOD__;
597 $callback = static function ( IDatabase $dbw, $method ) use ( $callers, $mwInstance ) {
598 // This path should only be hit during web requests
599 if ( count( self::$updates ) > 100 ) {
600 $groups = array_unique( array_column( self::$updates, 'tgs_group' ) );
601 LoggerFactory::getInstance( LogNames::MAIN )->warning(
602 "Huge translation update of {count} rows for group(s) {groups}",
603 [
604 'count' => count( self::$updates ),
605 'groups' => implode( ', ', $groups ),
606 'callers' => $callers,
607 ]
608 );
609 }
610
611 $dbw->newReplaceQueryBuilder()
612 ->replaceInto( self::TABLE )
613 ->uniqueIndexFields( [ 'tgs_group', 'tgs_lang' ] )
614 ->rows( array_values( self::$updates ) )
615 ->caller( $method )
616 ->execute();
617 self::$updates = [];
618
619 $mwInstance->getMainWANObjectCache()->touchCheckKey( self::LANGUAGE_STATS_KEY );
620 };
621 $updateOp = static function () use ( $dbw, $functionName, $callback ) {
622 // Maybe another deferred update already processed these
623 if ( self::$updates === [] ) {
624 return;
625 }
626
627 $lockName = 'MessageGroupStats:updates';
628 if ( !$dbw->lock( $lockName, $functionName, 1 ) ) {
629 $groups = array_unique( array_column( self::$updates, 'tgs_group' ) );
630 LoggerFactory::getInstance( LogNames::MAIN )->warning(
631 'Message group stats update of {count} rows failed for group(s) {groups} due to lock',
632 [
633 'count' => count( self::$updates ),
634 'groups' => implode( ', ', $groups ),
635 ]
636 );
637
638 return; // raced out
639 }
640
641 $dbw->commit( $functionName, 'flush' );
642 $callback( $dbw, $functionName );
643 $dbw->commit( $functionName, 'flush' );
644
645 $dbw->unlock( $lockName, $functionName );
646 };
647
648 if ( $flags & self::FLAG_IMMEDIATE_WRITES ) {
649 $updateOp();
650 } else {
651 DeferredUpdates::addCallableUpdate( $updateOp );
652 }
653 }
654
655 public static function getDatabaseIdForGroupId( string $id ): string {
656 // The column is 100 bytes long, but we don't need to use it all
657 if ( strlen( $id ) <= 72 ) {
658 return $id;
659 }
660
661 $hash = hash( 'sha256', $id, /*asHex*/false );
662 return substr( $id, 0, 50 ) . '||' . substr( $hash, 0, 20 );
663 }
664
666 public static function getStatsForCollection( MessageCollection $collection ): array {
667 $collection->filter( MessageCollection::FILTER_IGNORED, MessageCollection::EXCLUDE_MATCHING );
668 $collection->filterUntranslatedOptional();
669 // Store the count of real messages for later calculation.
670 $total = count( $collection );
671
672 // Count fuzzy first.
673 $collection->filter( MessageCollection::FILTER_FUZZY, MessageCollection::EXCLUDE_MATCHING );
674 $fuzzy = $total - count( $collection );
675
676 // Count the completed translations.
677 $collection->filter( MessageCollection::FILTER_HAS_TRANSLATION, MessageCollection::INCLUDE_MATCHING );
678 $translated = count( $collection );
679
680 // Count how many of the completed translations
681 // have been proofread
682 $collection->filter( MessageCollection::FILTER_REVIEWER, MessageCollection::INCLUDE_MATCHING );
683 $proofread = count( $collection );
684
685 return [
686 self::TOTAL => $total,
687 self::TRANSLATED => $translated,
688 self::FUZZY => $fuzzy,
689 self::PROOFREAD => $proofread,
690 ];
691 }
692}
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager($services->getTitleFactory(), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:AggregateGroupMessageGroupFactory'=> static function(MediaWikiServices $services):AggregateGroupMessageGroupFactory { return new AggregateGroupMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), $services->getContentLanguageCode() ->toString(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance(LogNames::GROUP_SUBSCRIPTION), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):?MessageGroupSubscriptionHookHandler { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getConnectionProvider(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getConnectionProvider());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getConnectionProvider());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup(), $services->getNamespaceInfo(), $services->getTitleFactory(), $services->getFormatterFactory());}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory(), $services->get( 'Translate:HookRunner'),);}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getConnectionProvider());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@phpcs-require-sorted-array
Groups multiple message groups together as one group.
getGroups()
Returns a list of message groups that this group consists of.
This class implements default behavior for file based message groups.
Constants for log channel names used in this extension.
Definition LogNames.php:13
Factory class for accessing message groups individually by id or all of them as a list.
This file contains the class for core message collections implementation.
filter(string $filter, bool $condition, ?int $value=null)
Filters messages based on some condition.
Class for pointing to messages, like Title class is for titles.
getGroupIds()
Returns all message group ids this message belongs to.
Minimal service container.
Definition Services.php:60
This class aims to provide efficient mechanism for fetching translation completion stats.
static clearGroup( $id, int $flags=0)
Recalculate stats for given group(s).
static clear(MessageHandle $handle)
Recalculate stats for all groups associated with the message.
static getStatsForCollection(MessageCollection $collection)
static getLanguages()
Get list of supported languages for statistics.
static forItem(string $groupId, string $languageCode, int $flags=0)
Returns stats for given group in given language.
static forLanguage(string $languageCode, int $flags=0)
Returns stats for all groups in given language.
const FLAG_CACHE_ONLY
If stats are not cached, do not attempt to calculate them on the fly.
static getApproximateLanguageStats()
Fetch aggregated statistics for all languages across groups.
static forGroup(string $groupId, int $flags=0)
Returns stats for all languages in given group.
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:29
Interface for message groups.
initCollection( $code)
Initialises a message collection with the given language code, message definitions and message tags.
getSourceLanguage()
Returns language code depicting the language of source text.