00001 <?php
00017 class MessageGroups {
00021 protected static $prioritycache = null;
00022
00023 protected static $groups = null;
00024
00026 public static function init() {
00027 if ( is_array( self::$groups ) ) {
00028 return;
00029 }
00030
00031 wfProfileIn( __METHOD__ );
00032 self::$groups = array();
00033
00034 global $wgAutoloadClasses;
00035
00036 $key = wfMemcKey( 'translate-groups' );
00037 $value = DependencyWrapper::getValueFromCache( self::getCache(), $key );
00038
00039 if ( $value === null ) {
00040 wfDebug( __METHOD__ . "-nocache\n" );
00041 self::loadGroupDefinitions();
00042 } else {
00043 wfDebug( __METHOD__ . "-withcache\n" );
00044 self::$groups = $value['cc'];
00045
00046 foreach ( $value['autoload'] as $class => $file ) {
00047 $wgAutoloadClasses[$class] = $file;
00048 }
00049 }
00050 wfProfileOut( __METHOD__ );
00051 }
00052
00058 public static function clearCache() {
00059 $key = wfMemckey( 'translate-groups' );
00060 self::getCache()->delete( $key );
00061 self::$groups = null;
00062 }
00063
00068 protected static function getCache() {
00069 return wfGetCache( CACHE_ANYTHING );
00070 }
00071
00079 protected static function loadGroupDefinitions() {
00080 wfProfileIn( __METHOD__ );
00081
00082 global $wgEnablePageTranslation, $wgTranslateGroupFiles;
00083 global $wgTranslateCC, $wgAutoloadClasses, $wgTranslateWorkflowStates;
00084
00085 $deps = array();
00086 $deps[] = new GlobalDependency( 'wgEnablePageTranslation' );
00087 $deps[] = new GlobalDependency( 'wgTranslateGroupFiles' );
00088 $deps[] = new GlobalDependency( 'wgTranslateCC' );
00089 $deps[] = new GlobalDependency( 'wgTranslateExtensionDirectory' );
00090 $deps[] = new GlobalDependency( 'wgTranslateWorkflowStates' );
00091
00092 self::$groups = $wgTranslateCC;
00093
00094 if ( $wgEnablePageTranslation ) {
00095 wfProfileIn( __METHOD__ . '-pt' );
00096 $dbr = wfGetDB( DB_MASTER );
00097
00098 $tables = array( 'page', 'revtag' );
00099 $vars = array( 'page_id', 'page_namespace', 'page_title' );
00100 $conds = array( 'page_id=rt_page', 'rt_type' => RevTag::getType( 'tp:mark' ) );
00101 $options = array( 'GROUP BY' => 'rt_page' );
00102 $res = $dbr->select( $tables, $vars, $conds, __METHOD__, $options );
00103
00104 foreach ( $res as $r ) {
00105 $title = Title::newFromRow( $r );
00106 $id = TranslatablePage::getMessageGroupIdFromTitle( $title );
00107 self::$groups[$id] = new WikiPageMessageGroup( $id, $title );
00108 self::$groups[$id]->setLabel( $title->getPrefixedText() );
00109 }
00110 wfProfileOut( __METHOD__ . '-pt' );
00111 }
00112
00113 if ( $wgTranslateWorkflowStates ) {
00114 self::$groups['translate-workflow-states'] = new WorkflowStatesMessageGroup();
00115 }
00116
00117 wfProfileIn( __METHOD__ . '-hook' );
00118 $autoload = array();
00119 wfRunHooks( 'TranslatePostInitGroups', array( &self::$groups, &$deps, &$autoload ) );
00120 wfProfileOut( __METHOD__ . '-hook' );
00121
00122 wfProfileIn( __METHOD__ . '-yaml' );
00123 foreach ( $wgTranslateGroupFiles as $configFile ) {
00124 wfDebug( $configFile . "\n" );
00125 $deps[] = new FileDependency( realpath( $configFile ) );
00126 $fgroups = TranslateYaml::parseGroupFile( $configFile );
00127
00128 foreach ( $fgroups as $id => $conf ) {
00129 if ( !empty( $conf['AUTOLOAD'] ) && is_array( $conf['AUTOLOAD'] ) ) {
00130 $dir = dirname( $configFile );
00131 foreach ( $conf['AUTOLOAD'] as $class => $file ) {
00132
00133 $wgAutoloadClasses[$class] = "$dir/$file";
00134 $autoload[$class] = "$dir/$file";
00135 }
00136 }
00137 $group = MessageGroupBase::factory( $conf );
00138 self::$groups[$id] = $group;
00139 }
00140 }
00141 wfProfileOut( __METHOD__ . '-yaml' );
00142
00143 wfProfileIn( __METHOD__ . '-agg' );
00144 $aggregateGroups = self::getAggregateGroups();
00145 foreach ( $aggregateGroups as $id => $group ) {
00146 self::$groups[$id] = $group;
00147 }
00148 wfProfileOut( __METHOD__ . '-agg' );
00149
00150 $key = wfMemckey( 'translate-groups' );
00151 $value = array(
00152 'cc' => self::$groups,
00153 'autoload' => $autoload,
00154 );
00155
00156 wfProfileIn( __METHOD__ . '-save' );
00157 $wrapper = new DependencyWrapper( $value, $deps );
00158 $wrapper->storeToCache( self::getCache(), $key, 60 * 60 * 2 );
00159 wfProfileOut( __METHOD__ . '-save' );
00160
00161 wfDebug( __METHOD__ . "-end\n" );
00162 wfProfileOut( __METHOD__ );
00163 }
00164
00170 public static function getGroup( $id ) {
00171
00172 $id = strtr( $id, '|', '-' );
00173
00174
00175 if ( strpos( $id, 'page-' ) === 0 ) {
00176 $id = strtr( $id, '_', ' ' );
00177 }
00178 self::init();
00179
00180 if ( isset( self::$groups[$id] ) ) {
00181 if ( is_callable( self::$groups[$id] ) ) {
00182 return call_user_func( self::$groups[$id], $id );
00183 }
00184
00185 return self::$groups[$id];
00186 } elseif ( strval( $id ) !== '' && $id[0] === '!' ) {
00187 $dynamic = self::getDynamicGroups();
00188 if ( isset( $dynamic[$id] ) ) {
00189 return new $dynamic[$id];
00190 }
00191 }
00192
00193 return null;
00194 }
00195
00200 public static function exists( $id ) {
00201 return (bool)self::getGroup( $id );
00202 }
00203
00208 public static function getAllGroups() {
00209 return self::singleton()->getGroups();
00210 }
00211
00221 public static function getPriority( $group ) {
00222 if ( !isset( self::$prioritycache ) ) {
00223 self::$prioritycache = array();
00224
00225 $db = wfGetDB( DB_MASTER );
00226 $table = 'translate_groupreviews';
00227 $fields = array( 'tgr_group', 'tgr_state' );
00228 $conds = array( 'tgr_lang' => '*priority' );
00229 $res = $db->select( $table, $fields, $conds, __METHOD__ );
00230 foreach ( $res as $row ) {
00231 self::$prioritycache[$row->tgr_group] = $row->tgr_state;
00232 }
00233 }
00234
00235 if ( $group instanceof MessageGroup ) {
00236 $id = $group->getId();
00237 } else {
00238 $id = $group;
00239 }
00240
00241 return isset( self::$prioritycache[$id] ) ? self::$prioritycache[$id] : '';
00242 }
00243
00252 public static function setPriority( $group, $priority = '' ) {
00253 if ( $group instanceof MessageGroup ) {
00254 $id = $group->getId();
00255 } else {
00256 $id = $group;
00257 }
00258
00259 self::$prioritycache[$id] = $priority;
00260
00261 $dbw = wfGetDB( DB_MASTER );
00262 $table = 'translate_groupreviews';
00263 $row = array(
00264 'tgr_group' => $id,
00265 'tgr_lang' => '*priority',
00266 'tgr_state' => $priority,
00267 );
00268
00269 if ( $priority === '' ) {
00270 unset( $row['tgr_state'] );
00271 $dbw->delete( $table, $row, __METHOD__ );
00272 } else {
00273 $index = array( 'tgr_group', 'tgr_lang' );
00274 $dbw->replace( $table, array( $index ), $row, __METHOD__ );
00275 }
00276 }
00277
00279 public static function isDynamic( MessageGroup $group ) {
00280 $id = $group->getId();
00281
00282 return strval( $id ) !== '' && $id[0] === '!';
00283 }
00284
00292 public static function getSharedGroups( MessageGroup $group ) {
00293
00294
00295
00296
00297 $keys = array_keys( $group->getDefinitions() );
00298 $title = Title::makeTitle( $group->getNamespace(), $keys[0] );
00299 $handle = new MessageHandle( $title );
00300 $ids = $handle->getGroupIds();
00301 foreach ( $ids as $index => $id ) {
00302 if ( $id === $group->getId() ) {
00303 unset( $ids[$index] );
00304 }
00305 }
00306
00307 return $ids;
00308 }
00309
00317 public static function getParentGroups( MessageGroup $targetGroup ) {
00318 $ids = self::getSharedGroups( $targetGroup );
00319 if ( $ids === array() ) {
00320 return array();
00321 }
00322
00323 $targetId = $targetGroup->getId();
00324
00325
00326
00327
00328 $structure = self::getGroupStructure();
00329 foreach ( $structure as $index => $group ) {
00330 if ( $group instanceof MessageGroup ) {
00331 unset( $structure[$index] );
00332 } else {
00333 $structure[$index] = array_shift( $group );
00334 }
00335 }
00336
00337
00338
00339
00340 $paths = array();
00341
00342
00343 $pathFinder = function ( &$paths, $group, $targetId, $prefix = '' )
00344 use ( &$pathFinder ) {
00345 if ( $group instanceof AggregateMessageGroup ) {
00349 foreach ( $group->getGroups() as $subgroup ) {
00350 $subId = $subgroup->getId();
00351 if ( $subId === $targetId ) {
00352 $paths[] = $prefix;
00353 continue;
00354 }
00355
00356 $pathFinder( $paths, $subgroup, $targetId, "$prefix|$subId" );
00357 }
00358 }
00359 };
00360
00361
00362 foreach ( $ids as $id ) {
00363
00364 $group = self::getGroup( $id );
00365
00366
00367 if ( !$group instanceof AggregateMessageGroup ) {
00368 continue;
00369 }
00370
00371 foreach ( $structure as $rootGroup ) {
00375 if ( $rootGroup->getId() === $group->getId() ) {
00376
00377 $pathFinder( $paths, $rootGroup, $targetId, $id );
00378 break;
00379 }
00380 }
00381 }
00382
00383
00384 foreach ( $paths as $index => $pathString ) {
00385 $paths[$index] = explode( '|', $pathString );
00386 }
00387
00388 return $paths;
00389 }
00390
00391 private function __construct() {
00392 }
00393
00398 public static function singleton() {
00399 static $instance;
00400 if ( !$instance instanceof self ) {
00401 $instance = new self();
00402 }
00403
00404 return $instance;
00405 }
00406
00411 public function getGroups() {
00412 self::init();
00413
00414 foreach ( self::$groups as $id => $mixed ) {
00415 if ( !is_object( $mixed ) ) {
00416 self::$groups[$id] = call_user_func( $mixed, $id );
00417 }
00418 }
00419
00420 return self::$groups;
00421 }
00422
00431 public static function getGroupsById( array $ids, $skipMeta = false ) {
00432 $groups = array();
00433 foreach ( $ids as $id ) {
00434 $group = self::getGroup( $id );
00435
00436 if ( $group !== null ) {
00437 if ( $skipMeta && $group->isMeta() ) {
00438 continue;
00439 } else {
00440 $groups[$id] = $group;
00441 }
00442 } else {
00443 wfDebug( __METHOD__ . ": Invalid message group id: $id\n" );
00444 }
00445 }
00446
00447 return $groups;
00448 }
00449
00458 public static function expandWildcards( $ids ) {
00459 $all = array();
00460
00461 $matcher = new StringMatcher( '', (array)$ids );
00462 foreach ( self::getAllGroups() as $id => $_ ) {
00463 if ( $matcher->match( $id ) ) {
00464 $all[] = $id;
00465 }
00466 }
00467
00468 return $all;
00469 }
00470
00475 public static function getDynamicGroups() {
00476 return array(
00477 '!recent' => 'RecentMessageGroup',
00478 '!additions' => 'RecentAdditionsMessageGroup',
00479 '!sandbox' => 'SandboxMessageGroup',
00480 );
00481 }
00482
00489 public static function getGroupsByType( $type ) {
00490 wfProfileIn( __METHOD__ );
00491 $groups = self::getAllGroups();
00492 foreach ( $groups as $id => $group ) {
00493 if ( !$group instanceof $type ) {
00494 unset( $groups[$id] );
00495 }
00496 }
00497 wfProfileOut( __METHOD__ );
00498
00499 return $groups;
00500 }
00501
00511 public static function getGroupStructure() {
00512 $groups = self::getAllGroups();
00513 wfProfileIn( __METHOD__ );
00514
00515
00516 $tree = $groups;
00520 foreach ( $groups as $id => $o ) {
00521 if ( !$o->exists() ) {
00522 unset( $groups[$id], $tree[$id] );
00523 continue;
00524 }
00525
00526 if ( $o instanceof AggregateMessageGroup ) {
00530 foreach ( $o->getGroups() as $sid => $so ) {
00531 unset( $tree[$sid] );
00532 }
00533 }
00534 }
00535
00536
00537
00538 wfSuppressWarnings();
00539 usort( $tree, array( __CLASS__, 'groupLabelSort' ) );
00540 wfRestoreWarnings();
00541
00542
00543
00544
00545 foreach ( $tree as $index => $group ) {
00546 if ( $group instanceof AggregateMessageGroup ) {
00547 $tree[$index] = self::subGroups( $group );
00548 }
00549 }
00550
00551
00552
00553
00554
00555 $used = array();
00556
00557 array_walk_recursive( $tree, array( __CLASS__, 'collectGroupIds' ), array( &$used ) );
00558 $unused = array_diff( array_keys( $groups ), array_keys( $used ) );
00559 if ( count( $unused ) ) {
00560 foreach ( $unused as $index => $id ) {
00561 if ( !$groups[$id] instanceof AggregateMessageGroup ) {
00562 unset( $unused[$index] );
00563 }
00564 }
00565
00566
00567 $participants = implode( ', ', $unused );
00568 throw new MWException( "Found cyclic aggregate message groups: $participants" );
00569 }
00570
00571 wfProfileOut( __METHOD__ );
00572
00573 return $tree;
00574 }
00575
00577 public static function collectGroupIds( $value, $key, $used ) {
00578 $used[0][$value->getId()] = true;
00579 }
00580
00582 public static function groupLabelSort( $a, $b ) {
00583 $al = $a->getLabel();
00584 $bl = $b->getLabel();
00585
00586 return strcasecmp( $al, $bl );
00587 }
00588
00598 public static function subGroups( AggregateMessageGroup $parent ) {
00599 static $recursionGuard = array();
00600
00601 $pid = $parent->getId();
00602 if ( isset( $recursionGuard[$pid] ) ) {
00603 $tid = $pid;
00604 $path = array( $tid );
00605 do {
00606 $tid = $recursionGuard[$tid];
00607 $path[] = $tid;
00608
00609 } while ( $tid !== $pid );
00610 $path = implode( ' > ', $path );
00611 throw new MWException( "Found cyclic aggregate message groups: $path" );
00612 }
00613
00614
00615 $tree = array_values( $parent->getGroups() );
00616 usort( $tree, array( __CLASS__, 'groupLabelSort' ) );
00617
00618 foreach ( $tree as $index => $group ) {
00619 if ( $group instanceof AggregateMessageGroup ) {
00620 $sid = $group->getId();
00621 $recursionGuard[$pid] = $sid;
00622 $tree[$index] = self::subGroups( $group );
00623 unset( $recursionGuard[$pid] );
00624 }
00625 }
00626
00627
00628 array_unshift( $tree, $parent );
00629
00630 return $tree;
00631 }
00632
00639 public static function haveSingleSourceLanguage( array $groups ) {
00640 $languages = array();
00641
00642 foreach ( $groups as $group ) {
00643 $language = $group->getSourceLanguage();
00644 if ( !in_array( $language, $languages ) ) {
00645 $languages[] = $language;
00646 }
00647 }
00648
00649 if ( count( $languages ) === 1 ) {
00650 return $languages[0];
00651 }
00652
00653 return '';
00654 }
00655
00661 protected static function getAggregateGroups() {
00662 $dbw = wfGetDB( DB_MASTER );
00663 $tables = array( 'translate_metadata' );
00664 $fields = array( 'tmd_group', 'tmd_value' );
00665 $conds = array( 'tmd_key' => 'subgroups' );
00666 $res = $dbw->select( $tables, $fields, $conds, __METHOD__ );
00667
00668 $groups = array();
00669 foreach ( $res as $row ) {
00670 $id = $row->tmd_group;
00671
00672 $conf = array();
00673 $conf['BASIC'] = array(
00674 'id' => $id,
00675 'label' => TranslateMetadata::get( $id, 'name' ),
00676 'description' => TranslateMetadata::get( $id, 'description' ),
00677 'meta' => 1,
00678 'class' => 'AggregateMessageGroup',
00679 'namespace' => NS_TRANSLATIONS,
00680 );
00681 $conf['GROUPS'] = TranslateMetadata::getSubgroups( $id );
00682 $group = MessageGroupBase::factory( $conf );
00683
00684 $groups[$id] = $group;
00685 }
00686
00687 return $groups;
00688 }
00689
00698 public static function isTranslatableMessage( MessageHandle $handle ) {
00699 static $cache = array();
00700
00701 if ( !$handle->isValid() ) {
00702 return false;
00703 }
00704
00705 $group = $handle->getGroup();
00706 $groupId = $group->getId();
00707 $language = $handle->getCode();
00708 $cacheKey = "$groupId:$language";
00709
00710 if ( !isset( $cache[$cacheKey] ) ) {
00711 $allowed = true;
00712 $discouraged = false;
00713
00714 $whitelist = $group->getTranslatableLanguages();
00715 if ( is_array( $whitelist ) && !isset( $whitelist[$language] ) ) {
00716 $allowed = false;
00717 }
00718
00719 if ( self::getPriority( $group ) === 'discouraged' ) {
00720 $discouraged = true;
00721 } else {
00722 $priorityLanguages = TranslateMetadata::get( $groupId, 'prioritylangs' );
00723 if ( $priorityLanguages ) {
00724 $map = array_flip( explode( ',', $priorityLanguages ) );
00725 if ( !isset( $map[$language] ) ) {
00726 $discouraged = true;
00727 }
00728 }
00729 }
00730
00731 $cache[$cacheKey] = array(
00732 'relevant' => $allowed && !$discouraged,
00733 'tags' => array(),
00734 );
00735
00736 $groupTags = $group->getTags();
00737 foreach ( array( 'ignored', 'optional' ) as $tag ) {
00738 if ( isset( $groupTags[$tag] ) ) {
00739 foreach ( $groupTags[$tag] as $key ) {
00740
00741 $cache[$cacheKey]['tags'][ucfirst( $key )] = true;
00742 }
00743 }
00744 }
00745 }
00746
00747 return $cache[$cacheKey]['relevant'] &&
00748 !isset( $cache[$cacheKey]['tags'][ucfirst( $handle->getKey() )] );
00749 }
00750 }