Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroups.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageGroupProcessing;
5
9use DependencyWrapper;
10use InvalidArgumentException;
14use MediaWiki\MediaWikiServices;
15use MessageGroup;
19use RuntimeException;
20use Title;
22use WANObjectCache;
23
36 private $groups;
38 private $groupLoaders;
40 private $cache;
41
48 private const CACHE_VERSION = 4;
49
51 protected function init(): void {
52 if ( is_array( $this->groups ) ) {
53 return; // groups already initialized
54 }
55
56 $value = $this->getCachedGroupDefinitions();
57 $groups = $value['cc'];
58
59 foreach ( $this->getGroupLoaders() as $loader ) {
60 $groups += $loader->getGroups();
61 }
62 $this->initGroupsFromDefinitions( $groups );
63 }
64
66 protected function getCachedGroupDefinitions( $recache = false ): array {
67 global $wgAutoloadClasses;
68
69 $regenerator = function () {
70 global $wgAutoloadClasses;
71
72 $groups = $deps = $autoload = [];
73 // This constructs the list of all groups from multiple different sources.
74 // When possible, a cache dependency is created to automatically recreate
75 // the cache when configuration changes. Currently used by other extensions
76 // such as Banner Messages and test cases to load message groups.
77 Services::getInstance()->getHookRunner()
78 ->onTranslatePostInitGroups( $groups, $deps, $autoload );
79 // Register autoloaders for this request, both values modified by reference
80 self::appendAutoloader( $autoload, $wgAutoloadClasses );
81
82 $value = [
83 'ts' => wfTimestamp( TS_MW ),
84 'cc' => $groups,
85 'autoload' => $autoload
86 ];
87 $wrapper = new DependencyWrapper( $value, $deps );
88 $wrapper->initialiseDeps();
89
90 return $wrapper; // save the new value to cache
91 };
92
93 $cache = $this->getCache();
95 $wrapper = $cache->getWithSetCallback(
96 $this->getCacheKey(),
97 $cache::TTL_DAY,
98 $regenerator,
99 [
100 'lockTSE' => 30, // avoid stampedes (mutex)
101 'checkKeys' => [ $this->getCacheKey() ],
102 'touchedCallback' => static function ( $value ) {
103 return ( $value instanceof DependencyWrapper && $value->isExpired() )
104 ? time() // treat value as if it just expired (for "lockTSE")
105 : null;
106 },
107 'minAsOf' => $recache ? INF : $cache::MIN_TIMESTAMP_NONE, // "miss" on recache
108 ]
109 );
110
111 $value = $wrapper->getValue();
112 self::appendAutoloader( $value['autoload'], $wgAutoloadClasses );
113
114 return $value;
115 }
116
122 protected function initGroupsFromDefinitions( array $groups ): void {
123 foreach ( $groups as $id => $mixed ) {
124 if ( !is_object( $mixed ) ) {
125 $groups[$id] = call_user_func( $mixed, $id );
126 }
127 }
128
129 $this->groups = $groups;
130 }
131
133 public function recache(): void {
134 // Purge the value from all datacenters
135 $cache = $this->getCache();
136 $cache->touchCheckKey( $this->getCacheKey() );
137
138 $this->clearProcessCache();
139
140 foreach ( $this->getCacheGroupLoaders() as $cacheLoader ) {
141 $cacheLoader->recache();
142 }
143
144 // Reload the cache value and update the local datacenter
145 $value = $this->getCachedGroupDefinitions( 'recache' );
146 $groups = $value['cc'];
147
148 foreach ( $this->getGroupLoaders() as $loader ) {
149 $groups += $loader->getGroups();
150 }
151
152 $this->initGroupsFromDefinitions( $groups );
153 }
154
160 public static function clearCache(): void {
161 $self = self::singleton();
162
163 $cache = $self->getCache();
164 $cache->delete( $self->getCacheKey(), 1 );
165
166 foreach ( $self->getCacheGroupLoaders() as $cacheLoader ) {
167 $cacheLoader->clearCache();
168 }
169
170 $self->clearProcessCache();
171 }
172
179 public function clearProcessCache(): void {
180 $this->groups = null;
181 $this->groupLoaders = null;
182 }
183
184 protected function getCache(): WANObjectCache {
185 if ( $this->cache === null ) {
186 return MediaWikiServices::getInstance()->getMainWANObjectCache();
187 } else {
188 return $this->cache;
189 }
190 }
191
193 public function setCache( ?WANObjectCache $cache = null ) {
194 $this->cache = $cache;
195 }
196
198 public function getCacheKey(): string {
199 return $this->getCache()->makeKey( 'translate-groups', 'v' . self::CACHE_VERSION );
200 }
201
208 protected static function appendAutoloader( array &$additions, array &$to ): void {
209 foreach ( $additions as $class => $file ) {
210 if ( isset( $to[$class] ) && $to[$class] !== $file ) {
211 $msg = "Autoload conflict for $class: {$to[$class]} !== $file";
212 trigger_error( $msg, E_USER_WARNING );
213 continue;
214 }
215
216 $to[$class] = $file;
217 }
218 }
219
225 protected function getGroupLoaders(): array {
226 if ( $this->groupLoaders !== null ) {
227 return $this->groupLoaders;
228 }
229
230 $cache = $this->getCache();
231
232 $groupLoaderInstances = $this->groupLoaders = [];
233
234 // Initialize the dependencies
235 $deps = [
236 'database' => Utilities::getSafeReadDB(),
237 'cache' => $cache
238 ];
239
240 Services::getInstance()->getHookRunner()
241 ->onTranslateInitGroupLoaders( $groupLoaderInstances, $deps );
242
243 if ( $groupLoaderInstances === [] ) {
244 return $this->groupLoaders;
245 }
246
247 foreach ( $groupLoaderInstances as $loader ) {
248 if ( !$loader instanceof MessageGroupLoader ) {
249 throw new InvalidArgumentException(
250 "MessageGroupLoader - $loader must implement the " .
251 "MessageGroupLoader interface."
252 );
253 }
254
255 $this->groupLoaders[] = $loader;
256 }
257
258 return $this->groupLoaders;
259 }
260
266 protected function getCacheGroupLoaders(): array {
267 // @phan-suppress-next-line PhanTypeMismatchReturn
268 return array_filter( $this->getGroupLoaders(), static function ( $groupLoader ) {
269 return $groupLoader instanceof CachedMessageGroupLoader;
270 } );
271 }
272
279 public static function getGroup( string $id ): ?MessageGroup {
280 $groups = self::singleton()->getGroups();
281 $id = self::normalizeId( $id );
282
283 if ( isset( $groups[$id] ) ) {
284 return $groups[$id];
285 }
286
287 if ( $id !== '' && $id[0] === '!' ) {
288 $dynamic = self::getDynamicGroups();
289 if ( isset( $dynamic[$id] ) ) {
290 return new $dynamic[$id];
291 }
292 }
293
294 return null;
295 }
296
298 public static function normalizeId( ?string $id ): string {
299 /* Translatable pages use spaces, but MW occasionally likes to
300 * normalize spaces to underscores */
301 $id = (string)$id;
302
303 if ( str_starts_with( $id, 'page-' ) ) {
304 $id = strtr( $id, '_', ' ' );
305 }
306
307 global $wgTranslateGroupAliases;
308 if ( isset( $wgTranslateGroupAliases[$id] ) ) {
309 $id = $wgTranslateGroupAliases[$id];
310 }
311
312 return $id;
313 }
314
315 public static function exists( string $id ): bool {
316 return (bool)self::getGroup( $id );
317 }
318
320 public static function labelExists( string $name ): bool {
321 $loader = AggregateMessageGroupLoader::getInstance();
322 $labels = array_map( static function ( MessageGroupBase $g ) {
323 return $g->getLabel();
324 }, $loader->loadAggregateGroups() );
325 return in_array( $name, $labels, true );
326 }
327
332 public static function getAllGroups(): array {
333 return self::singleton()->getGroups();
334 }
335
344 public static function getPriority( $group ): string {
345 if ( $group instanceof MessageGroup ) {
346 $id = $group->getId();
347 } else {
348 $id = self::normalizeId( $group );
349 }
350
351 return Services::getInstance()->getMessageGroupReviewStore()->getGroupPriority( $id ) ?? '';
352 }
353
360 public static function setPriority( $group, string $priority = '' ): void {
361 if ( $group instanceof MessageGroup ) {
362 $id = $group->getId();
363 } else {
364 $id = self::normalizeId( $group );
365 }
366
367 $priority = $priority === '' ? null : $priority;
368 Services::getInstance()->getMessageGroupReviewStore()->setGroupPriority( $id, $priority );
369 }
370
371 public static function isDynamic( MessageGroup $group ): bool {
372 $id = $group->getId();
373
374 return ( $id[0] ?? null ) === '!';
375 }
376
381 public static function getSharedGroups( MessageGroup $group ): array {
382 // Take the first message, get a handle for it and check
383 // if that message belongs to other groups. Those are the
384 // parent aggregate groups. Ideally we loop over all keys,
385 // but this should be enough.
386 $keys = array_keys( $group->getDefinitions() );
387 $title = Title::makeTitle( $group->getNamespace(), $keys[0] );
388 $handle = new MessageHandle( $title );
389 $ids = $handle->getGroupIds();
390 foreach ( $ids as $index => $id ) {
391 if ( $id === $group->getId() ) {
392 unset( $ids[$index] );
393 break;
394 }
395 }
396
397 return $ids;
398 }
399
404 public static function getParentGroups( MessageGroup $targetGroup ): array {
405 $ids = self::getSharedGroups( $targetGroup );
406 if ( $ids === [] ) {
407 return [];
408 }
409
410 $targetId = $targetGroup->getId();
411
412 /* Get the group structure. We will be using this to find which
413 * of our candidates are top-level groups. Prefilter it to only
414 * contain aggregate groups. */
415 $structure = self::getGroupStructure();
416 foreach ( $structure as $index => $group ) {
417 if ( $group instanceof MessageGroup ) {
418 unset( $structure[$index] );
419 } else {
420 $structure[$index] = array_shift( $group );
421 }
422 }
423
424 /* Now that we have all related groups, use them to find all paths
425 * from top-level groups to target group with any number of subgroups
426 * in between. */
427 $paths = [];
428
429 /* This function recursively finds paths to the target group */
430 $pathFinder = static function ( &$paths, $group, $targetId, $prefix = '' )
431 use ( &$pathFinder ) {
432 if ( $group instanceof AggregateMessageGroup ) {
433 foreach ( $group->getGroups() as $subgroup ) {
434 $subId = $subgroup->getId();
435 if ( $subId === $targetId ) {
436 $paths[] = $prefix;
437 continue;
438 }
439
440 $pathFinder( $paths, $subgroup, $targetId, "$prefix|$subId" );
441 }
442 }
443 };
444
445 // Iterate over the top-level groups only
446 foreach ( $ids as $id ) {
447 // First, find a top level groups
448 $group = self::getGroup( $id );
449
450 // Quick escape for leaf groups
451 if ( !$group instanceof AggregateMessageGroup ) {
452 continue;
453 }
454
455 foreach ( $structure as $rootGroup ) {
457 if ( $rootGroup->getId() === $group->getId() ) {
458 // Yay we found a top-level group
459 $pathFinder( $paths, $rootGroup, $targetId, $id );
460 break; // No we have one or more paths appended into $paths
461 }
462 }
463 }
464
465 // And finally explode the strings
466 return array_map( static function ( string $pathString ): array {
467 return explode( '|', $pathString );
468 }, $paths );
469 }
470
471 public static function singleton(): self {
472 static $instance;
473 if ( !$instance instanceof self ) {
474 $instance = new self();
475 }
476
477 return $instance;
478 }
479
485 public function getGroups(): array {
486 $this->init();
487
488 return $this->groups;
489 }
490
499 public static function getGroupsById( array $ids, bool $skipMeta = false ): array {
500 $groups = [];
501 foreach ( $ids as $id ) {
502 $group = self::getGroup( $id );
503
504 if ( $group !== null ) {
505 if ( $skipMeta && $group->isMeta() ) {
506 continue;
507 } else {
508 $groups[$id] = $group;
509 }
510 } else {
511 wfDebug( __METHOD__ . ": Invalid message group id: $id\n" );
512 }
513 }
514
515 return $groups;
516 }
517
525 public static function expandWildcards( $ids ): array {
526 $all = [];
527
528 $ids = (array)$ids;
529 foreach ( $ids as $index => $id ) {
530 // Fast path, no wildcards
531 if ( strcspn( $id, '*?' ) === strlen( $id ) ) {
532 $g = self::getGroup( $id );
533 if ( $g ) {
534 $all[] = $g->getId();
535 }
536 unset( $ids[$index] );
537 }
538 }
539
540 if ( $ids === [] ) {
541 return $all;
542 }
543
544 // Slow path for the ones with wildcards
545 $matcher = new StringMatcher( '', $ids );
546 foreach ( self::getAllGroups() as $id => $_ ) {
547 if ( $matcher->matches( $id ) ) {
548 $all[] = $id;
549 }
550 }
551
552 return $all;
553 }
554
556 public static function getDynamicGroups(): array {
557 return [
558 '!recent' => 'RecentMessageGroup',
559 '!additions' => 'RecentAdditionsMessageGroup',
560 '!sandbox' => 'SandboxMessageGroup',
561 ];
562 }
563
573 public static function getGroupsByType( string $type ): array {
574 $groups = self::getAllGroups();
575 foreach ( $groups as $id => $group ) {
576 if ( !$group instanceof $type ) {
577 unset( $groups[$id] );
578 }
579 }
580
581 // @phan-suppress-next-line PhanTypeMismatchReturn
582 return $groups;
583 }
584
594 public static function getGroupStructure(): array {
595 $groups = self::getAllGroups();
596
597 // Determine the top level groups of the tree
598 $tree = $groups;
599 foreach ( $groups as $id => $o ) {
600 if ( !$o->exists() ) {
601 unset( $groups[$id], $tree[$id] );
602 continue;
603 }
604
605 if ( $o instanceof AggregateMessageGroup ) {
606 foreach ( $o->getGroups() as $sid => $so ) {
607 unset( $tree[$sid] );
608 }
609 }
610 }
611
612 usort( $tree, [ self::class, 'groupLabelSort' ] );
613
614 /* Now we have two things left in $tree array:
615 * - solitaries: top-level non-aggregate message groups
616 * - top-level aggregate message groups */
617 foreach ( $tree as $index => $group ) {
618 if ( $group instanceof AggregateMessageGroup ) {
619 $tree[$index] = self::subGroups( $group );
620 }
621 }
622
623 /* Essentially we are done now. Cyclic groups can cause part of the
624 * groups not be included at all, because they have all unset each
625 * other in the first loop. So now we check if there are groups left
626 * over. */
627 $used = [];
628 array_walk_recursive(
629 $tree,
630 static function ( MessageGroup $group ) use ( &$used ) {
631 $used[$group->getId()] = true;
632 }
633 );
634 $unused = array_diff_key( $groups, $used );
635 if ( $unused ) {
636 foreach ( $unused as $index => $group ) {
637 if ( !$group instanceof AggregateMessageGroup ) {
638 unset( $unused[$index] );
639 }
640 }
641
642 // Only list the aggregate groups, other groups cannot cause cycles
643 $participants = implode( ', ', array_keys( $unused ) );
644 throw new RuntimeException( "Found cyclic aggregate message groups: $participants" );
645 }
646
647 return $tree;
648 }
649
651 public static function groupLabelSort( MessageGroup $a, MessageGroup $b ): int {
652 $al = $a->getLabel();
653 $bl = $b->getLabel();
654
655 return strcasecmp( $al, $bl );
656 }
657
667 public static function subGroups(
668 AggregateMessageGroup $parent,
669 array &$childIds = [],
670 string $fname = 'caller'
671 ): array {
672 static $recursionGuard = [];
673
674 $pid = $parent->getId();
675 if ( isset( $recursionGuard[$pid] ) ) {
676 $tid = $pid;
677 $path = [ $tid ];
678 do {
679 $tid = $recursionGuard[$tid];
680 $path[] = $tid;
681 // Until we have gone full cycle
682 } while ( $tid !== $pid );
683 $path = implode( ' > ', $path );
684 throw new RuntimeException( "Found cyclic aggregate message groups: $path" );
685 }
686
687 // We don't care about the ids.
688 $tree = array_values( $parent->getGroups() );
689 usort( $tree, [ self::class, 'groupLabelSort' ] );
690 // Expand aggregate groups (if any left) after sorting to form a tree
691 foreach ( $tree as $index => $group ) {
692 if ( $group instanceof AggregateMessageGroup ) {
693 $sid = $group->getId();
694 $recursionGuard[$pid] = $sid;
695 $tree[$index] = self::subGroups( $group, $childIds, __METHOD__ );
696 unset( $recursionGuard[$pid] );
697
698 $childIds[$sid] = 1;
699 }
700 }
701
702 // Parent group must be first item in the array
703 array_unshift( $tree, $parent );
704
705 if ( $fname !== __METHOD__ ) {
706 // Move the IDs from the keys to the value for final return
707 $childIds = array_values( $childIds );
708 }
709
710 return $tree;
711 }
712
718 public static function haveSingleSourceLanguage( array $groups ): string {
719 $seen = '';
720
721 foreach ( $groups as $group ) {
722 $language = $group->getSourceLanguage();
723 if ( $seen === '' ) {
724 $seen = $language;
725 } elseif ( $language !== $seen ) {
726 return '';
727 }
728 }
729
730 return $seen;
731 }
732
741 public static function isTranslatableMessage( MessageHandle $handle, string $targetLanguage ): bool {
742 static $cache = [];
743
744 if ( !$handle->isValid() ) {
745 return false;
746 }
747
748 $group = $handle->getGroup();
749 $groupId = $group->getId();
750 $cacheKey = "$groupId:$targetLanguage";
751
752 if ( !isset( $cache[$cacheKey] ) ) {
753 $supportedLanguages = Utilities::getLanguageNames( 'en' );
754 $inclusionList = $group->getTranslatableLanguages() ?? $supportedLanguages;
755
756 $included = isset( $inclusionList[$targetLanguage] );
757 $excluded = TranslateMetadata::isExcluded( $groupId, $targetLanguage );
758
759 $cache[$cacheKey] = [
760 'relevant' => $included && !$excluded,
761 'tags' => [],
762 ];
763
764 $groupTags = $group->getTags();
765 foreach ( [ 'ignored', 'optional' ] as $tag ) {
766 if ( isset( $groupTags[$tag] ) ) {
767 foreach ( $groupTags[$tag] as $key ) {
768 // TODO: ucfirst should not be here
769 $cache[$cacheKey]['tags'][ucfirst( $key )] = true;
770 }
771 }
772 }
773 }
774
775 return $cache[$cacheKey]['relevant'] &&
776 !isset( $cache[$cacheKey]['tags'][ucfirst( $handle->getKey() )] );
777 }
778}
779
780class_alias( MessageGroups::class, 'MessageGroups' );
return[ '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:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'));}, '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:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getDBLoadBalancer(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, '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->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore($services->getDBLoadBalancerFactory());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getDBLoadBalancer());}, '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());}, '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->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'),);}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnection(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getDBLoadBalancer());}, '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->getDBLoadBalancer());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);}]
@phpcs-require-sorted-array
Loads AggregateMessageGroup, and handles the related cache.
Groups multiple message groups together as one group.
getGroups()
Returns a list of message groups that this group consists of.
Factory class for accessing message groups individually by id or all of them as a list.
static getGroup(string $id)
Fetch a message group by id.
static getPriority( $group)
We want to de-emphasize time sensitive groups like news for 2009.
static getDynamicGroups()
Contents on these groups changes on a whim.
static getGroupsByType(string $type)
Get only groups of specific type (class).
static getSharedGroups(MessageGroup $group)
Returns a list of message groups that share (certain) messages with this group.
static getGroupsById(array $ids, bool $skipMeta=false)
Get message groups for corresponding message group ids.
static isTranslatableMessage(MessageHandle $handle, string $targetLanguage)
Filters out messages that should not be translated under normal conditions.
static getGroupStructure()
Returns a tree of message groups.
setCache(?WANObjectCache $cache=null)
Override cache, for example during tests.
static normalizeId(?string $id)
Fixes the id and resolves aliases.
getGroups()
Get all enabled non-dynamic message groups.
static subGroups(AggregateMessageGroup $parent, array &$childIds=[], string $fname='caller')
Like getGroupStructure but start from one root which must be an AggregateMessageGroup.
getCacheGroupLoaders()
Returns group loaders that implement the CachedMessageGroupLoader.
static appendAutoloader(array &$additions, array &$to)
Safely merges first array to second array, throwing warning on duplicates and removing duplicates fro...
static haveSingleSourceLanguage(array $groups)
Checks whether all the message groups have the same source language.
static labelExists(string $name)
Check if a particular aggregate group label exists.
static expandWildcards( $ids)
If the list of message group ids contains wildcards, this function will match them against the list o...
static groupLabelSort(MessageGroup $a, MessageGroup $b)
Sorts groups by label value.
static setPriority( $group, string $priority='')
Sets the message group priority.
initGroupsFromDefinitions(array $groups)
Expand process cached groups to objects.
static getParentGroups(MessageGroup $targetGroup)
Returns a list of parent message groups.
The versatile default implementation of StringMangler interface.
Minimal service container.
Definition Services.php:44
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
This class implements some basic functions that wrap around the YAML message group configurations.
getLabel(IContextSource $context=null)
Returns the human readable label (as plain text).
getId()
Returns the unique identifier for this group.
An abstract class to be implemented by group loaders / stores.
Class for pointing to messages, like Title class is for titles.
getGroup()
Get the primary MessageGroup this message belongs to.
isValid()
Checks if the handle corresponds to a known message.
getKey()
Returns the identified or guessed message key.
To be implemented by MessageGroupLoaders that use the MessageGroupWANCache.
Interface for message groups.
getTranslatableLanguages()
Get all the translatable languages for a group, considering the inclusion and exclusion list.
getTags( $type=null)
Returns message tags.
getNamespace()
Returns the namespace where messages are placed.
getId()
Returns the unique identifier for this group.
getLabel(IContextSource $context=null)
Returns the human readable label (as plain text).