MediaWiki  master
LocalisationCache.php
Go to the documentation of this file.
00001 <?php
00023 define( 'MW_LC_VERSION', 2 );
00024 
00037 class LocalisationCache {
00039         var $conf;
00040 
00046         var $manualRecache = false;
00047 
00051         var $forceRecache = false;
00052 
00059         var $data = array();
00060 
00066         var $store;
00067 
00075         var $loadedItems = array();
00076 
00081         var $loadedSubitems = array();
00082 
00088         var $initialisedLangs = array();
00089 
00095         var $shallowFallbacks = array();
00096 
00100         var $recachedLangs = array();
00101 
00105         static public $allKeys = array(
00106                 'fallback', 'namespaceNames', 'bookstoreList',
00107                 'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
00108                 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
00109                 'linkTrail', 'namespaceAliases',
00110                 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
00111                 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
00112                 'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
00113                 'digitGroupingPattern', 'pluralRules', 'compiledPluralRules',
00114         );
00115 
00120         static public $mergeableMapKeys = array( 'messages', 'namespaceNames',
00121                 'dateFormats', 'imageFiles', 'preloadedMessages'
00122         );
00123 
00127         static public $mergeableListKeys = array( 'extraUserToggles' );
00128 
00133         static public $mergeableAliasListKeys = array( 'specialPageAliases' );
00134 
00140         static public $optionalMergeKeys = array( 'bookstoreList' );
00141 
00145         static public $magicWordKeys = array( 'magicWords' );
00146 
00150         static public $splitKeys = array( 'messages' );
00151 
00155         static public $preloadedKeys = array( 'dateFormats', 'namespaceNames' );
00156 
00161         var $pluralRules = null;
00162 
00163         var $mergeableKeys = null;
00164 
00173         function __construct( $conf ) {
00174                 global $wgCacheDirectory;
00175 
00176                 $this->conf = $conf;
00177                 $storeConf = array();
00178                 if ( !empty( $conf['storeClass'] ) ) {
00179                         $storeClass = $conf['storeClass'];
00180                 } else {
00181                         switch ( $conf['store'] ) {
00182                                 case 'files':
00183                                 case 'file':
00184                                         $storeClass = 'LCStore_CDB';
00185                                         break;
00186                                 case 'db':
00187                                         $storeClass = 'LCStore_DB';
00188                                         break;
00189                                 case 'accel':
00190                                         $storeClass = 'LCStore_Accel';
00191                                         break;
00192                                 case 'detect':
00193                                         $storeClass = $wgCacheDirectory ? 'LCStore_CDB' : 'LCStore_DB';
00194                                         break;
00195                                 default:
00196                                         throw new MWException(
00197                                                 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.' );
00198                         }
00199                 }
00200 
00201                 wfDebug( get_class( $this ) . ": using store $storeClass\n" );
00202                 if ( !empty( $conf['storeDirectory'] ) ) {
00203                         $storeConf['directory'] = $conf['storeDirectory'];
00204                 }
00205 
00206                 $this->store = new $storeClass( $storeConf );
00207                 foreach ( array( 'manualRecache', 'forceRecache' ) as $var ) {
00208                         if ( isset( $conf[$var] ) ) {
00209                                 $this->$var = $conf[$var];
00210                         }
00211                 }
00212         }
00213 
00220         public function isMergeableKey( $key ) {
00221                 if ( $this->mergeableKeys === null ) {
00222                         $this->mergeableKeys = array_flip( array_merge(
00223                                 self::$mergeableMapKeys,
00224                                 self::$mergeableListKeys,
00225                                 self::$mergeableAliasListKeys,
00226                                 self::$optionalMergeKeys,
00227                                 self::$magicWordKeys
00228                         ) );
00229                 }
00230                 return isset( $this->mergeableKeys[$key] );
00231         }
00232 
00242         public function getItem( $code, $key ) {
00243                 if ( !isset( $this->loadedItems[$code][$key] ) ) {
00244                         wfProfileIn( __METHOD__ . '-load' );
00245                         $this->loadItem( $code, $key );
00246                         wfProfileOut( __METHOD__ . '-load' );
00247                 }
00248 
00249                 if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
00250                         return $this->shallowFallbacks[$code];
00251                 }
00252 
00253                 return $this->data[$code][$key];
00254         }
00255 
00263         public function getSubitem( $code, $key, $subkey ) {
00264                 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
00265                          !isset( $this->loadedItems[$code][$key] ) ) {
00266                         wfProfileIn( __METHOD__ . '-load' );
00267                         $this->loadSubitem( $code, $key, $subkey );
00268                         wfProfileOut( __METHOD__ . '-load' );
00269                 }
00270 
00271                 if ( isset( $this->data[$code][$key][$subkey] ) ) {
00272                         return $this->data[$code][$key][$subkey];
00273                 } else {
00274                         return null;
00275                 }
00276         }
00277 
00290         public function getSubitemList( $code, $key ) {
00291                 if ( in_array( $key, self::$splitKeys ) ) {
00292                         return $this->getSubitem( $code, 'list', $key );
00293                 } else {
00294                         $item = $this->getItem( $code, $key );
00295                         if ( is_array( $item ) ) {
00296                                 return array_keys( $item );
00297                         } else {
00298                                 return false;
00299                         }
00300                 }
00301         }
00302 
00308         protected function loadItem( $code, $key ) {
00309                 if ( !isset( $this->initialisedLangs[$code] ) ) {
00310                         $this->initLanguage( $code );
00311                 }
00312 
00313                 // Check to see if initLanguage() loaded it for us
00314                 if ( isset( $this->loadedItems[$code][$key] ) ) {
00315                         return;
00316                 }
00317 
00318                 if ( isset( $this->shallowFallbacks[$code] ) ) {
00319                         $this->loadItem( $this->shallowFallbacks[$code], $key );
00320                         return;
00321                 }
00322 
00323                 if ( in_array( $key, self::$splitKeys ) ) {
00324                         $subkeyList = $this->getSubitem( $code, 'list', $key );
00325                         foreach ( $subkeyList as $subkey ) {
00326                                 if ( isset( $this->data[$code][$key][$subkey] ) ) {
00327                                         continue;
00328                                 }
00329                                 $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
00330                         }
00331                 } else {
00332                         $this->data[$code][$key] = $this->store->get( $code, $key );
00333                 }
00334 
00335                 $this->loadedItems[$code][$key] = true;
00336         }
00337 
00345         protected function loadSubitem( $code, $key, $subkey ) {
00346                 if ( !in_array( $key, self::$splitKeys ) ) {
00347                         $this->loadItem( $code, $key );
00348                         return;
00349                 }
00350 
00351                 if ( !isset( $this->initialisedLangs[$code] ) ) {
00352                         $this->initLanguage( $code );
00353                 }
00354 
00355                 // Check to see if initLanguage() loaded it for us
00356                 if ( isset( $this->loadedItems[$code][$key] ) ||
00357                          isset( $this->loadedSubitems[$code][$key][$subkey] ) ) {
00358                         return;
00359                 }
00360 
00361                 if ( isset( $this->shallowFallbacks[$code] ) ) {
00362                         $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
00363                         return;
00364                 }
00365 
00366                 $value = $this->store->get( $code, "$key:$subkey" );
00367                 $this->data[$code][$key][$subkey] = $value;
00368                 $this->loadedSubitems[$code][$key][$subkey] = true;
00369         }
00370 
00375         public function isExpired( $code ) {
00376                 if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
00377                         wfDebug( __METHOD__ . "($code): forced reload\n" );
00378                         return true;
00379                 }
00380 
00381                 $deps = $this->store->get( $code, 'deps' );
00382                 $keys = $this->store->get( $code, 'list', 'messages' );
00383                 $preload = $this->store->get( $code, 'preload' );
00384                 // Different keys may expire separately, at least in LCStore_Accel
00385                 if ( $deps === null || $keys === null || $preload === null ) {
00386                         wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
00387                         return true;
00388                 }
00389 
00390                 foreach ( $deps as $dep ) {
00391                         // Because we're unserializing stuff from cache, we
00392                         // could receive objects of classes that don't exist
00393                         // anymore (e.g. uninstalled extensions)
00394                         // When this happens, always expire the cache
00395                         if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
00396                                 wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
00397                                         get_class( $dep ) . "\n" );
00398                                 return true;
00399                         }
00400                 }
00401 
00402                 return false;
00403         }
00404 
00410         protected function initLanguage( $code ) {
00411                 if ( isset( $this->initialisedLangs[$code] ) ) {
00412                         return;
00413                 }
00414 
00415                 $this->initialisedLangs[$code] = true;
00416 
00417                 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
00418                 if ( !Language::isValidBuiltInCode( $code ) ) {
00419                         $this->initShallowFallback( $code, 'en' );
00420                         return;
00421                 }
00422 
00423                 # Recache the data if necessary
00424                 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
00425                         if ( file_exists( Language::getMessagesFileName( $code ) ) ) {
00426                                 $this->recache( $code );
00427                         } elseif ( $code === 'en' ) {
00428                                 throw new MWException( 'MessagesEn.php is missing.' );
00429                         } else {
00430                                 $this->initShallowFallback( $code, 'en' );
00431                         }
00432                         return;
00433                 }
00434 
00435                 # Preload some stuff
00436                 $preload = $this->getItem( $code, 'preload' );
00437                 if ( $preload === null ) {
00438                         if ( $this->manualRecache ) {
00439                                 // No Messages*.php file. Do shallow fallback to en.
00440                                 if ( $code === 'en' ) {
00441                                         throw new MWException( 'No localisation cache found for English. ' .
00442                                                 'Please run maintenance/rebuildLocalisationCache.php.' );
00443                                 }
00444                                 $this->initShallowFallback( $code, 'en' );
00445                                 return;
00446                         } else {
00447                                 throw new MWException( 'Invalid or missing localisation cache.' );
00448                         }
00449                 }
00450                 $this->data[$code] = $preload;
00451                 foreach ( $preload as $key => $item ) {
00452                         if ( in_array( $key, self::$splitKeys ) ) {
00453                                 foreach ( $item as $subkey => $subitem ) {
00454                                         $this->loadedSubitems[$code][$key][$subkey] = true;
00455                                 }
00456                         } else {
00457                                 $this->loadedItems[$code][$key] = true;
00458                         }
00459                 }
00460         }
00461 
00468         public function initShallowFallback( $primaryCode, $fallbackCode ) {
00469                 $this->data[$primaryCode] =& $this->data[$fallbackCode];
00470                 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
00471                 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
00472                 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
00473         }
00474 
00482         protected function readPHPFile( $_fileName, $_fileType ) {
00483                 // Disable APC caching
00484                 $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
00485                 include( $_fileName );
00486                 ini_set( 'apc.cache_by_default', $_apcEnabled );
00487 
00488                 if ( $_fileType == 'core' || $_fileType == 'extension' ) {
00489                         $data = compact( self::$allKeys );
00490                 } elseif ( $_fileType == 'aliases' ) {
00491                         $data = compact( 'aliases' );
00492                 } else {
00493                         throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
00494                 }
00495                 return $data;
00496         }
00497 
00502         public function getCompiledPluralRules( $code ) {
00503                 $rules = $this->getPluralRules( $code );
00504                 if ( $rules === null ) {
00505                         return null;
00506                 }
00507                 try {
00508                         $compiledRules = CLDRPluralRuleEvaluator::compile( $rules );
00509                 } catch( CLDRPluralRuleError $e ) {
00510                         wfDebugLog( 'l10n', $e->getMessage() . "\n" );
00511                         return array();
00512                 }
00513                 return $compiledRules;
00514         }
00515 
00521         public function getPluralRules( $code ) {
00522                 if ( $this->pluralRules === null ) {
00523                         $cldrPlural = __DIR__ . "/../languages/data/plurals.xml";
00524                         $mwPlural = __DIR__ . "/../languages/data/plurals-mediawiki.xml";
00525                         // Load CLDR plural rules
00526                         $this->loadPluralFile( $cldrPlural );
00527                         if ( file_exists( $mwPlural ) ) {
00528                                 // Override or extend
00529                                 $this->loadPluralFile( $mwPlural );
00530                         }
00531                 }
00532                 if ( !isset( $this->pluralRules[$code] ) ) {
00533                         return null;
00534                 } else {
00535                         return $this->pluralRules[$code];
00536                 }
00537         }
00538 
00539 
00544         protected function loadPluralFile( $fileName ) {
00545                 $doc = new DOMDocument;
00546                 $doc->load( $fileName );
00547                 $rulesets = $doc->getElementsByTagName( "pluralRules" );
00548                 foreach ( $rulesets as $ruleset ) {
00549                         $codes = $ruleset->getAttribute( 'locales' );
00550                         $rules = array();
00551                         $ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
00552                         foreach ( $ruleElements as $elt ) {
00553                                 $rules[] = $elt->nodeValue;
00554                         }
00555                         foreach ( explode( ' ', $codes ) as $code ) {
00556                                 $this->pluralRules[$code] = $rules;
00557                         }
00558                 }
00559         }
00560 
00566         protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
00567                 $fileName = Language::getMessagesFileName( $code );
00568                 if ( !file_exists( $fileName ) ) {
00569                         return false;
00570                 }
00571 
00572                 $deps[] = new FileDependency( $fileName );
00573                 $data = $this->readPHPFile( $fileName, 'core' );
00574 
00575                 # Load CLDR plural rules for JavaScript
00576                 $data['pluralRules'] = $this->getPluralRules( $code );
00577                 # And for PHP
00578                 $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
00579 
00580                 $deps['plurals'] = new FileDependency( __DIR__ . "/../languages/data/plurals.xml" );
00581                 $deps['plurals-mw'] = new FileDependency( __DIR__ . "/../languages/data/plurals-mediawiki.xml" );
00582                 return $data;
00583         }
00584 
00592         protected function mergeItem( $key, &$value, $fallbackValue ) {
00593                 if ( !is_null( $value ) ) {
00594                         if ( !is_null( $fallbackValue ) ) {
00595                                 if ( in_array( $key, self::$mergeableMapKeys ) ) {
00596                                         $value = $value + $fallbackValue;
00597                                 } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
00598                                         $value = array_unique( array_merge( $fallbackValue, $value ) );
00599                                 } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
00600                                         $value = array_merge_recursive( $value, $fallbackValue );
00601                                 } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
00602                                         if ( !empty( $value['inherit'] ) )  {
00603                                                 $value = array_merge( $fallbackValue, $value );
00604                                         }
00605 
00606                                         if ( isset( $value['inherit'] ) ) {
00607                                                 unset( $value['inherit'] );
00608                                         }
00609                                 } elseif ( in_array( $key, self::$magicWordKeys ) ) {
00610                                         $this->mergeMagicWords( $value, $fallbackValue );
00611                                 }
00612                         }
00613                 } else {
00614                         $value = $fallbackValue;
00615                 }
00616         }
00617 
00622         protected function mergeMagicWords( &$value, $fallbackValue ) {
00623                 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
00624                         if ( !isset( $value[$magicName] ) ) {
00625                                 $value[$magicName] = $fallbackInfo;
00626                         } else {
00627                                 $oldSynonyms = array_slice( $fallbackInfo, 1 );
00628                                 $newSynonyms = array_slice( $value[$magicName], 1 );
00629                                 $synonyms = array_values( array_unique( array_merge(
00630                                         $newSynonyms, $oldSynonyms ) ) );
00631                                 $value[$magicName] = array_merge( array( $fallbackInfo[0] ), $synonyms );
00632                         }
00633                 }
00634         }
00635 
00649         protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
00650                 $used = false;
00651                 foreach ( $codeSequence as $code ) {
00652                         if ( isset( $fallbackValue[$code] ) ) {
00653                                 $this->mergeItem( $key, $value, $fallbackValue[$code] );
00654                                 $used = true;
00655                         }
00656                 }
00657 
00658                 return $used;
00659         }
00660 
00667         public function recache( $code ) {
00668                 global $wgExtensionMessagesFiles;
00669                 wfProfileIn( __METHOD__ );
00670 
00671                 if ( !$code ) {
00672                         throw new MWException( "Invalid language code requested" );
00673                 }
00674                 $this->recachedLangs[$code] = true;
00675 
00676                 # Initial values
00677                 $initialData = array_combine(
00678                         self::$allKeys,
00679                         array_fill( 0, count( self::$allKeys ), null ) );
00680                 $coreData = $initialData;
00681                 $deps = array();
00682 
00683                 # Load the primary localisation from the source file
00684                 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
00685                 if ( $data === false ) {
00686                         wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" );
00687                         $coreData['fallback'] = 'en';
00688                 } else {
00689                         wfDebug( __METHOD__ . ": got localisation for $code from source\n" );
00690 
00691                         # Merge primary localisation
00692                         foreach ( $data as $key => $value ) {
00693                                 $this->mergeItem( $key, $coreData[$key], $value );
00694                         }
00695 
00696                 }
00697 
00698                 # Fill in the fallback if it's not there already
00699                 if ( is_null( $coreData['fallback'] ) ) {
00700                         $coreData['fallback'] = $code === 'en' ? false : 'en';
00701                 }
00702                 if ( $coreData['fallback'] === false ) {
00703                         $coreData['fallbackSequence'] = array();
00704                 } else {
00705                         $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
00706                         $len = count( $coreData['fallbackSequence'] );
00707 
00708                         # Ensure that the sequence ends at en
00709                         if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
00710                                 $coreData['fallbackSequence'][] = 'en';
00711                         }
00712 
00713                         # Load the fallback localisation item by item and merge it
00714                         foreach ( $coreData['fallbackSequence'] as $fbCode ) {
00715                                 # Load the secondary localisation from the source file to
00716                                 # avoid infinite cycles on cyclic fallbacks
00717                                 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps );
00718                                 if ( $fbData === false ) {
00719                                         continue;
00720                                 }
00721 
00722                                 foreach ( self::$allKeys as $key ) {
00723                                         if ( !isset( $fbData[$key] ) ) {
00724                                                 continue;
00725                                         }
00726 
00727                                         if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
00728                                                 $this->mergeItem( $key, $coreData[$key], $fbData[$key] );
00729                                         }
00730                                 }
00731                         }
00732                 }
00733 
00734                 $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] );
00735 
00736                 # Load the extension localisations
00737                 # This is done after the core because we know the fallback sequence now.
00738                 # But it has a higher precedence for merging so that we can support things
00739                 # like site-specific message overrides.
00740                 $allData = $initialData;
00741                 foreach ( $wgExtensionMessagesFiles as $fileName ) {
00742                         $data = $this->readPHPFile( $fileName, 'extension' );
00743                         $used = false;
00744 
00745                         foreach ( $data as $key => $item ) {
00746                                 if ( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
00747                                         $used = true;
00748                                 }
00749                         }
00750 
00751                         if ( $used ) {
00752                                 $deps[] = new FileDependency( $fileName );
00753                         }
00754                 }
00755 
00756                 # Merge core data into extension data
00757                 foreach ( $coreData as $key => $item ) {
00758                         $this->mergeItem( $key, $allData[$key], $item );
00759                 }
00760 
00761                 # Add cache dependencies for any referenced globals
00762                 $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
00763                 $deps['version'] = new ConstantDependency( 'MW_LC_VERSION' );
00764 
00765                 # Add dependencies to the cache entry
00766                 $allData['deps'] = $deps;
00767 
00768                 # Replace spaces with underscores in namespace names
00769                 $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
00770 
00771                 # And do the same for special page aliases. $page is an array.
00772                 foreach ( $allData['specialPageAliases'] as &$page ) {
00773                         $page = str_replace( ' ', '_', $page );
00774                 }
00775                 # Decouple the reference to prevent accidental damage
00776                 unset( $page );
00777 
00778                 # If there were no plural rules, return an empty array
00779                 if ( $allData['pluralRules'] === null ) {
00780                         $allData['pluralRules'] = array();
00781                 }
00782                 if ( $allData['compiledPluralRules'] === null ) {
00783                         $allData['compiledPluralRules'] = array();
00784                 }
00785 
00786                 # Set the list keys
00787                 $allData['list'] = array();
00788                 foreach ( self::$splitKeys as $key ) {
00789                         $allData['list'][$key] = array_keys( $allData[$key] );
00790                 }
00791                 # Run hooks
00792                 wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) );
00793 
00794                 if ( is_null( $allData['namespaceNames'] ) ) {
00795                         throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
00796                                 'Check that your languages/messages/MessagesEn.php file is intact.' );
00797                 }
00798 
00799                 # Set the preload key
00800                 $allData['preload'] = $this->buildPreload( $allData );
00801 
00802                 # Save to the process cache and register the items loaded
00803                 $this->data[$code] = $allData;
00804                 foreach ( $allData as $key => $item ) {
00805                         $this->loadedItems[$code][$key] = true;
00806                 }
00807 
00808                 # Save to the persistent cache
00809                 $this->store->startWrite( $code );
00810                 foreach ( $allData as $key => $value ) {
00811                         if ( in_array( $key, self::$splitKeys ) ) {
00812                                 foreach ( $value as $subkey => $subvalue ) {
00813                                         $this->store->set( "$key:$subkey", $subvalue );
00814                                 }
00815                         } else {
00816                                 $this->store->set( $key, $value );
00817                         }
00818                 }
00819                 $this->store->finishWrite();
00820 
00821                 # Clear out the MessageBlobStore
00822                 # HACK: If using a null (i.e. disabled) storage backend, we
00823                 # can't write to the MessageBlobStore either
00824                 if ( !$this->store instanceof LCStore_Null ) {
00825                         MessageBlobStore::clear();
00826                 }
00827 
00828                 wfProfileOut( __METHOD__ );
00829         }
00830 
00839         protected function buildPreload( $data ) {
00840                 $preload = array( 'messages' => array() );
00841                 foreach ( self::$preloadedKeys as $key ) {
00842                         $preload[$key] = $data[$key];
00843                 }
00844 
00845                 foreach ( $data['preloadedMessages'] as $subkey ) {
00846                         if ( isset( $data['messages'][$subkey] ) ) {
00847                                 $subitem = $data['messages'][$subkey];
00848                         } else {
00849                                 $subitem = null;
00850                         }
00851                         $preload['messages'][$subkey] = $subitem;
00852                 }
00853 
00854                 return $preload;
00855         }
00856 
00862         public function unload( $code ) {
00863                 unset( $this->data[$code] );
00864                 unset( $this->loadedItems[$code] );
00865                 unset( $this->loadedSubitems[$code] );
00866                 unset( $this->initialisedLangs[$code] );
00867 
00868                 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
00869                         if ( $fbCode === $code ) {
00870                                 $this->unload( $shallowCode );
00871                         }
00872                 }
00873         }
00874 
00878         public function unloadAll() {
00879                 foreach ( $this->initialisedLangs as $lang => $unused ) {
00880                         $this->unload( $lang );
00881                 }
00882         }
00883 
00887         public function disableBackend() {
00888                 $this->store = new LCStore_Null;
00889                 $this->manualRecache = false;
00890         }
00891 }
00892 
00910 interface LCStore {
00916         function get( $code, $key );
00917 
00922         function startWrite( $code );
00923 
00927         function finishWrite();
00928 
00935         function set( $key, $value );
00936 }
00937 
00943 class LCStore_Accel implements LCStore {
00944         var $currentLang;
00945         var $keys;
00946 
00947         public function __construct() {
00948                 $this->cache = wfGetCache( CACHE_ACCEL );
00949         }
00950 
00951         public function get( $code, $key ) {
00952                 $k = wfMemcKey( 'l10n', $code, 'k', $key );
00953                 $r = $this->cache->get( $k );
00954                 return $r === false ? null : $r;
00955         }
00956 
00957         public function startWrite( $code ) {
00958                 $k = wfMemcKey( 'l10n', $code, 'l' );
00959                 $keys = $this->cache->get( $k );
00960                 if ( $keys ) {
00961                         foreach ( $keys as $k ) {
00962                                 $this->cache->delete( $k );
00963                         }
00964                 }
00965                 $this->currentLang = $code;
00966                 $this->keys = array();
00967         }
00968 
00969         public function finishWrite() {
00970                 if ( $this->currentLang ) {
00971                         $k = wfMemcKey( 'l10n', $this->currentLang, 'l' );
00972                         $this->cache->set( $k, array_keys( $this->keys ) );
00973                 }
00974                 $this->currentLang = null;
00975                 $this->keys = array();
00976         }
00977 
00978         public function set( $key, $value ) {
00979                 if ( $this->currentLang ) {
00980                         $k = wfMemcKey( 'l10n', $this->currentLang, 'k', $key );
00981                         $this->keys[$k] = true;
00982                         $this->cache->set( $k, $value );
00983                 }
00984         }
00985 }
00986 
00991 class LCStore_DB implements LCStore {
00992         var $currentLang;
00993         var $writesDone = false;
00994 
00998         var $dbw;
00999         var $batch;
01000         var $readOnly = false;
01001 
01002         public function get( $code, $key ) {
01003                 if ( $this->writesDone ) {
01004                         $db = wfGetDB( DB_MASTER );
01005                 } else {
01006                         $db = wfGetDB( DB_SLAVE );
01007                 }
01008                 $row = $db->selectRow( 'l10n_cache', array( 'lc_value' ),
01009                         array( 'lc_lang' => $code, 'lc_key' => $key ), __METHOD__ );
01010                 if ( $row ) {
01011                         return unserialize( $row->lc_value );
01012                 } else {
01013                         return null;
01014                 }
01015         }
01016 
01017         public function startWrite( $code ) {
01018                 if ( $this->readOnly ) {
01019                         return;
01020                 }
01021 
01022                 if ( !$code ) {
01023                         throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
01024                 }
01025 
01026                 $this->dbw = wfGetDB( DB_MASTER );
01027                 try {
01028                         $this->dbw->begin( __METHOD__ );
01029                         $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ );
01030                 } catch ( DBQueryError $e ) {
01031                         if ( $this->dbw->wasReadOnlyError() ) {
01032                                 $this->readOnly = true;
01033                                 $this->dbw->rollback( __METHOD__ );
01034                                 $this->dbw->ignoreErrors( false );
01035                                 return;
01036                         } else {
01037                                 throw $e;
01038                         }
01039                 }
01040 
01041                 $this->currentLang = $code;
01042                 $this->batch = array();
01043         }
01044 
01045         public function finishWrite() {
01046                 if ( $this->readOnly ) {
01047                         return;
01048                 }
01049 
01050                 if ( $this->batch ) {
01051                         $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
01052                 }
01053 
01054                 $this->dbw->commit( __METHOD__ );
01055                 $this->currentLang = null;
01056                 $this->dbw = null;
01057                 $this->batch = array();
01058                 $this->writesDone = true;
01059         }
01060 
01061         public function set( $key, $value ) {
01062                 if ( $this->readOnly ) {
01063                         return;
01064                 }
01065 
01066                 if ( is_null( $this->currentLang ) ) {
01067                         throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
01068                 }
01069 
01070                 $this->batch[] = array(
01071                         'lc_lang' => $this->currentLang,
01072                         'lc_key' => $key,
01073                         'lc_value' => serialize( $value ) );
01074 
01075                 if ( count( $this->batch ) >= 100 ) {
01076                         $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
01077                         $this->batch = array();
01078                 }
01079         }
01080 }
01081 
01094 class LCStore_CDB implements LCStore {
01095         var $readers, $writer, $currentLang, $directory;
01096 
01097         function __construct( $conf = array() ) {
01098                 global $wgCacheDirectory;
01099 
01100                 if ( isset( $conf['directory'] ) ) {
01101                         $this->directory = $conf['directory'];
01102                 } else {
01103                         $this->directory = $wgCacheDirectory;
01104                 }
01105         }
01106 
01107         public function get( $code, $key ) {
01108                 if ( !isset( $this->readers[$code] ) ) {
01109                         $fileName = $this->getFileName( $code );
01110 
01111                         if ( !file_exists( $fileName ) ) {
01112                                 $this->readers[$code] = false;
01113                         } else {
01114                                 $this->readers[$code] = CdbReader::open( $fileName );
01115                         }
01116                 }
01117 
01118                 if ( !$this->readers[$code] ) {
01119                         return null;
01120                 } else {
01121                         $value = $this->readers[$code]->get( $key );
01122 
01123                         if ( $value === false ) {
01124                                 return null;
01125                         }
01126                         return unserialize( $value );
01127                 }
01128         }
01129 
01130         public function startWrite( $code ) {
01131                 if ( !file_exists( $this->directory ) ) {
01132                         if ( !wfMkdirParents( $this->directory, null, __METHOD__ ) ) {
01133                                 throw new MWException( "Unable to create the localisation store " .
01134                                         "directory \"{$this->directory}\"" );
01135                         }
01136                 }
01137 
01138                 // Close reader to stop permission errors on write
01139                 if ( !empty( $this->readers[$code] ) ) {
01140                         $this->readers[$code]->close();
01141                 }
01142 
01143                 $this->writer = CdbWriter::open( $this->getFileName( $code ) );
01144                 $this->currentLang = $code;
01145         }
01146 
01147         public function finishWrite() {
01148                 // Close the writer
01149                 $this->writer->close();
01150                 $this->writer = null;
01151                 unset( $this->readers[$this->currentLang] );
01152                 $this->currentLang = null;
01153         }
01154 
01155         public function set( $key, $value ) {
01156                 if ( is_null( $this->writer ) ) {
01157                         throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
01158                 }
01159                 $this->writer->set( $key, serialize( $value ) );
01160         }
01161 
01162         protected function getFileName( $code ) {
01163                 if ( !$code || strpos( $code, '/' ) !== false ) {
01164                         throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
01165                 }
01166                 return "{$this->directory}/l10n_cache-$code.cdb";
01167         }
01168 }
01169 
01173 class LCStore_Null implements LCStore {
01174         public function get( $code, $key ) {
01175                 return null;
01176         }
01177 
01178         public function startWrite( $code ) {}
01179         public function finishWrite() {}
01180         public function set( $key, $value ) {}
01181 }
01182 
01187 class LocalisationCache_BulkLoad extends LocalisationCache {
01192         var $fileCache = array();
01193 
01199         var $mruLangs = array();
01200 
01204         var $maxLoadedLangs = 10;
01205 
01211         protected function readPHPFile( $fileName, $fileType ) {
01212                 $serialize = $fileType === 'core';
01213                 if ( !isset( $this->fileCache[$fileName][$fileType] ) ) {
01214                         $data = parent::readPHPFile( $fileName, $fileType );
01215 
01216                         if ( $serialize ) {
01217                                 $encData = serialize( $data );
01218                         } else {
01219                                 $encData = $data;
01220                         }
01221 
01222                         $this->fileCache[$fileName][$fileType] = $encData;
01223 
01224                         return $data;
01225                 } elseif ( $serialize ) {
01226                         return unserialize( $this->fileCache[$fileName][$fileType] );
01227                 } else {
01228                         return $this->fileCache[$fileName][$fileType];
01229                 }
01230         }
01231 
01237         public function getItem( $code, $key ) {
01238                 unset( $this->mruLangs[$code] );
01239                 $this->mruLangs[$code] = true;
01240                 return parent::getItem( $code, $key );
01241         }
01242 
01249         public function getSubitem( $code, $key, $subkey ) {
01250                 unset( $this->mruLangs[$code] );
01251                 $this->mruLangs[$code] = true;
01252                 return parent::getSubitem( $code, $key, $subkey );
01253         }
01254 
01258         public function recache( $code ) {
01259                 parent::recache( $code );
01260                 unset( $this->mruLangs[$code] );
01261                 $this->mruLangs[$code] = true;
01262                 $this->trimCache();
01263         }
01264 
01268         public function unload( $code ) {
01269                 unset( $this->mruLangs[$code] );
01270                 parent::unload( $code );
01271         }
01272 
01276         protected function trimCache() {
01277                 while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) {
01278                         reset( $this->mruLangs );
01279                         $code = key( $this->mruLangs );
01280                         wfDebug( __METHOD__ . ": unloading $code\n" );
01281                         $this->unload( $code );
01282                 }
01283         }
01284 
01285 }