diff options
Diffstat (limited to 'MLEB/Translate/MessageGroups.php')
-rw-r--r-- | MLEB/Translate/MessageGroups.php | 455 |
1 files changed, 304 insertions, 151 deletions
diff --git a/MLEB/Translate/MessageGroups.php b/MLEB/Translate/MessageGroups.php index 279439b5..92ba97fa 100644 --- a/MLEB/Translate/MessageGroups.php +++ b/MLEB/Translate/MessageGroups.php @@ -6,7 +6,7 @@ * @author Niklas Laxström * @author Siebrand Mazeland * @copyright Copyright © 2008-2013, Niklas Laxström, Siebrand Mazeland - * @license GPL-2.0+ + * @license GPL-2.0-or-later */ /** @@ -16,36 +16,67 @@ */ class MessageGroups { /** - * @var Array Cache for message group priorities + * @var string[]|null Cache for message group priorities */ - protected static $prioritycache = null; + protected static $prioritycache; - protected static $groups = null; - - /// Initialises the list of groups (but not the groups itself if possible). - public static function init() { - if ( is_array( self::$groups ) ) { - return; - } + /** + * @var array|null + */ + protected $groups; - self::$groups = array(); + /** + * @var BagOStuff|null + */ + protected $cache; + /** + * Initialises the list of groups + */ + protected function init() { global $wgAutoloadClasses; + if ( is_array( $this->groups ) ) { + return; + } + $key = wfMemcKey( 'translate-groups' ); - $value = DependencyWrapper::getValueFromCache( self::getCache(), $key ); + $value = DependencyWrapper::getValueFromCache( $this->getCache(), $key ); if ( $value === null ) { wfDebug( __METHOD__ . "-nocache\n" ); - self::loadGroupDefinitions(); + $groups = $this->loadGroupDefinitions(); } else { wfDebug( __METHOD__ . "-withcache\n" ); - self::$groups = $value['cc']; + $groups = $value['cc']; + self::appendAutoloader( $value['autoload'], $wgAutoloadClasses ); + } + + $this->postInit( $groups ); + } - foreach ( $value['autoload'] as $class => $file ) { - $wgAutoloadClasses[$class] = $file; + /** + * @param array $groups + */ + protected function postInit( $groups ) { + // Expand groups to objects + foreach ( $groups as $id => $mixed ) { + if ( !is_object( $mixed ) ) { + $groups[$id] = call_user_func( $mixed, $id ); } } + + $this->groups = $groups; + } + + /** + * Immediately update the cache. + * + * @since 2015.04 + */ + public function recache() { + $groups = $this->loadGroupDefinitions(); + $this->postInit( $groups ); } /** @@ -54,68 +85,133 @@ class MessageGroups { * Use when automatic dependency tracking fails. */ public static function clearCache() { - $key = wfMemckey( 'translate-groups' ); - self::getCache()->delete( $key ); - self::$groups = null; + $self = self::singleton(); + $self->getCache()->delete( wfMemcKey( 'translate-groups' ) ); + $self->clearProcessCache(); + } + + /** + * Manually reset the process cache. + * + * This is helpful for long running scripts where the process cache might get stale + * even though the global cache is updated. + * @since 2016.08 + */ + public function clearProcessCache() { + $this->groups = null; } /** * Returns a cacher object. + * * @return BagOStuff */ - protected static function getCache() { - return wfGetCache( CACHE_ANYTHING ); + protected function getCache() { + if ( $this->cache === null ) { + return wfGetCache( CACHE_ANYTHING ); + } else { + return $this->cache; + } + } + + /** + * Override cache, for example during tests. + * + * @param BagOStuff|null $cache + */ + public function setCache( BagOStuff $cache = null ) { + $this->cache = $cache; + } + + /** + * Safely merges first array to second array, throwing warning on duplicates and removing + * duplicates from the first array. + * @param array &$additions Things to append + * @param array &$to Where to append + */ + protected static function appendAutoloader( array &$additions, array &$to ) { + foreach ( $additions as $class => $file ) { + if ( isset( $to[$class] ) && $to[$class] !== $file ) { + $msg = "Autoload conflict for $class: {$to[$class]} !== $file"; + trigger_error( $msg, E_USER_WARNING ); + continue; + } + + $to[$class] = $file; + } } /** * This constructs the list of all groups from multiple different * sources. When possible, a cache dependency is created to automatically * recreate the cache when configuration changes. - * @todo Reduce the ways of which messages can be added. Target is just - * to have three ways: Yaml files, translatable pages and with the hook. + * @return array */ - protected static function loadGroupDefinitions() { + protected function loadGroupDefinitions() { + global $wgAutoloadClasses; - global $wgEnablePageTranslation, $wgTranslateGroupFiles; - global $wgTranslateCC, $wgAutoloadClasses, $wgTranslateWorkflowStates; + $groups = $deps = $autoload = []; - $deps = array(); - $deps[] = new GlobalDependency( 'wgEnablePageTranslation' ); - $deps[] = new GlobalDependency( 'wgTranslateGroupFiles' ); - $deps[] = new GlobalDependency( 'wgTranslateCC' ); - $deps[] = new GlobalDependency( 'wgTranslateWorkflowStates' ); + Hooks::run( 'TranslatePostInitGroups', [ &$groups, &$deps, &$autoload ] ); + + // Register autoloaders for this request, both values modified by reference + self::appendAutoloader( $autoload, $wgAutoloadClasses ); + + $key = wfMemcKey( 'translate-groups' ); + $value = [ + 'ts' => wfTimestamp( TS_MW ), + 'cc' => $groups, + 'autoload' => $autoload, + ]; - self::$groups = $wgTranslateCC; + $wrapper = new DependencyWrapper( $value, $deps ); + $wrapper->storeToCache( $this->getCache(), $key, 60 * 60 * 2 ); - if ( $wgEnablePageTranslation ) { - wfProfileIn( __METHOD__ . '-pt' ); - $dbr = wfGetDB( DB_MASTER ); + return $groups; + } - $tables = array( 'page', 'revtag' ); - $vars = array( 'page_id', 'page_namespace', 'page_title' ); - $conds = array( 'page_id=rt_page', 'rt_type' => RevTag::getType( 'tp:mark' ) ); - $options = array( 'GROUP BY' => 'rt_page' ); - $res = $dbr->select( $tables, $vars, $conds, __METHOD__, $options ); + /** + * Hook: TranslatePostInitGroups + * @param array &$groups + * @param array &$deps + * @param array &$autoload + */ + public static function getTranslatablePages( array &$groups, array &$deps, array &$autoload ) { + global $wgEnablePageTranslation; - foreach ( $res as $r ) { - $title = Title::newFromRow( $r ); - $id = TranslatablePage::getMessageGroupIdFromTitle( $title ); - self::$groups[$id] = new WikiPageMessageGroup( $id, $title ); - self::$groups[$id]->setLabel( $title->getPrefixedText() ); - } - wfProfileOut( __METHOD__ . '-pt' ); + $deps[] = new GlobalDependency( 'wgEnablePageTranslation' ); + + if ( !$wgEnablePageTranslation ) { + return; } - if ( $wgTranslateWorkflowStates ) { - self::$groups['translate-workflow-states'] = new WorkflowStatesMessageGroup(); + $db = TranslateUtils::getSafeReadDB(); + + $tables = [ 'page', 'revtag' ]; + $vars = [ 'page_id', 'page_namespace', 'page_title' ]; + $conds = [ 'page_id=rt_page', 'rt_type' => RevTag::getType( 'tp:mark' ) ]; + $options = [ 'GROUP BY' => 'rt_page' ]; + $res = $db->select( $tables, $vars, $conds, __METHOD__, $options ); + + foreach ( $res as $r ) { + $title = Title::newFromRow( $r ); + $id = TranslatablePage::getMessageGroupIdFromTitle( $title ); + $groups[$id] = new WikiPageMessageGroup( $id, $title ); + $groups[$id]->setLabel( $title->getPrefixedText() ); } + } - wfProfileIn( __METHOD__ . '-hook' ); - $autoload = array(); - wfRunHooks( 'TranslatePostInitGroups', array( &self::$groups, &$deps, &$autoload ) ); - wfProfileOut( __METHOD__ . '-hook' ); + /** + * Hook: TranslatePostInitGroups + * @param array &$groups + * @param array &$deps + * @param array &$autoload + */ + public static function getConfiguredGroups( array &$groups, array &$deps, array &$autoload ) { + global $wgTranslateGroupFiles; + + $deps[] = new GlobalDependency( 'wgTranslateGroupFiles' ); - wfProfileIn( __METHOD__ . '-yaml' ); $parser = new MessageGroupConfigurationParser(); foreach ( $wgTranslateGroupFiles as $configFile ) { $deps[] = new FileDependency( realpath( $configFile ) ); @@ -131,61 +227,72 @@ class MessageGroups { foreach ( $fgroups as $id => $conf ) { if ( !empty( $conf['AUTOLOAD'] ) && is_array( $conf['AUTOLOAD'] ) ) { $dir = dirname( $configFile ); - foreach ( $conf['AUTOLOAD'] as $class => $file ) { - // For this request and for caching. - $wgAutoloadClasses[$class] = "$dir/$file"; - $autoload[$class] = "$dir/$file"; - } + $additions = array_map( function ( $file ) use ( $dir ) { + return "$dir/$file"; + }, $conf['AUTOLOAD'] ); + self::appendAutoloader( $additions, $autoload ); } - $group = MessageGroupBase::factory( $conf ); - self::$groups[$id] = $group; + + $groups[$id] = MessageGroupBase::factory( $conf ); } } - wfProfileOut( __METHOD__ . '-yaml' ); + } + + /** + * Hook: TranslatePostInitGroups + * @param array &$groups + * @param array &$deps + * @param array &$autoload + */ + public static function getWorkflowGroups( array &$groups, array &$deps, array &$autoload ) { + global $wgTranslateWorkflowStates; + + $deps[] = new GlobalDependency( 'wgTranslateWorkflowStates' ); - wfProfileIn( __METHOD__ . '-agg' ); - $aggregateGroups = self::getAggregateGroups(); - foreach ( $aggregateGroups as $id => $group ) { - self::$groups[$id] = $group; + if ( $wgTranslateWorkflowStates ) { + $groups['translate-workflow-states'] = new WorkflowStatesMessageGroup(); } - wfProfileOut( __METHOD__ . '-agg' ); + } - $key = wfMemckey( 'translate-groups' ); - $value = array( - 'cc' => self::$groups, - 'autoload' => $autoload, - ); + /** + * Hook: TranslatePostInitGroups + * @param array &$groups + * @param array &$deps + * @param array &$autoload + */ + public static function getAggregateGroups( array &$groups, array &$deps, array &$autoload ) { + $groups += self::loadAggregateGroups(); + } - wfProfileIn( __METHOD__ . '-save' ); - $wrapper = new DependencyWrapper( $value, $deps ); - $wrapper->storeToCache( self::getCache(), $key, 60 * 60 * 2 ); - wfProfileOut( __METHOD__ . '-save' ); + /** + * Hook: TranslatePostInitGroups + * @param array &$groups + * @param array &$deps + * @param array &$autoload + */ + public static function getCCGroups( array &$groups, array &$deps, array &$autoload ) { + global $wgTranslateCC; + + $deps[] = new GlobalDependency( 'wgTranslateCC' ); - wfDebug( __METHOD__ . "-end\n" ); + $groups += $wgTranslateCC; } /** * Fetch a message group by id. + * * @param string $id Message group id. * @return MessageGroup|null if it doesn't exist. */ public static function getGroup( $id ) { - // BC with page| which is now page- - $id = strtr( $id, '|', '-' ); - /* Translatable pages use spaces, but MW occasionally likes to - * normalize spaces to underscores */ - if ( strpos( $id, 'page-' ) === 0 ) { - $id = strtr( $id, '_', ' ' ); - } - self::init(); + $groups = self::singleton()->getGroups(); + $id = self::normalizeId( $id ); - if ( isset( self::$groups[$id] ) ) { - if ( is_callable( self::$groups[$id] ) ) { - return call_user_func( self::$groups[$id], $id ); - } + if ( isset( $groups[$id] ) ) { + return $groups[$id]; + } - return self::$groups[$id]; - } elseif ( strval( $id ) !== '' && $id[0] === '!' ) { + if ( (string)$id !== '' && $id[0] === '!' ) { $dynamic = self::getDynamicGroups(); if ( isset( $dynamic[$id] ) ) { return new $dynamic[$id]; @@ -196,6 +303,28 @@ class MessageGroups { } /** + * Fixes the id and resolves aliases. + * + * @param string $id + * @return string + * @since 2016.01 + */ + public static function normalizeId( $id ) { + /* Translatable pages use spaces, but MW occasionally likes to + * normalize spaces to underscores */ + if ( strpos( $id, 'page-' ) === 0 ) { + $id = strtr( $id, '_', ' ' ); + } + + global $wgTranslateGroupAliases; + if ( isset( $wgTranslateGroupAliases[$id] ) ) { + $id = $wgTranslateGroupAliases[$id]; + } + + return $id; + } + + /** * @param string $id * @return bool */ @@ -203,14 +332,13 @@ class MessageGroups { return (bool)self::getGroup( $id ); } - /** * Check if a particular aggregate group label exists * @param string $name * @return bool */ public static function labelExists( $name ) { - $groups = MessageGroups::getAggregateGroups(); + $groups = self::loadAggregateGroups(); $labels = array_map( function ( $g ) { return $g->getLabel(); }, $groups ); @@ -236,12 +364,12 @@ class MessageGroups { */ public static function getPriority( $group ) { if ( !isset( self::$prioritycache ) ) { - self::$prioritycache = array(); + self::$prioritycache = []; // Abusing this table originally intented for other purposes - $db = wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_REPLICA ); $table = 'translate_groupreviews'; - $fields = array( 'tgr_group', 'tgr_state' ); - $conds = array( 'tgr_lang' => '*priority' ); + $fields = [ 'tgr_group', 'tgr_state' ]; + $conds = [ 'tgr_lang' => '*priority' ]; $res = $db->select( $table, $fields, $conds, __METHOD__ ); foreach ( $res as $row ) { self::$prioritycache[$row->tgr_group] = $row->tgr_state; @@ -251,7 +379,7 @@ class MessageGroups { if ( $group instanceof MessageGroup ) { $id = $group->getId(); } else { - $id = $group; + $id = self::normalizeId( $group ); } return isset( self::$prioritycache[$id] ) ? self::$prioritycache[$id] : ''; @@ -269,33 +397,37 @@ class MessageGroups { if ( $group instanceof MessageGroup ) { $id = $group->getId(); } else { - $id = $group; + $id = self::normalizeId( $group ); } self::$prioritycache[$id] = $priority; $dbw = wfGetDB( DB_MASTER ); $table = 'translate_groupreviews'; - $row = array( + $row = [ 'tgr_group' => $id, 'tgr_lang' => '*priority', 'tgr_state' => $priority, - ); + ]; if ( $priority === '' ) { unset( $row['tgr_state'] ); $dbw->delete( $table, $row, __METHOD__ ); } else { - $index = array( 'tgr_group', 'tgr_lang' ); - $dbw->replace( $table, array( $index ), $row, __METHOD__ ); + $index = [ 'tgr_group', 'tgr_lang' ]; + $dbw->replace( $table, [ $index ], $row, __METHOD__ ); } } - /// @since 2011-12-28 + /** + * @since 2011-12-28 + * @param MessageGroup $group + * @return bool + */ public static function isDynamic( MessageGroup $group ) { $id = $group->getId(); - return strval( $id ) !== '' && $id[0] === '!'; + return (string)$id !== '' && $id[0] === '!'; } /** @@ -332,8 +464,8 @@ class MessageGroups { */ public static function getParentGroups( MessageGroup $targetGroup ) { $ids = self::getSharedGroups( $targetGroup ); - if ( $ids === array() ) { - return array(); + if ( $ids === [] ) { + return []; } $targetId = $targetGroup->getId(); @@ -353,7 +485,7 @@ class MessageGroups { /* Now that we have all related groups, use them to find all paths * from top-level groups to target group with any number of subgroups * in between. */ - $paths = array(); + $paths = []; /* This function recursively finds paths to the target group */ $pathFinder = function ( &$paths, $group, $targetId, $prefix = '' ) @@ -404,9 +536,6 @@ class MessageGroups { return $paths; } - private function __construct() { - } - /** * Constructor function. * @return MessageGroups @@ -422,18 +551,13 @@ class MessageGroups { /** * Get all enabled non-dynamic message groups. + * * @return array */ public function getGroups() { - self::init(); - // Expand groups to objects - foreach ( self::$groups as $id => $mixed ) { - if ( !is_object( $mixed ) ) { - self::$groups[$id] = call_user_func( $mixed, $id ); - } - } + $this->init(); - return self::$groups; + return $this->groups; } /** @@ -445,7 +569,7 @@ class MessageGroups { * @since 2012-02-13 */ public static function getGroupsById( array $ids, $skipMeta = false ) { - $groups = array(); + $groups = []; foreach ( $ids as $id ) { $group = self::getGroup( $id ); @@ -472,9 +596,26 @@ class MessageGroups { * @since 2012-02-13 */ public static function expandWildcards( $ids ) { - $all = array(); + $all = []; + + $ids = (array)$ids; + foreach ( $ids as $index => $id ) { + // Fast path, no wildcards + if ( strcspn( $id, '*?' ) === strlen( $id ) ) { + $g = self::getGroup( $id ); + if ( $g ) { + $all[] = $g->getId(); + } + unset( $ids[$index] ); + } + } - $matcher = new StringMatcher( '', (array)$ids ); + if ( $ids === [] ) { + return $all; + } + + // Slow path for the ones with wildcards + $matcher = new StringMatcher( '', $ids ); foreach ( self::getAllGroups() as $id => $_ ) { if ( $matcher->match( $id ) ) { $all[] = $id; @@ -487,13 +628,14 @@ class MessageGroups { /** * Contents on these groups changes on a whim. * @since 2011-12-28 + * @return array */ public static function getDynamicGroups() { - return array( + return [ '!recent' => 'RecentMessageGroup', '!additions' => 'RecentAdditionsMessageGroup', '!sandbox' => 'SandboxMessageGroup', - ); + ]; } /** @@ -519,6 +661,7 @@ class MessageGroups { * other code might not handle more than two (or even one) nesting levels. * One group can exist multiple times in differents parts of the tree. * In other words: [Group1, Group2, [AggGroup, Group3, Group4]] + * * @throws MWException If cyclic structure is detected. * @return array */ @@ -548,9 +691,9 @@ class MessageGroups { // Work around php bug: https://bugs.php.net/bug.php?id=50688 // Triggered by ApiQueryMessageGroups for example - wfSuppressWarnings(); - usort( $tree, array( __CLASS__, 'groupLabelSort' ) ); - wfRestoreWarnings(); + MediaWiki\suppressWarnings(); + usort( $tree, [ __CLASS__, 'groupLabelSort' ] ); + MediaWiki\restoreWarnings(); /* Now we have two things left in $tree array: * - solitaries: top-level non-aggregate message groups @@ -565,9 +708,9 @@ class MessageGroups { * groups not be included at all, because they have all unset each * other in the first loop. So now we check if there are groups left * over. */ - $used = array(); + $used = []; // Hack to allow passing by reference - array_walk_recursive( $tree, array( __CLASS__, 'collectGroupIds' ), array( &$used ) ); + array_walk_recursive( $tree, [ __CLASS__, 'collectGroupIds' ], [ &$used ] ); $unused = array_diff( array_keys( $groups ), array_keys( $used ) ); if ( count( $unused ) ) { foreach ( $unused as $index => $id ) { @@ -584,12 +727,22 @@ class MessageGroups { return $tree; } - /// See getGroupStructure, just collects ids into array - public static function collectGroupIds( $value, $key, $used ) { + /** + * See getGroupStructure, just collects ids into array + * @param MessageGroup $value + * @param string $key + * @param bool $used + */ + public static function collectGroupIds( MessageGroup $value, $key, $used ) { $used[0][$value->getId()] = true; } - /// Sorts groups by label value + /** + * Sorts groups by label value + * @param string $a + * @param string $b + * @return int + */ public static function groupLabelSort( $a, $b ) { $al = $a->getLabel(); $bl = $b->getLabel(); @@ -607,12 +760,12 @@ class MessageGroups { * @since Public since 2012-11-29 */ public static function subGroups( AggregateMessageGroup $parent ) { - static $recursionGuard = array(); + static $recursionGuard = []; $pid = $parent->getId(); if ( isset( $recursionGuard[$pid] ) ) { $tid = $pid; - $path = array( $tid ); + $path = [ $tid ]; do { $tid = $recursionGuard[$tid]; $path[] = $tid; @@ -624,7 +777,7 @@ class MessageGroups { // We don't care about the ids. $tree = array_values( $parent->getGroups() ); - usort( $tree, array( __CLASS__, 'groupLabelSort' ) ); + usort( $tree, [ __CLASS__, 'groupLabelSort' ] ); // Expand aggregate groups (if any left) after sorting to form a tree foreach ( $tree as $index => $group ) { if ( $group instanceof AggregateMessageGroup ) { @@ -664,29 +817,29 @@ class MessageGroups { /** * Get all the aggregate messages groups defined in translate_metadata table. + * * @return array - * @since 2012-05-09 return value changed */ - protected static function getAggregateGroups() { - $dbw = wfGetDB( DB_MASTER ); - $tables = array( 'translate_metadata' ); - $fields = array( 'tmd_group', 'tmd_value' ); - $conds = array( 'tmd_key' => 'subgroups' ); + protected static function loadAggregateGroups() { + $dbw = TranslateUtils::getSafeReadDB(); + $tables = [ 'translate_metadata' ]; + $fields = [ 'tmd_group', 'tmd_value' ]; + $conds = [ 'tmd_key' => 'subgroups' ]; $res = $dbw->select( $tables, $fields, $conds, __METHOD__ ); - $groups = array(); + $groups = []; foreach ( $res as $row ) { $id = $row->tmd_group; - $conf = array(); - $conf['BASIC'] = array( + $conf = []; + $conf['BASIC'] = [ 'id' => $id, 'label' => TranslateMetadata::get( $id, 'name' ), 'description' => TranslateMetadata::get( $id, 'description' ), 'meta' => 1, 'class' => 'AggregateMessageGroup', 'namespace' => NS_TRANSLATIONS, - ); + ]; $conf['GROUPS'] = TranslateMetadata::getSubgroups( $id ); $group = MessageGroupBase::factory( $conf ); @@ -701,11 +854,11 @@ class MessageGroups { * conditions. * * @param MessageHandle $handle Handle for the translation target. - * @return boolean + * @return bool * @since 2013.10 */ public static function isTranslatableMessage( MessageHandle $handle ) { - static $cache = array(); + static $cache = []; if ( !$handle->isValid() ) { return false; @@ -737,13 +890,13 @@ class MessageGroups { } } - $cache[$cacheKey] = array( + $cache[$cacheKey] = [ 'relevant' => $allowed && !$discouraged, - 'tags' => array(), - ); + 'tags' => [], + ]; $groupTags = $group->getTags(); - foreach ( array( 'ignored', 'optional' ) as $tag ) { + foreach ( [ 'ignored', 'optional' ] as $tag ) { if ( isset( $groupTags[$tag] ) ) { foreach ( $groupTags[$tag] as $key ) { // TODO: ucfirst should not be here |