Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageGroups.php
Go to the documentation of this file.
1<?php
13use MediaWiki\MediaWikiServices;
14
22 private static $prioritycache;
24 private $groups;
26 private $groupLoaders;
28 private $cache;
29
36 private const CACHE_VERSION = 4;
37
41 protected function init() {
42 if ( is_array( $this->groups ) ) {
43 return; // groups already initialized
44 }
45
46 $value = $this->getCachedGroupDefinitions();
47 $groups = $value['cc'];
48
49 foreach ( $this->getGroupLoaders() as $loader ) {
50 $groups += $loader->getGroups();
51 }
52 $this->initGroupsFromDefinitions( $groups );
53 }
54
59 protected function getCachedGroupDefinitions( $recache = false ) {
60 global $wgAutoloadClasses;
61
62 $regenerator = function () {
63 global $wgAutoloadClasses;
64
65 $groups = $deps = $autoload = [];
66 // This constructs the list of all groups from multiple different sources.
67 // When possible, a cache dependency is created to automatically recreate
68 // the cache when configuration changes. Currently used by other extensions
69 // such as Banner Messages and test cases to load message groups.
70 Hooks::run( 'TranslatePostInitGroups', [ &$groups, &$deps, &$autoload ] );
71 // Register autoloaders for this request, both values modified by reference
72 self::appendAutoloader( $autoload, $wgAutoloadClasses );
73
74 $value = [
75 'ts' => wfTimestamp( TS_MW ),
76 'cc' => $groups,
77 'autoload' => $autoload
78 ];
79 $wrapper = new DependencyWrapper( $value, $deps );
80 $wrapper->initialiseDeps();
81
82 return $wrapper; // save the new value to cache
83 };
84
85 $cache = $this->getCache();
87 $wrapper = $cache->getWithSetCallback(
88 $this->getCacheKey(),
89 $cache::TTL_DAY,
90 $regenerator,
91 [
92 'lockTSE' => 30, // avoid stampedes (mutex)
93 'checkKeys' => [ $this->getCacheKey() ],
94 'touchedCallback' => static function ( $value ) {
95 return ( $value instanceof DependencyWrapper && $value->isExpired() )
96 ? time() // treat value as if it just expired (for "lockTSE")
97 : null;
98 },
99 'minAsOf' => $recache ? INF : $cache::MIN_TIMESTAMP_NONE, // "miss" on recache
100 ]
101 );
102
103 $value = $wrapper->getValue();
104 self::appendAutoloader( $value['autoload'], $wgAutoloadClasses );
105
106 return $value;
107 }
108
114 protected function initGroupsFromDefinitions( $groups ) {
115 foreach ( $groups as $id => $mixed ) {
116 if ( !is_object( $mixed ) ) {
117 $groups[$id] = call_user_func( $mixed, $id );
118 }
119 }
120
121 $this->groups = $groups;
122 }
123
129 public function recache() {
130 // Purge the value from all datacenters
131 $cache = $this->getCache();
132 $cache->touchCheckKey( $this->getCacheKey() );
133
134 $this->clearProcessCache();
135
136 foreach ( $this->getCacheGroupLoaders() as $cacheLoader ) {
137 $cacheLoader->recache();
138 }
139
140 // Reload the cache value and update the local datacenter
141 $value = $this->getCachedGroupDefinitions( 'recache' );
142 $groups = $value['cc'];
143
144 foreach ( $this->getGroupLoaders() as $loader ) {
145 $groups += $loader->getGroups();
146 }
147
148 $this->initGroupsFromDefinitions( $groups );
149 }
150
156 public static function clearCache() {
157 $self = self::singleton();
158
159 $cache = $self->getCache();
160 $cache->delete( $self->getCacheKey(), 1 );
161
162 foreach ( $self->getCacheGroupLoaders() as $cacheLoader ) {
163 $cacheLoader->clearCache();
164 }
165
166 $self->clearProcessCache();
167 }
168
176 public function clearProcessCache() {
177 $this->groups = null;
178 $this->groupLoaders = null;
179
180 self::$prioritycache = null;
181 }
182
183 protected function getCache(): WANObjectCache {
184 if ( $this->cache === null ) {
185 return MediaWikiServices::getInstance()->getMainWANObjectCache();
186 } else {
187 return $this->cache;
188 }
189 }
190
196 public function setCache( WANObjectCache $cache = null ) {
197 $this->cache = $cache;
198 }
199
205 public function getCacheKey(): string {
206 return $this->getCache()->makeKey( 'translate-groups', 'v' . self::CACHE_VERSION );
207 }
208
215 protected static function appendAutoloader( array &$additions, array &$to ) {
216 foreach ( $additions as $class => $file ) {
217 if ( isset( $to[$class] ) && $to[$class] !== $file ) {
218 $msg = "Autoload conflict for $class: {$to[$class]} !== $file";
219 trigger_error( $msg, E_USER_WARNING );
220 continue;
221 }
222
223 $to[$class] = $file;
224 }
225 }
226
232 protected function getGroupLoaders() {
233 if ( $this->groupLoaders !== null ) {
234 return $this->groupLoaders;
235 }
236
237 $cache = $this->getCache();
238
239 $groupLoaderInstances = $this->groupLoaders = [];
240
241 // Initialize the dependencies
242 $deps = [
243 'database' => TranslateUtils::getSafeReadDB(),
244 'cache' => $cache
245 ];
246
247 Hooks::run( 'TranslateInitGroupLoaders', [ &$groupLoaderInstances, $deps ] );
248
249 if ( $groupLoaderInstances === [] ) {
250 return $this->groupLoaders;
251 }
252
253 // @phan-suppress-next-line PhanEmptyForeach False positive
254 foreach ( $groupLoaderInstances as $loader ) {
255 if ( !$loader instanceof MessageGroupLoader ) {
256 throw new InvalidArgumentException(
257 "MessageGroupLoader - $loader must implement the " .
258 "MessageGroupLoader interface."
259 );
260 }
261
262 $this->groupLoaders[] = $loader;
263 }
264
265 return $this->groupLoaders;
266 }
267
273 protected function getCacheGroupLoaders() {
274 // @phan-suppress-next-line PhanTypeMismatchReturn
275 return array_filter( $this->getGroupLoaders(), static function ( $groupLoader ) {
276 return $groupLoader instanceof CachedMessageGroupLoader;
277 } );
278 }
279
286 public static function getGroup( $id ) {
287 $groups = self::singleton()->getGroups();
288 $id = self::normalizeId( $id );
289
290 if ( isset( $groups[$id] ) ) {
291 return $groups[$id];
292 }
293
294 if ( (string)$id !== '' && $id[0] === '!' ) {
295 $dynamic = self::getDynamicGroups();
296 if ( isset( $dynamic[$id] ) ) {
297 return new $dynamic[$id];
298 }
299 }
300
301 return null;
302 }
303
311 public static function normalizeId( $id ) {
312 /* Translatable pages use spaces, but MW occasionally likes to
313 * normalize spaces to underscores */
314 if ( strpos( $id, 'page-' ) === 0 ) {
315 $id = strtr( $id, '_', ' ' );
316 }
317
318 global $wgTranslateGroupAliases;
319 if ( isset( $wgTranslateGroupAliases[$id] ) ) {
320 $id = $wgTranslateGroupAliases[$id];
321 }
322
323 return $id;
324 }
325
330 public static function exists( $id ) {
331 return (bool)self::getGroup( $id );
332 }
333
339 public static function labelExists( $name ) {
341 $labels = array_map( static function ( MessageGroupBase $g ) {
342 return $g->getLabel();
343 }, $loader->loadAggregateGroups() );
344 return in_array( $name, $labels, true );
345 }
346
351 public static function getAllGroups() {
352 return self::singleton()->getGroups();
353 }
354
364 public static function getPriority( $group ) {
365 if ( self::$prioritycache === null ) {
366 self::$prioritycache = [];
367 // Abusing this table originally intended for other purposes
368 $db = wfGetDB( DB_REPLICA );
369 $table = 'translate_groupreviews';
370 $fields = [ 'tgr_group', 'tgr_state' ];
371 $conds = [ 'tgr_lang' => '*priority' ];
372 $res = $db->select( $table, $fields, $conds, __METHOD__ );
373 foreach ( $res as $row ) {
374 self::$prioritycache[$row->tgr_group] = $row->tgr_state;
375 }
376 }
377
378 if ( $group instanceof MessageGroup ) {
379 $id = $group->getId();
380 } else {
381 $id = self::normalizeId( $group );
382 }
383
384 return self::$prioritycache[$id] ?? '';
385 }
386
394 public static function setPriority( $group, $priority = '' ) {
395 if ( $group instanceof MessageGroup ) {
396 $id = $group->getId();
397 } else {
398 $id = self::normalizeId( $group );
399 }
400
401 // FIXME: This assumes prioritycache has been populated
402 self::$prioritycache[$id] = $priority;
403
404 $dbw = wfGetDB( DB_PRIMARY );
405 $table = 'translate_groupreviews';
406 $row = [
407 'tgr_group' => $id,
408 'tgr_lang' => '*priority',
409 'tgr_state' => $priority,
410 ];
411
412 if ( $priority === '' ) {
413 unset( $row['tgr_state'] );
414 $dbw->delete( $table, $row, __METHOD__ );
415 } else {
416 $index = [ 'tgr_group', 'tgr_lang' ];
417 $dbw->replace( $table, [ $index ], $row, __METHOD__ );
418 }
419 }
420
426 public static function isDynamic( MessageGroup $group ) {
427 $id = $group->getId();
428
429 return ( $id[0] ?? null ) === '!';
430 }
431
439 public static function getSharedGroups( MessageGroup $group ) {
440 // Take the first message, get a handle for it and check
441 // if that message belongs to other groups. Those are the
442 // parent aggregate groups. Ideally we loop over all keys,
443 // but this should be enough.
444 $keys = array_keys( $group->getDefinitions() );
445 $title = Title::makeTitle( $group->getNamespace(), $keys[0] );
446 $handle = new MessageHandle( $title );
447 $ids = $handle->getGroupIds();
448 foreach ( $ids as $index => $id ) {
449 if ( $id === $group->getId() ) {
450 unset( $ids[$index] );
451 break;
452 }
453 }
454
455 return $ids;
456 }
457
465 public static function getParentGroups( MessageGroup $targetGroup ) {
466 $ids = self::getSharedGroups( $targetGroup );
467 if ( $ids === [] ) {
468 return [];
469 }
470
471 $targetId = $targetGroup->getId();
472
473 /* Get the group structure. We will be using this to find which
474 * of our candidates are top-level groups. Prefilter it to only
475 * contain aggregate groups. */
476 $structure = self::getGroupStructure();
477 foreach ( $structure as $index => $group ) {
478 if ( $group instanceof MessageGroup ) {
479 unset( $structure[$index] );
480 } else {
481 $structure[$index] = array_shift( $group );
482 }
483 }
484
485 /* Now that we have all related groups, use them to find all paths
486 * from top-level groups to target group with any number of subgroups
487 * in between. */
488 $paths = [];
489
490 /* This function recursively finds paths to the target group */
491 $pathFinder = static function ( &$paths, $group, $targetId, $prefix = '' )
492 use ( &$pathFinder ) {
493 if ( $group instanceof AggregateMessageGroup ) {
494 foreach ( $group->getGroups() as $subgroup ) {
495 $subId = $subgroup->getId();
496 if ( $subId === $targetId ) {
497 $paths[] = $prefix;
498 continue;
499 }
500
501 $pathFinder( $paths, $subgroup, $targetId, "$prefix|$subId" );
502 }
503 }
504 };
505
506 // Iterate over the top-level groups only
507 foreach ( $ids as $id ) {
508 // First, find a top level groups
509 $group = self::getGroup( $id );
510
511 // Quick escape for leaf groups
512 if ( !$group instanceof AggregateMessageGroup ) {
513 continue;
514 }
515
516 foreach ( $structure as $rootGroup ) {
518 if ( $rootGroup->getId() === $group->getId() ) {
519 // Yay we found a top-level group
520 $pathFinder( $paths, $rootGroup, $targetId, $id );
521 break; // No we have one or more paths appended into $paths
522 }
523 }
524 }
525
526 // And finally explode the strings
527 return array_map( static function ( string $pathString ): array {
528 return explode( '|', $pathString );
529 }, $paths );
530 }
531
533 public static function singleton() {
534 static $instance;
535 if ( !$instance instanceof self ) {
536 $instance = new self();
537 }
538
539 return $instance;
540 }
541
547 public function getGroups() {
548 $this->init();
549
550 return $this->groups;
551 }
552
561 public static function getGroupsById( array $ids, $skipMeta = false ) {
562 $groups = [];
563 foreach ( $ids as $id ) {
564 $group = self::getGroup( $id );
565
566 if ( $group !== null ) {
567 if ( $skipMeta && $group->isMeta() ) {
568 continue;
569 } else {
570 $groups[$id] = $group;
571 }
572 } else {
573 wfDebug( __METHOD__ . ": Invalid message group id: $id\n" );
574 }
575 }
576
577 return $groups;
578 }
579
588 public static function expandWildcards( $ids ) {
589 $all = [];
590
591 $ids = (array)$ids;
592 foreach ( $ids as $index => $id ) {
593 // Fast path, no wildcards
594 if ( strcspn( $id, '*?' ) === strlen( $id ) ) {
595 $g = self::getGroup( $id );
596 if ( $g ) {
597 $all[] = $g->getId();
598 }
599 unset( $ids[$index] );
600 }
601 }
602
603 if ( $ids === [] ) {
604 return $all;
605 }
606
607 // Slow path for the ones with wildcards
608 $matcher = new StringMatcher( '', $ids );
609 foreach ( self::getAllGroups() as $id => $_ ) {
610 if ( $matcher->matches( $id ) ) {
611 $all[] = $id;
612 }
613 }
614
615 return $all;
616 }
617
623 public static function getDynamicGroups() {
624 return [
625 '!recent' => 'RecentMessageGroup',
626 '!additions' => 'RecentAdditionsMessageGroup',
627 '!sandbox' => 'SandboxMessageGroup',
628 ];
629 }
630
640 public static function getGroupsByType( $type ) {
641 $groups = self::getAllGroups();
642 foreach ( $groups as $id => $group ) {
643 if ( !$group instanceof $type ) {
644 unset( $groups[$id] );
645 }
646 }
647
648 // @phan-suppress-next-line PhanTypeMismatchReturn
649 return $groups;
650 }
651
662 public static function getGroupStructure() {
663 $groups = self::getAllGroups();
664
665 // Determine the top level groups of the tree
666 $tree = $groups;
668 foreach ( $groups as $id => $o ) {
669 if ( !$o->exists() ) {
670 unset( $groups[$id], $tree[$id] );
671 continue;
672 }
673
674 if ( $o instanceof AggregateMessageGroup ) {
675 foreach ( $o->getGroups() as $sid => $so ) {
676 unset( $tree[$sid] );
677 }
678 }
679 }
680
681 usort( $tree, [ __CLASS__, 'groupLabelSort' ] );
682
683 /* Now we have two things left in $tree array:
684 * - solitaries: top-level non-aggregate message groups
685 * - top-level aggregate message groups */
686 foreach ( $tree as $index => $group ) {
687 if ( $group instanceof AggregateMessageGroup ) {
688 $tree[$index] = self::subGroups( $group );
689 }
690 }
691
692 /* Essentially we are done now. Cyclic groups can cause part of the
693 * groups not be included at all, because they have all unset each
694 * other in the first loop. So now we check if there are groups left
695 * over. */
696 $used = [];
697 array_walk_recursive(
698 $tree,
699 static function ( MessageGroup $group ) use ( &$used ) {
700 $used[$group->getId()] = true;
701 }
702 );
703 $unused = array_diff_key( $groups, $used );
704 if ( $unused ) {
705 foreach ( $unused as $index => $group ) {
706 if ( !$group instanceof AggregateMessageGroup ) {
707 unset( $unused[$index] );
708 }
709 }
710
711 // Only list the aggregate groups, other groups cannot cause cycles
712 $participants = implode( ', ', array_keys( $unused ) );
713 throw new MWException( "Found cyclic aggregate message groups: $participants" );
714 }
715
716 return $tree;
717 }
718
725 public static function groupLabelSort( $a, $b ) {
726 $al = $a->getLabel();
727 $bl = $b->getLabel();
728
729 return strcasecmp( $al, $bl );
730 }
731
743 public static function subGroups(
744 AggregateMessageGroup $parent,
745 array &$childIds = [],
746 $fname = 'caller'
747) {
748 static $recursionGuard = [];
749
750 $pid = $parent->getId();
751 if ( isset( $recursionGuard[$pid] ) ) {
752 $tid = $pid;
753 $path = [ $tid ];
754 do {
755 $tid = $recursionGuard[$tid];
756 $path[] = $tid;
757 // Until we have gone full cycle
758 } while ( $tid !== $pid );
759 $path = implode( ' > ', $path );
760 throw new MWException( "Found cyclic aggregate message groups: $path" );
761 }
762
763 // We don't care about the ids.
764 $tree = array_values( $parent->getGroups() );
765 usort( $tree, [ __CLASS__, 'groupLabelSort' ] );
766 // Expand aggregate groups (if any left) after sorting to form a tree
767 foreach ( $tree as $index => $group ) {
768 if ( $group instanceof AggregateMessageGroup ) {
769 $sid = $group->getId();
770 $recursionGuard[$pid] = $sid;
771 $tree[$index] = self::subGroups( $group, $childIds, __METHOD__ );
772 unset( $recursionGuard[$pid] );
773
774 $childIds[$sid] = 1;
775 }
776 }
777
778 // Parent group must be first item in the array
779 array_unshift( $tree, $parent );
780
781 if ( $fname !== __METHOD__ ) {
782 // Move the IDs from the keys to the value for final return
783 $childIds = array_values( $childIds );
784 }
785
786 return $tree;
787 }
788
795 public static function haveSingleSourceLanguage( array $groups ) {
796 $seen = '';
797
798 foreach ( $groups as $group ) {
799 $language = $group->getSourceLanguage();
800 if ( $seen === '' ) {
801 $seen = $language;
802 } elseif ( $language !== $seen ) {
803 return '';
804 }
805 }
806
807 return $seen;
808 }
809
819 public static function isTranslatableMessage( MessageHandle $handle, string $targetLanguage ): bool {
820 static $cache = [];
821
822 if ( !$handle->isValid() ) {
823 return false;
824 }
825
826 $group = $handle->getGroup();
827 $groupId = $group->getId();
828 $cacheKey = "$groupId:$targetLanguage";
829
830 if ( !isset( $cache[$cacheKey] ) ) {
831 $supportedLanguages = TranslateUtils::getLanguageNames( 'en' );
832 $inclusionList = $group->getTranslatableLanguages() ?? $supportedLanguages;
833
834 $included = isset( $inclusionList[$targetLanguage] );
835 $excluded = TranslateMetadata::isExcluded( $groupId, $targetLanguage );
836
837 $cache[$cacheKey] = [
838 'relevant' => $included && !$excluded,
839 'tags' => [],
840 ];
841
842 $groupTags = $group->getTags();
843 foreach ( [ 'ignored', 'optional' ] as $tag ) {
844 if ( isset( $groupTags[$tag] ) ) {
845 foreach ( $groupTags[$tag] as $key ) {
846 // TODO: ucfirst should not be here
847 $cache[$cacheKey]['tags'][ucfirst( $key )] = true;
848 }
849 }
850 }
851 }
852
853 return $cache[$cacheKey]['relevant'] &&
854 !isset( $cache[$cacheKey]['tags'][ucfirst( $handle->getKey() )] );
855 }
856}
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'), MessageIndex::singleton());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, '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: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:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, '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: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(), new RevTagStore(), $services->getDBLoadBalancer());}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(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());}, '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
static getInstance(IDatabase $db=null, WANObjectCache $cache=null)
Return an instance of this class using the parameters, if passed, else initialize the necessary depen...
Groups multiple message groups together as one group.
getGroups()
Returns a list of message groups that this group consists of.
The versatile default implementation of StringMangler interface.
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.
Factory class for accessing message groups individually by id or all of them as an list.
static appendAutoloader(array &$additions, array &$to)
Safely merges first array to second array, throwing warning on duplicates and removing duplicates fro...
static getGroup( $id)
Fetch a message group by id.
static getParentGroups(MessageGroup $targetGroup)
Returns a list of parent message groups.
static subGroups(AggregateMessageGroup $parent, array &$childIds=[], $fname='caller')
Like getGroupStructure but start from one root which must be an AggregateMessageGroup.
static isDynamic(MessageGroup $group)
getCacheGroupLoaders()
Returns group loaders that implement the CachedMessageGroupLoader.
static normalizeId( $id)
Fixes the id and resolves aliases.
clearProcessCache()
Manually reset the process cache.
static getDynamicGroups()
Contents on these groups changes on a whim.
getGroups()
Get all enabled non-dynamic message groups.
static expandWildcards( $ids)
If the list of message group ids contains wildcards, this function will match them against the list o...
static exists( $id)
static getGroupStructure()
Returns a tree of message groups.
static getPriority( $group)
We want to de-emphasize time sensitive groups like news for 2009.
static labelExists( $name)
Check if a particular aggregate group label exists.
static isTranslatableMessage(MessageHandle $handle, string $targetLanguage)
Filters out messages that should not be translated under normal conditions.
static getGroupsByType( $type)
Get only groups of specific type (class).
initGroupsFromDefinitions( $groups)
Expand process cached groups to objects.
static getGroupsById(array $ids, $skipMeta=false)
Get message groups for corresponding message group ids.
init()
Initialises the list of groups.
setCache(WANObjectCache $cache=null)
Override cache, for example during tests.
static clearCache()
Manually reset group cache.
static haveSingleSourceLanguage(array $groups)
Checks whether all the message groups have the same source language.
getCacheKey()
Returns the cache key.
recache()
Immediately update the cache.
static getSharedGroups(MessageGroup $group)
Returns a list of message groups that share (certain) messages with this group.
getGroupLoaders()
Loads and returns group loaders.
static setPriority( $group, $priority='')
Sets the message group priority.
static groupLabelSort( $a, $b)
Sorts groups by label value.
static getAllGroups()
Get all enabled message groups.
getCachedGroupDefinitions( $recache=false)
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.
getNamespace()
Returns the namespace where messages are placed.
getId()
Returns the unique identifier for this group.
getDefinitions()
Shortcut for load( getSourceLanguage() ).