MediaWiki  REL1_19
LocalisationCache.php
Go to the documentation of this file.
00001 <?php
00002 
00003 define( 'MW_LC_VERSION', 2 );
00004 
00017 class LocalisationCache {
00019         var $conf;
00020 
00026         var $manualRecache = false;
00027 
00031         var $forceRecache = false;
00032 
00039         var $data = array();
00040 
00046         var $store;
00047 
00055         var $loadedItems = array();
00056 
00061         var $loadedSubitems = array();
00062 
00068         var $initialisedLangs = array();
00069 
00075         var $shallowFallbacks = array();
00076 
00080         var $recachedLangs = array();
00081 
00085         static public $allKeys = array(
00086                 'fallback', 'namespaceNames', 'bookstoreList',
00087                 'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
00088                 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
00089                 'linkTrail', 'namespaceAliases',
00090                 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
00091                 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
00092                 'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
00093                 'digitGroupingPattern'
00094         );
00095 
00100         static public $mergeableMapKeys = array( 'messages', 'namespaceNames',
00101                 'dateFormats', 'imageFiles', 'preloadedMessages',
00102         );
00103 
00107         static public $mergeableListKeys = array( 'extraUserToggles' );
00108 
00113         static public $mergeableAliasListKeys = array( 'specialPageAliases' );
00114 
00120         static public $optionalMergeKeys = array( 'bookstoreList' );
00121 
00125         static public $magicWordKeys = array( 'magicWords' );
00126 
00130         static public $splitKeys = array( 'messages' );
00131 
00135         static public $preloadedKeys = array( 'dateFormats', 'namespaceNames' );
00136 
00137         var $mergeableKeys = null;
00138 
00146         function __construct( $conf ) {
00147                 global $wgCacheDirectory;
00148 
00149                 $this->conf = $conf;
00150                 $storeConf = array();
00151                 if ( !empty( $conf['storeClass'] ) ) {
00152                         $storeClass = $conf['storeClass'];
00153                 } else {
00154                         switch ( $conf['store'] ) {
00155                                 case 'files':
00156                                 case 'file':
00157                                         $storeClass = 'LCStore_CDB';
00158                                         break;
00159                                 case 'db':
00160                                         $storeClass = 'LCStore_DB';
00161                                         break;
00162                                 case 'accel':
00163                                         $storeClass = 'LCStore_Accel';
00164                                         break;
00165                                 case 'detect':
00166                                         $storeClass = $wgCacheDirectory ? 'LCStore_CDB' : 'LCStore_DB';
00167                                         break;
00168                                 default:
00169                                         throw new MWException(
00170                                                 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.' );
00171                         }
00172                 }
00173 
00174                 wfDebug( get_class( $this ) . ": using store $storeClass\n" );
00175                 if ( !empty( $conf['storeDirectory'] ) ) {
00176                         $storeConf['directory'] = $conf['storeDirectory'];
00177                 }
00178 
00179                 $this->store = new $storeClass( $storeConf );
00180                 foreach ( array( 'manualRecache', 'forceRecache' ) as $var ) {
00181                         if ( isset( $conf[$var] ) ) {
00182                                 $this->$var = $conf[$var];
00183                         }
00184                 }
00185         }
00186 
00193         public function isMergeableKey( $key ) {
00194                 if ( $this->mergeableKeys === null ) {
00195                         $this->mergeableKeys = array_flip( array_merge(
00196                                 self::$mergeableMapKeys,
00197                                 self::$mergeableListKeys,
00198                                 self::$mergeableAliasListKeys,
00199                                 self::$optionalMergeKeys,
00200                                 self::$magicWordKeys
00201                         ) );
00202                 }
00203                 return isset( $this->mergeableKeys[$key] );
00204         }
00205 
00215         public function getItem( $code, $key ) {
00216                 if ( !isset( $this->loadedItems[$code][$key] ) ) {
00217                         wfProfileIn( __METHOD__.'-load' );
00218                         $this->loadItem( $code, $key );
00219                         wfProfileOut( __METHOD__.'-load' );
00220                 }
00221 
00222                 if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
00223                         return $this->shallowFallbacks[$code];
00224                 }
00225 
00226                 return $this->data[$code][$key];
00227         }
00228 
00236         public function getSubitem( $code, $key, $subkey ) {
00237                 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
00238                          !isset( $this->loadedItems[$code][$key] ) ) {
00239                         wfProfileIn( __METHOD__.'-load' );
00240                         $this->loadSubitem( $code, $key, $subkey );
00241                         wfProfileOut( __METHOD__.'-load' );
00242                 }
00243 
00244                 if ( isset( $this->data[$code][$key][$subkey] ) ) {
00245                         return $this->data[$code][$key][$subkey];
00246                 } else {
00247                         return null;
00248                 }
00249         }
00250 
00263         public function getSubitemList( $code, $key ) {
00264                 if ( in_array( $key, self::$splitKeys ) ) {
00265                         return $this->getSubitem( $code, 'list', $key );
00266                 } else {
00267                         $item = $this->getItem( $code, $key );
00268                         if ( is_array( $item ) ) {
00269                                 return array_keys( $item );
00270                         } else {
00271                                 return false;
00272                         }
00273                 }
00274         }
00275 
00281         protected function loadItem( $code, $key ) {
00282                 if ( !isset( $this->initialisedLangs[$code] ) ) {
00283                         $this->initLanguage( $code );
00284                 }
00285 
00286                 // Check to see if initLanguage() loaded it for us
00287                 if ( isset( $this->loadedItems[$code][$key] ) ) {
00288                         return;
00289                 }
00290 
00291                 if ( isset( $this->shallowFallbacks[$code] ) ) {
00292                         $this->loadItem( $this->shallowFallbacks[$code], $key );
00293                         return;
00294                 }
00295 
00296                 if ( in_array( $key, self::$splitKeys ) ) {
00297                         $subkeyList = $this->getSubitem( $code, 'list', $key );
00298                         foreach ( $subkeyList as $subkey ) {
00299                                 if ( isset( $this->data[$code][$key][$subkey] ) ) {
00300                                         continue;
00301                                 }
00302                                 $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
00303                         }
00304                 } else {
00305                         $this->data[$code][$key] = $this->store->get( $code, $key );
00306                 }
00307 
00308                 $this->loadedItems[$code][$key] = true;
00309         }
00310 
00318         protected function loadSubitem( $code, $key, $subkey ) {
00319                 if ( !in_array( $key, self::$splitKeys ) ) {
00320                         $this->loadItem( $code, $key );
00321                         return;
00322                 }
00323 
00324                 if ( !isset( $this->initialisedLangs[$code] ) ) {
00325                         $this->initLanguage( $code );
00326                 }
00327 
00328                 // Check to see if initLanguage() loaded it for us
00329                 if ( isset( $this->loadedItems[$code][$key] ) ||
00330                          isset( $this->loadedSubitems[$code][$key][$subkey] ) ) {
00331                         return;
00332                 }
00333 
00334                 if ( isset( $this->shallowFallbacks[$code] ) ) {
00335                         $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
00336                         return;
00337                 }
00338 
00339                 $value = $this->store->get( $code, "$key:$subkey" );
00340                 $this->data[$code][$key][$subkey] = $value;
00341                 $this->loadedSubitems[$code][$key][$subkey] = true;
00342         }
00343 
00347         public function isExpired( $code ) {
00348                 if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
00349                         wfDebug( __METHOD__."($code): forced reload\n" );
00350                         return true;
00351                 }
00352 
00353                 $deps = $this->store->get( $code, 'deps' );
00354                 $keys = $this->store->get( $code, 'list', 'messages' );
00355                 $preload = $this->store->get( $code, 'preload' );
00356                 // Different keys may expire separately, at least in LCStore_Accel
00357                 if ( $deps === null || $keys === null || $preload === null ) {
00358                         wfDebug( __METHOD__."($code): cache missing, need to make one\n" );
00359                         return true;
00360                 }
00361 
00362                 foreach ( $deps as $dep ) {
00363                         // Because we're unserializing stuff from cache, we
00364                         // could receive objects of classes that don't exist
00365                         // anymore (e.g. uninstalled extensions)
00366                         // When this happens, always expire the cache
00367                         if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
00368                                 wfDebug( __METHOD__."($code): cache for $code expired due to " .
00369                                         get_class( $dep ) . "\n" );
00370                                 return true;
00371                         }
00372                 }
00373 
00374                 return false;
00375         }
00376 
00381         protected function initLanguage( $code ) {
00382                 if ( isset( $this->initialisedLangs[$code] ) ) {
00383                         return;
00384                 }
00385 
00386                 $this->initialisedLangs[$code] = true;
00387 
00388                 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
00389                 if ( !Language::isValidBuiltInCode( $code ) ) {
00390                         $this->initShallowFallback( $code, 'en' );
00391                         return;
00392                 }
00393 
00394                 # Recache the data if necessary
00395                 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
00396                         if ( file_exists( Language::getMessagesFileName( $code ) ) ) {
00397                                 $this->recache( $code );
00398                         } elseif ( $code === 'en' ) {
00399                                 throw new MWException( 'MessagesEn.php is missing.' );
00400                         } else {
00401                                 $this->initShallowFallback( $code, 'en' );
00402                         }
00403                         return;
00404                 }
00405 
00406                 # Preload some stuff
00407                 $preload = $this->getItem( $code, 'preload' );
00408                 if ( $preload === null ) {
00409                         if ( $this->manualRecache ) {
00410                                 // No Messages*.php file. Do shallow fallback to en.
00411                                 if ( $code === 'en' ) {
00412                                         throw new MWException( 'No localisation cache found for English. ' .
00413                                                 'Please run maintenance/rebuildLocalisationCache.php.' );
00414                                 }
00415                                 $this->initShallowFallback( $code, 'en' );
00416                                 return;
00417                         } else {
00418                                 throw new MWException( 'Invalid or missing localisation cache.' );
00419                         }
00420                 }
00421                 $this->data[$code] = $preload;
00422                 foreach ( $preload as $key => $item ) {
00423                         if ( in_array( $key, self::$splitKeys ) ) {
00424                                 foreach ( $item as $subkey => $subitem ) {
00425                                         $this->loadedSubitems[$code][$key][$subkey] = true;
00426                                 }
00427                         } else {
00428                                 $this->loadedItems[$code][$key] = true;
00429                         }
00430                 }
00431         }
00432 
00439         public function initShallowFallback( $primaryCode, $fallbackCode ) {
00440                 $this->data[$primaryCode] =& $this->data[$fallbackCode];
00441                 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
00442                 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
00443                 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
00444         }
00445 
00452         protected function readPHPFile( $_fileName, $_fileType ) {
00453                 // Disable APC caching
00454                 $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
00455                 include( $_fileName );
00456                 ini_set( 'apc.cache_by_default', $_apcEnabled );
00457 
00458                 if ( $_fileType == 'core' || $_fileType == 'extension' ) {
00459                         $data = compact( self::$allKeys );
00460                 } elseif ( $_fileType == 'aliases' ) {
00461                         $data = compact( 'aliases' );
00462                 } else {
00463                         throw new MWException( __METHOD__.": Invalid file type: $_fileType" );
00464                 }
00465 
00466                 return $data;
00467         }
00468 
00476         protected function mergeItem( $key, &$value, $fallbackValue ) {
00477                 if ( !is_null( $value ) ) {
00478                         if ( !is_null( $fallbackValue ) ) {
00479                                 if ( in_array( $key, self::$mergeableMapKeys ) ) {
00480                                         $value = $value + $fallbackValue;
00481                                 } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
00482                                         $value = array_unique( array_merge( $fallbackValue, $value ) );
00483                                 } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
00484                                         $value = array_merge_recursive( $value, $fallbackValue );
00485                                 } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
00486                                         if ( !empty( $value['inherit'] ) )  {
00487                                                 $value = array_merge( $fallbackValue, $value );
00488                                         }
00489 
00490                                         if ( isset( $value['inherit'] ) ) {
00491                                                 unset( $value['inherit'] );
00492                                         }
00493                                 } elseif ( in_array( $key, self::$magicWordKeys ) ) {
00494                                         $this->mergeMagicWords( $value, $fallbackValue );
00495                                 }
00496                         }
00497                 } else {
00498                         $value = $fallbackValue;
00499                 }
00500         }
00501 
00506         protected function mergeMagicWords( &$value, $fallbackValue ) {
00507                 foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
00508                         if ( !isset( $value[$magicName] ) ) {
00509                                 $value[$magicName] = $fallbackInfo;
00510                         } else {
00511                                 $oldSynonyms = array_slice( $fallbackInfo, 1 );
00512                                 $newSynonyms = array_slice( $value[$magicName], 1 );
00513                                 $synonyms = array_values( array_unique( array_merge(
00514                                         $newSynonyms, $oldSynonyms ) ) );
00515                                 $value[$magicName] = array_merge( array( $fallbackInfo[0] ), $synonyms );
00516                         }
00517                 }
00518         }
00519 
00533         protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
00534                 $used = false;
00535                 foreach ( $codeSequence as $code ) {
00536                         if ( isset( $fallbackValue[$code] ) ) {
00537                                 $this->mergeItem( $key, $value, $fallbackValue[$code] );
00538                                 $used = true;
00539                         }
00540                 }
00541 
00542                 return $used;
00543         }
00544 
00550         public function recache( $code ) {
00551                 global $wgExtensionMessagesFiles;
00552                 wfProfileIn( __METHOD__ );
00553 
00554                 if ( !$code ) {
00555                         throw new MWException( "Invalid language code requested" );
00556                 }
00557                 $this->recachedLangs[$code] = true;
00558 
00559                 # Initial values
00560                 $initialData = array_combine(
00561                         self::$allKeys,
00562                         array_fill( 0, count( self::$allKeys ), null ) );
00563                 $coreData = $initialData;
00564                 $deps = array();
00565 
00566                 # Load the primary localisation from the source file
00567                 $fileName = Language::getMessagesFileName( $code );
00568                 if ( !file_exists( $fileName ) ) {
00569                         wfDebug( __METHOD__.": no localisation file for $code, using fallback to en\n" );
00570                         $coreData['fallback'] = 'en';
00571                 } else {
00572                         $deps[] = new FileDependency( $fileName );
00573                         $data = $this->readPHPFile( $fileName, 'core' );
00574                         wfDebug( __METHOD__.": got localisation for $code from source\n" );
00575 
00576                         # Merge primary localisation
00577                         foreach ( $data as $key => $value ) {
00578                                 $this->mergeItem( $key, $coreData[$key], $value );
00579                         }
00580 
00581                 }
00582 
00583                 # Fill in the fallback if it's not there already
00584                 if ( is_null( $coreData['fallback'] ) ) {
00585                         $coreData['fallback'] = $code === 'en' ? false : 'en';
00586                 }
00587 
00588                 if ( $coreData['fallback'] === false ) {
00589                         $coreData['fallbackSequence'] = array();
00590                 } else {
00591                         $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
00592                         $len = count( $coreData['fallbackSequence'] );
00593 
00594                         # Ensure that the sequence ends at en
00595                         if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
00596                                 $coreData['fallbackSequence'][] = 'en';
00597                         }
00598 
00599                         # Load the fallback localisation item by item and merge it
00600                         foreach ( $coreData['fallbackSequence'] as $fbCode ) {
00601                                 # Load the secondary localisation from the source file to
00602                                 # avoid infinite cycles on cyclic fallbacks
00603                                 $fbFilename = Language::getMessagesFileName( $fbCode );
00604 
00605                                 if ( !file_exists( $fbFilename ) ) {
00606                                         continue;
00607                                 }
00608 
00609                                 $deps[] = new FileDependency( $fbFilename );
00610                                 $fbData = $this->readPHPFile( $fbFilename, 'core' );
00611 
00612                                 foreach ( self::$allKeys as $key ) {
00613                                         if ( !isset( $fbData[$key] ) ) {
00614                                                 continue;
00615                                         }
00616 
00617                                         if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
00618                                                 $this->mergeItem( $key, $coreData[$key], $fbData[$key] );
00619                                         }
00620                                 }
00621                         }
00622                 }
00623 
00624                 $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] );
00625 
00626                 # Load the extension localisations
00627                 # This is done after the core because we know the fallback sequence now.
00628                 # But it has a higher precedence for merging so that we can support things
00629                 # like site-specific message overrides.
00630                 $allData = $initialData;
00631                 foreach ( $wgExtensionMessagesFiles as $fileName ) {
00632                         $data = $this->readPHPFile( $fileName, 'extension' );
00633                         $used = false;
00634 
00635                         foreach ( $data as $key => $item ) {
00636                                 if( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
00637                                         $used = true;
00638                                 }
00639                         }
00640 
00641                         if ( $used ) {
00642                                 $deps[] = new FileDependency( $fileName );
00643                         }
00644                 }
00645 
00646                 # Merge core data into extension data
00647                 foreach ( $coreData as $key => $item ) {
00648                         $this->mergeItem( $key, $allData[$key], $item );
00649                 }
00650 
00651                 # Add cache dependencies for any referenced globals
00652                 $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
00653                 $deps['version'] = new ConstantDependency( 'MW_LC_VERSION' );
00654 
00655                 # Add dependencies to the cache entry
00656                 $allData['deps'] = $deps;
00657 
00658                 # Replace spaces with underscores in namespace names
00659                 $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
00660 
00661                 # And do the same for special page aliases. $page is an array.
00662                 foreach ( $allData['specialPageAliases'] as &$page ) {
00663                         $page = str_replace( ' ', '_', $page );
00664                 }
00665                 # Decouple the reference to prevent accidental damage
00666                 unset($page);
00667 
00668                 # Set the list keys
00669                 $allData['list'] = array();
00670                 foreach ( self::$splitKeys as $key ) {
00671                         $allData['list'][$key] = array_keys( $allData[$key] );
00672                 }
00673 
00674                 # Run hooks
00675                 wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) );
00676 
00677                 if ( is_null( $allData['namespaceNames'] ) ) {
00678                         throw new MWException( __METHOD__.': Localisation data failed sanity check! ' .
00679                                 'Check that your languages/messages/MessagesEn.php file is intact.' );
00680                 }
00681 
00682                 # Set the preload key
00683                 $allData['preload'] = $this->buildPreload( $allData );
00684 
00685                 # Save to the process cache and register the items loaded
00686                 $this->data[$code] = $allData;
00687                 foreach ( $allData as $key => $item ) {
00688                         $this->loadedItems[$code][$key] = true;
00689                 }
00690 
00691                 # Save to the persistent cache
00692                 $this->store->startWrite( $code );
00693                 foreach ( $allData as $key => $value ) {
00694                         if ( in_array( $key, self::$splitKeys ) ) {
00695                                 foreach ( $value as $subkey => $subvalue ) {
00696                                         $this->store->set( "$key:$subkey", $subvalue );
00697                                 }
00698                         } else {
00699                                 $this->store->set( $key, $value );
00700                         }
00701                 }
00702                 $this->store->finishWrite();
00703 
00704                 # Clear out the MessageBlobStore
00705                 # HACK: If using a null (i.e. disabled) storage backend, we
00706                 # can't write to the MessageBlobStore either
00707                 if ( !$this->store instanceof LCStore_Null ) {
00708                         MessageBlobStore::clear();
00709                 }
00710 
00711                 wfProfileOut( __METHOD__ );
00712         }
00713 
00722         protected function buildPreload( $data ) {
00723                 $preload = array( 'messages' => array() );
00724                 foreach ( self::$preloadedKeys as $key ) {
00725                         $preload[$key] = $data[$key];
00726                 }
00727 
00728                 foreach ( $data['preloadedMessages'] as $subkey ) {
00729                         if ( isset( $data['messages'][$subkey] ) ) {
00730                                 $subitem = $data['messages'][$subkey];
00731                         } else {
00732                                 $subitem = null;
00733                         }
00734                         $preload['messages'][$subkey] = $subitem;
00735                 }
00736 
00737                 return $preload;
00738         }
00739 
00745         public function unload( $code ) {
00746                 unset( $this->data[$code] );
00747                 unset( $this->loadedItems[$code] );
00748                 unset( $this->loadedSubitems[$code] );
00749                 unset( $this->initialisedLangs[$code] );
00750 
00751                 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
00752                         if ( $fbCode === $code ) {
00753                                 $this->unload( $shallowCode );
00754                         }
00755                 }
00756         }
00757 
00761         public function unloadAll() {
00762                 foreach ( $this->initialisedLangs as $lang => $unused ) {
00763                         $this->unload( $lang );
00764                 }
00765         }
00766 
00770         public function disableBackend() {
00771                 $this->store = new LCStore_Null;
00772                 $this->manualRecache = false;
00773         }
00774 }
00775 
00793 interface LCStore {
00799         function get( $code, $key );
00800 
00805         function startWrite( $code );
00806 
00810         function finishWrite();
00811 
00818         function set( $key, $value );
00819 }
00820 
00826 class LCStore_Accel implements LCStore {
00827         var $currentLang;
00828         var $keys;
00829 
00830         public function __construct() {
00831                 $this->cache = wfGetCache( CACHE_ACCEL );
00832         }
00833 
00834         public function get( $code, $key ) {
00835                 $k = wfMemcKey( 'l10n', $code, 'k', $key );
00836                 $r = $this->cache->get( $k );
00837                 return $r === false ? null : $r;
00838         }
00839 
00840         public function startWrite( $code ) {
00841                 $k = wfMemcKey( 'l10n', $code, 'l' );
00842                 $keys = $this->cache->get( $k );
00843                 if ( $keys ) {
00844                         foreach ( $keys as $k ) {
00845                                 $this->cache->delete( $k );
00846                         }
00847                 }
00848                 $this->currentLang = $code;
00849                 $this->keys = array();
00850         }
00851 
00852         public function finishWrite() {
00853                 if ( $this->currentLang ) {
00854                         $k = wfMemcKey( 'l10n', $this->currentLang, 'l' );
00855                         $this->cache->set( $k, array_keys( $this->keys ) );
00856                 }
00857                 $this->currentLang = null;
00858                 $this->keys = array();
00859         }
00860 
00861         public function set( $key, $value ) {
00862                 if ( $this->currentLang ) {
00863                         $k = wfMemcKey( 'l10n', $this->currentLang, 'k', $key );
00864                         $this->keys[$k] = true;
00865                         $this->cache->set( $k, $value );
00866                 }
00867         }
00868 }
00869 
00874 class LCStore_DB implements LCStore {
00875         var $currentLang;
00876         var $writesDone = false;
00877 
00881         var $dbw;
00882         var $batch;
00883         var $readOnly = false;
00884 
00885         public function get( $code, $key ) {
00886                 if ( $this->writesDone ) {
00887                         $db = wfGetDB( DB_MASTER );
00888                 } else {
00889                         $db = wfGetDB( DB_SLAVE );
00890                 }
00891                 $row = $db->selectRow( 'l10n_cache', array( 'lc_value' ),
00892                         array( 'lc_lang' => $code, 'lc_key' => $key ), __METHOD__ );
00893                 if ( $row ) {
00894                         return unserialize( $row->lc_value );
00895                 } else {
00896                         return null;
00897                 }
00898         }
00899 
00900         public function startWrite( $code ) {
00901                 if ( $this->readOnly ) {
00902                         return;
00903                 }
00904 
00905                 if ( !$code ) {
00906                         throw new MWException( __METHOD__.": Invalid language \"$code\"" );
00907                 }
00908 
00909                 $this->dbw = wfGetDB( DB_MASTER );
00910                 try {
00911                         $this->dbw->begin();
00912                         $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ );
00913                 } catch ( DBQueryError $e ) {
00914                         if ( $this->dbw->wasReadOnlyError() ) {
00915                                 $this->readOnly = true;
00916                                 $this->dbw->rollback();
00917                                 $this->dbw->ignoreErrors( false );
00918                                 return;
00919                         } else {
00920                                 throw $e;
00921                         }
00922                 }
00923 
00924                 $this->currentLang = $code;
00925                 $this->batch = array();
00926         }
00927 
00928         public function finishWrite() {
00929                 if ( $this->readOnly ) {
00930                         return;
00931                 }
00932 
00933                 if ( $this->batch ) {
00934                         $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
00935                 }
00936 
00937                 $this->dbw->commit();
00938                 $this->currentLang = null;
00939                 $this->dbw = null;
00940                 $this->batch = array();
00941                 $this->writesDone = true;
00942         }
00943 
00944         public function set( $key, $value ) {
00945                 if ( $this->readOnly ) {
00946                         return;
00947                 }
00948 
00949                 if ( is_null( $this->currentLang ) ) {
00950                         throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
00951                 }
00952 
00953                 $this->batch[] = array(
00954                         'lc_lang' => $this->currentLang,
00955                         'lc_key' => $key,
00956                         'lc_value' => serialize( $value ) );
00957 
00958                 if ( count( $this->batch ) >= 100 ) {
00959                         $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
00960                         $this->batch = array();
00961                 }
00962         }
00963 }
00964 
00977 class LCStore_CDB implements LCStore {
00978         var $readers, $writer, $currentLang, $directory;
00979 
00980         function __construct( $conf = array() ) {
00981                 global $wgCacheDirectory;
00982 
00983                 if ( isset( $conf['directory'] ) ) {
00984                         $this->directory = $conf['directory'];
00985                 } else {
00986                         $this->directory = $wgCacheDirectory;
00987                 }
00988         }
00989 
00990         public function get( $code, $key ) {
00991                 if ( !isset( $this->readers[$code] ) ) {
00992                         $fileName = $this->getFileName( $code );
00993 
00994                         if ( !file_exists( $fileName ) ) {
00995                                 $this->readers[$code] = false;
00996                         } else {
00997                                 $this->readers[$code] = CdbReader::open( $fileName );
00998                         }
00999                 }
01000 
01001                 if ( !$this->readers[$code] ) {
01002                         return null;
01003                 } else {
01004                         $value = $this->readers[$code]->get( $key );
01005 
01006                         if ( $value === false ) {
01007                                 return null;
01008                         }
01009                         return unserialize( $value );
01010                 }
01011         }
01012 
01013         public function startWrite( $code ) {
01014                 if ( !file_exists( $this->directory ) ) {
01015                         if ( !wfMkdirParents( $this->directory, null, __METHOD__ ) ) {
01016                                 throw new MWException( "Unable to create the localisation store " .
01017                                         "directory \"{$this->directory}\"" );
01018                         }
01019                 }
01020 
01021                 // Close reader to stop permission errors on write
01022                 if( !empty($this->readers[$code]) ) {
01023                         $this->readers[$code]->close();
01024                 }
01025 
01026                 $this->writer = CdbWriter::open( $this->getFileName( $code ) );
01027                 $this->currentLang = $code;
01028         }
01029 
01030         public function finishWrite() {
01031                 // Close the writer
01032                 $this->writer->close();
01033                 $this->writer = null;
01034                 unset( $this->readers[$this->currentLang] );
01035                 $this->currentLang = null;
01036         }
01037 
01038         public function set( $key, $value ) {
01039                 if ( is_null( $this->writer ) ) {
01040                         throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
01041                 }
01042                 $this->writer->set( $key, serialize( $value ) );
01043         }
01044 
01045         protected function getFileName( $code ) {
01046                 if ( !$code || strpos( $code, '/' ) !== false ) {
01047                         throw new MWException( __METHOD__.": Invalid language \"$code\"" );
01048                 }
01049                 return "{$this->directory}/l10n_cache-$code.cdb";
01050         }
01051 }
01052 
01056 class LCStore_Null implements LCStore {
01057         public function get( $code, $key ) {
01058                 return null;
01059         }
01060 
01061         public function startWrite( $code ) {}
01062         public function finishWrite() {}
01063         public function set( $key, $value ) {}
01064 }
01065 
01070 class LocalisationCache_BulkLoad extends LocalisationCache {
01075         var $fileCache = array();
01076 
01082         var $mruLangs = array();
01083 
01087         var $maxLoadedLangs = 10;
01088 
01094         protected function readPHPFile( $fileName, $fileType ) {
01095                 $serialize = $fileType === 'core';
01096                 if ( !isset( $this->fileCache[$fileName][$fileType] ) ) {
01097                         $data = parent::readPHPFile( $fileName, $fileType );
01098 
01099                         if ( $serialize ) {
01100                                 $encData = serialize( $data );
01101                         } else {
01102                                 $encData = $data;
01103                         }
01104 
01105                         $this->fileCache[$fileName][$fileType] = $encData;
01106 
01107                         return $data;
01108                 } elseif ( $serialize ) {
01109                         return unserialize( $this->fileCache[$fileName][$fileType] );
01110                 } else {
01111                         return $this->fileCache[$fileName][$fileType];
01112                 }
01113         }
01114 
01120         public function getItem( $code, $key ) {
01121                 unset( $this->mruLangs[$code] );
01122                 $this->mruLangs[$code] = true;
01123                 return parent::getItem( $code, $key );
01124         }
01125 
01132         public function getSubitem( $code, $key, $subkey ) {
01133                 unset( $this->mruLangs[$code] );
01134                 $this->mruLangs[$code] = true;
01135                 return parent::getSubitem( $code, $key, $subkey );
01136         }
01137 
01141         public function recache( $code ) {
01142                 parent::recache( $code );
01143                 unset( $this->mruLangs[$code] );
01144                 $this->mruLangs[$code] = true;
01145                 $this->trimCache();
01146         }
01147 
01151         public function unload( $code ) {
01152                 unset( $this->mruLangs[$code] );
01153                 parent::unload( $code );
01154         }
01155 
01159         protected function trimCache() {
01160                 while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) {
01161                         reset( $this->mruLangs );
01162                         $code = key( $this->mruLangs );
01163                         wfDebug( __METHOD__.": unloading $code\n" );
01164                         $this->unload( $code );
01165                 }
01166         }
01167 }