13use MediaWiki\MediaWikiServices;
22 private static $prioritycache;
26 private $groupLoaders;
36 private const CACHE_VERSION = 4;
41 protected function init() {
42 if ( is_array( $this->groups ) ) {
47 $groups = $value[
'cc'];
50 $groups += $loader->getGroups();
60 global $wgAutoloadClasses;
62 $regenerator =
function () {
63 global $wgAutoloadClasses;
65 $groups = $deps = $autoload = [];
70 Hooks::run(
'TranslatePostInitGroups', [ &$groups, &$deps, &$autoload ] );
72 self::appendAutoloader( $autoload, $wgAutoloadClasses );
75 'ts' => wfTimestamp( TS_MW ),
77 'autoload' => $autoload
79 $wrapper =
new DependencyWrapper( $value, $deps );
80 $wrapper->initialiseDeps();
85 $cache = $this->getCache();
87 $wrapper = $cache->getWithSetCallback(
94 'touchedCallback' =>
static function ( $value ) {
95 return ( $value instanceof DependencyWrapper && $value->isExpired() )
99 'minAsOf' => $recache ? INF : $cache::MIN_TIMESTAMP_NONE,
103 $value = $wrapper->getValue();
104 self::appendAutoloader( $value[
'autoload'], $wgAutoloadClasses );
115 foreach ( $groups as $id => $mixed ) {
116 if ( !is_object( $mixed ) ) {
117 $groups[$id] = call_user_func( $mixed, $id );
121 $this->groups = $groups;
131 $cache = $this->getCache();
137 $cacheLoader->recache();
142 $groups = $value[
'cc'];
145 $groups += $loader->getGroups();
157 $self = self::singleton();
159 $cache = $self->getCache();
160 $cache->delete( $self->getCacheKey(), 1 );
162 foreach ( $self->getCacheGroupLoaders() as $cacheLoader ) {
163 $cacheLoader->clearCache();
166 $self->clearProcessCache();
177 $this->groups =
null;
178 $this->groupLoaders =
null;
180 self::$prioritycache =
null;
183 protected function getCache(): WANObjectCache {
184 if ( $this->cache === null ) {
185 return MediaWikiServices::getInstance()->getMainWANObjectCache();
196 public function setCache( WANObjectCache $cache =
null ) {
197 $this->cache = $cache;
206 return $this->getCache()->makeKey(
'translate-groups',
'v' . self::CACHE_VERSION );
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 );
233 if ( $this->groupLoaders !==
null ) {
234 return $this->groupLoaders;
237 $cache = $this->getCache();
239 $groupLoaderInstances = $this->groupLoaders = [];
243 'database' => TranslateUtils::getSafeReadDB(),
247 Hooks::run(
'TranslateInitGroupLoaders', [ &$groupLoaderInstances, $deps ] );
249 if ( $groupLoaderInstances === [] ) {
250 return $this->groupLoaders;
254 foreach ( $groupLoaderInstances as $loader ) {
256 throw new InvalidArgumentException(
257 "MessageGroupLoader - $loader must implement the " .
258 "MessageGroupLoader interface."
262 $this->groupLoaders[] = $loader;
265 return $this->groupLoaders;
275 return array_filter( $this->getGroupLoaders(),
static function ( $groupLoader ) {
287 $groups = self::singleton()->getGroups();
288 $id = self::normalizeId( $id );
290 if ( isset( $groups[$id] ) ) {
294 if ( (
string)$id !==
'' && $id[0] ===
'!' ) {
295 $dynamic = self::getDynamicGroups();
296 if ( isset( $dynamic[$id] ) ) {
297 return new $dynamic[$id];
314 if ( strpos( $id,
'page-' ) === 0 ) {
315 $id = strtr( $id,
'_',
' ' );
318 global $wgTranslateGroupAliases;
319 if ( isset( $wgTranslateGroupAliases[$id] ) ) {
320 $id = $wgTranslateGroupAliases[$id];
331 return (
bool)self::getGroup( $id );
343 }, $loader->loadAggregateGroups() );
344 return in_array( $name, $labels,
true );
352 return self::singleton()->getGroups();
365 if ( self::$prioritycache ===
null ) {
366 self::$prioritycache = [];
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;
379 $id = $group->getId();
381 $id = self::normalizeId( $group );
384 return self::$prioritycache[$id] ??
'';
396 $id = $group->getId();
398 $id = self::normalizeId( $group );
402 self::$prioritycache[$id] = $priority;
404 $dbw = wfGetDB( DB_PRIMARY );
405 $table =
'translate_groupreviews';
408 'tgr_lang' =>
'*priority',
409 'tgr_state' => $priority,
412 if ( $priority ===
'' ) {
413 unset( $row[
'tgr_state'] );
414 $dbw->delete( $table, $row, __METHOD__ );
416 $index = [
'tgr_group',
'tgr_lang' ];
417 $dbw->replace( $table, [ $index ], $row, __METHOD__ );
427 $id = $group->
getId();
429 return ( $id[0] ??
null ) ===
'!';
445 $title = Title::makeTitle( $group->
getNamespace(), $keys[0] );
447 $ids = $handle->getGroupIds();
448 foreach ( $ids as $index => $id ) {
449 if ( $id === $group->
getId() ) {
450 unset( $ids[$index] );
466 $ids = self::getSharedGroups( $targetGroup );
471 $targetId = $targetGroup->
getId();
476 $structure = self::getGroupStructure();
477 foreach ( $structure as $index => $group ) {
479 unset( $structure[$index] );
481 $structure[$index] = array_shift( $group );
491 $pathFinder =
static function ( &$paths, $group, $targetId, $prefix =
'' )
492 use ( &$pathFinder ) {
494 foreach ( $group->getGroups() as $subgroup ) {
495 $subId = $subgroup->getId();
496 if ( $subId === $targetId ) {
501 $pathFinder( $paths, $subgroup, $targetId,
"$prefix|$subId" );
507 foreach ( $ids as $id ) {
509 $group = self::getGroup( $id );
516 foreach ( $structure as $rootGroup ) {
518 if ( $rootGroup->getId() === $group->getId() ) {
520 $pathFinder( $paths, $rootGroup, $targetId, $id );
527 return array_map(
static function (
string $pathString ): array {
528 return explode(
'|', $pathString );
535 if ( !$instance instanceof
self ) {
536 $instance =
new self();
550 return $this->groups;
563 foreach ( $ids as $id ) {
564 $group = self::getGroup( $id );
566 if ( $group !==
null ) {
567 if ( $skipMeta && $group->isMeta() ) {
570 $groups[$id] = $group;
573 wfDebug( __METHOD__ .
": Invalid message group id: $id\n" );
592 foreach ( $ids as $index => $id ) {
594 if ( strcspn( $id,
'*?' ) === strlen( $id ) ) {
595 $g = self::getGroup( $id );
597 $all[] = $g->getId();
599 unset( $ids[$index] );
609 foreach ( self::getAllGroups() as $id => $_ ) {
610 if ( $matcher->matches( $id ) ) {
625 '!recent' =>
'RecentMessageGroup',
626 '!additions' =>
'RecentAdditionsMessageGroup',
627 '!sandbox' =>
'SandboxMessageGroup',
641 $groups = self::getAllGroups();
642 foreach ( $groups as $id => $group ) {
643 if ( !$group instanceof $type ) {
644 unset( $groups[$id] );
663 $groups = self::getAllGroups();
668 foreach ( $groups as $id => $o ) {
669 if ( !$o->exists() ) {
670 unset( $groups[$id], $tree[$id] );
675 foreach ( $o->getGroups() as $sid => $so ) {
676 unset( $tree[$sid] );
681 usort( $tree, [ __CLASS__,
'groupLabelSort' ] );
686 foreach ( $tree as $index => $group ) {
688 $tree[$index] = self::subGroups( $group );
697 array_walk_recursive(
699 static function (
MessageGroup $group ) use ( &$used ) {
700 $used[$group->
getId()] =
true;
703 $unused = array_diff_key( $groups, $used );
705 foreach ( $unused as $index => $group ) {
707 unset( $unused[$index] );
712 $participants = implode(
', ', array_keys( $unused ) );
713 throw new MWException(
"Found cyclic aggregate message groups: $participants" );
726 $al = $a->getLabel();
727 $bl = $b->getLabel();
729 return strcasecmp( $al, $bl );
745 array &$childIds = [],
748 static $recursionGuard = [];
750 $pid = $parent->
getId();
751 if ( isset( $recursionGuard[$pid] ) ) {
755 $tid = $recursionGuard[$tid];
758 }
while ( $tid !== $pid );
759 $path = implode(
' > ', $path );
760 throw new MWException(
"Found cyclic aggregate message groups: $path" );
764 $tree = array_values( $parent->
getGroups() );
765 usort( $tree, [ __CLASS__,
'groupLabelSort' ] );
767 foreach ( $tree as $index => $group ) {
769 $sid = $group->getId();
770 $recursionGuard[$pid] = $sid;
771 $tree[$index] = self::subGroups( $group, $childIds, __METHOD__ );
772 unset( $recursionGuard[$pid] );
779 array_unshift( $tree, $parent );
781 if ( $fname !== __METHOD__ ) {
783 $childIds = array_values( $childIds );
798 foreach ( $groups as $group ) {
799 $language = $group->getSourceLanguage();
800 if ( $seen ===
'' ) {
802 } elseif ( $language !== $seen ) {
827 $groupId = $group->getId();
828 $cacheKey =
"$groupId:$targetLanguage";
830 if ( !isset( $cache[$cacheKey] ) ) {
831 $supportedLanguages = TranslateUtils::getLanguageNames(
'en' );
832 $inclusionList = $group->getTranslatableLanguages() ?? $supportedLanguages;
834 $included = isset( $inclusionList[$targetLanguage] );
835 $excluded = TranslateMetadata::isExcluded( $groupId, $targetLanguage );
837 $cache[$cacheKey] = [
838 'relevant' => $included && !$excluded,
842 $groupTags = $group->getTags();
843 foreach ( [
'ignored',
'optional' ] as $tag ) {
844 if ( isset( $groupTags[$tag] ) ) {
845 foreach ( $groupTags[$tag] as $key ) {
847 $cache[$cacheKey][
'tags'][ucfirst( $key )] =
true;
853 return $cache[$cacheKey][
'relevant'] &&
854 !isset( $cache[$cacheKey][
'tags'][ucfirst( $handle->
getKey() )] );
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.
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 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() ).