00001 <?php
00002
00003 define( 'MW_LC_VERSION', 1 );
00004
00017 class LocalisationCache {
00019 var $conf;
00020
00026 var $manualRecache = false;
00027
00031 var $forceRecache = false;
00032
00039 var $data = array();
00040
00044 var $store;
00045
00053 var $loadedItems = array();
00054
00059 var $loadedSubitems = array();
00060
00066 var $initialisedLangs = array();
00067
00073 var $shallowFallbacks = array();
00074
00078 var $recachedLangs = array();
00079
00084 var $legacyData = array();
00085
00089 static public $allKeys = array(
00090 'fallback', 'namespaceNames', 'mathNames', 'bookstoreList',
00091 'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
00092 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
00093 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
00094 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
00095 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
00096 'imageFiles', 'preloadedMessages',
00097 );
00098
00103 static public $mergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
00104 'dateFormats', 'defaultUserOptionOverrides', 'magicWords', 'imageFiles',
00105 'preloadedMessages',
00106 );
00107
00111 static public $mergeableListKeys = array( 'extraUserToggles' );
00112
00117 static public $mergeableAliasListKeys = array( 'specialPageAliases' );
00118
00124 static public $optionalMergeKeys = array( 'bookstoreList' );
00125
00129 static public $splitKeys = array( 'messages' );
00130
00134 static public $preloadedKeys = array( 'dateFormats', 'namespaceNames',
00135 'defaultUserOptionOverrides' );
00136
00142 function __construct( $conf ) {
00143 global $wgCacheDirectory;
00144
00145 $this->conf = $conf;
00146 $storeConf = array();
00147 if ( !empty( $conf['storeClass'] ) ) {
00148 $storeClass = $conf['storeClass'];
00149 } else {
00150 switch ( $conf['store'] ) {
00151 case 'files':
00152 case 'file':
00153 $storeClass = 'LCStore_CDB';
00154 break;
00155 case 'db':
00156 $storeClass = 'LCStore_DB';
00157 break;
00158 case 'detect':
00159 $storeClass = $wgCacheDirectory ? 'LCStore_CDB' : 'LCStore_DB';
00160 break;
00161 default:
00162 throw new MWException(
00163 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.' );
00164 }
00165 }
00166
00167 wfDebug( get_class( $this ) . ": using store $storeClass\n" );
00168 if ( !empty( $conf['storeDirectory'] ) ) {
00169 $storeConf['directory'] = $conf['storeDirectory'];
00170 }
00171
00172 $this->store = new $storeClass( $storeConf );
00173 foreach ( array( 'manualRecache', 'forceRecache' ) as $var ) {
00174 if ( isset( $conf[$var] ) ) {
00175 $this->$var = $conf[$var];
00176 }
00177 }
00178 }
00179
00184 public function isMergeableKey( $key ) {
00185 if ( !isset( $this->mergeableKeys ) ) {
00186 $this->mergeableKeys = array_flip( array_merge(
00187 self::$mergeableMapKeys,
00188 self::$mergeableListKeys,
00189 self::$mergeableAliasListKeys,
00190 self::$optionalMergeKeys
00191 ) );
00192 }
00193 return isset( $this->mergeableKeys[$key] );
00194 }
00195
00202 public function getItem( $code, $key ) {
00203 if ( !isset( $this->loadedItems[$code][$key] ) ) {
00204 wfProfileIn( __METHOD__.'-load' );
00205 $this->loadItem( $code, $key );
00206 wfProfileOut( __METHOD__.'-load' );
00207 }
00208 if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
00209 return $this->shallowFallbacks[$code];
00210 }
00211 return $this->data[$code][$key];
00212 }
00213
00217 public function getSubitem( $code, $key, $subkey ) {
00218 if ( isset( $this->legacyData[$code][$key][$subkey] ) ) {
00219 return $this->legacyData[$code][$key][$subkey];
00220 }
00221 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] )
00222 && !isset( $this->loadedItems[$code][$key] ) )
00223 {
00224 wfProfileIn( __METHOD__.'-load' );
00225 $this->loadSubitem( $code, $key, $subkey );
00226 wfProfileOut( __METHOD__.'-load' );
00227 }
00228 if ( isset( $this->data[$code][$key][$subkey] ) ) {
00229 return $this->data[$code][$key][$subkey];
00230 } else {
00231 return null;
00232 }
00233 }
00234
00244 public function getSubitemList( $code, $key ) {
00245 if ( in_array( $key, self::$splitKeys ) ) {
00246 return $this->getSubitem( $code, 'list', $key );
00247 } else {
00248 $item = $this->getItem( $code, $key );
00249 if ( is_array( $item ) ) {
00250 return array_keys( $item );
00251 } else {
00252 return false;
00253 }
00254 }
00255 }
00256
00260 protected function loadItem( $code, $key ) {
00261 if ( !isset( $this->initialisedLangs[$code] ) ) {
00262 $this->initLanguage( $code );
00263 }
00264
00265 if ( isset( $this->loadedItems[$code][$key] ) ) {
00266 return;
00267 }
00268 if ( isset( $this->shallowFallbacks[$code] ) ) {
00269 $this->loadItem( $this->shallowFallbacks[$code], $key );
00270 return;
00271 }
00272 if ( in_array( $key, self::$splitKeys ) ) {
00273 $subkeyList = $this->getSubitem( $code, 'list', $key );
00274 foreach ( $subkeyList as $subkey ) {
00275 if ( isset( $this->data[$code][$key][$subkey] ) ) {
00276 continue;
00277 }
00278 $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
00279 }
00280 } else {
00281 $this->data[$code][$key] = $this->store->get( $code, $key );
00282 }
00283 $this->loadedItems[$code][$key] = true;
00284 }
00285
00289 protected function loadSubitem( $code, $key, $subkey ) {
00290 if ( !in_array( $key, self::$splitKeys ) ) {
00291 $this->loadItem( $code, $key );
00292 return;
00293 }
00294 if ( !isset( $this->initialisedLangs[$code] ) ) {
00295 $this->initLanguage( $code );
00296 }
00297
00298 if ( isset( $this->loadedItems[$code][$key] )
00299 || isset( $this->loadedSubitems[$code][$key][$subkey] ) )
00300 {
00301 return;
00302 }
00303 if ( isset( $this->shallowFallbacks[$code] ) ) {
00304 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
00305 return;
00306 }
00307 $value = $this->store->get( $code, "$key:$subkey" );
00308 $this->data[$code][$key][$subkey] = $value;
00309 $this->loadedSubitems[$code][$key][$subkey] = true;
00310 }
00311
00315 public function isExpired( $code ) {
00316 if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
00317 wfDebug( __METHOD__."($code): forced reload\n" );
00318 return true;
00319 }
00320
00321 $deps = $this->store->get( $code, 'deps' );
00322 if ( $deps === null ) {
00323 wfDebug( __METHOD__."($code): cache missing, need to make one\n" );
00324 return true;
00325 }
00326 foreach ( $deps as $dep ) {
00327
00328
00329
00330
00331 if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
00332 wfDebug( __METHOD__."($code): cache for $code expired due to " .
00333 get_class( $dep ) . "\n" );
00334 return true;
00335 }
00336 }
00337 return false;
00338 }
00339
00343 protected function initLanguage( $code ) {
00344 if ( isset( $this->initialisedLangs[$code] ) ) {
00345 return;
00346 }
00347 $this->initialisedLangs[$code] = true;
00348
00349 # Recache the data if necessary
00350 if ( !$this->manualRecache && $this->isExpired( $code ) ) {
00351 if ( file_exists( Language::getMessagesFileName( $code ) ) ) {
00352 $this->recache( $code );
00353 } elseif ( $code === 'en' ) {
00354 throw new MWException( 'MessagesEn.php is missing.' );
00355 } else {
00356 $this->initShallowFallback( $code, 'en' );
00357 }
00358 return;
00359 }
00360
00361 # Preload some stuff
00362 $preload = $this->getItem( $code, 'preload' );
00363 if ( $preload === null ) {
00364 if ( $this->manualRecache ) {
00365
00366 if ( $code === 'en' ) {
00367 throw new MWException( 'No localisation cache found for English. ' .
00368 'Please run maintenance/rebuildLocalisationCache.php.' );
00369 }
00370 $this->initShallowFallback( $code, 'en' );
00371 return;
00372 } else {
00373 throw new MWException( 'Invalid or missing localisation cache.' );
00374 }
00375 }
00376 $this->data[$code] = $preload;
00377 foreach ( $preload as $key => $item ) {
00378 if ( in_array( $key, self::$splitKeys ) ) {
00379 foreach ( $item as $subkey => $subitem ) {
00380 $this->loadedSubitems[$code][$key][$subkey] = true;
00381 }
00382 } else {
00383 $this->loadedItems[$code][$key] = true;
00384 }
00385 }
00386 }
00387
00392 public function initShallowFallback( $primaryCode, $fallbackCode ) {
00393 $this->data[$primaryCode] =& $this->data[$fallbackCode];
00394 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
00395 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
00396 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
00397 }
00398
00402 protected function readPHPFile( $_fileName, $_fileType ) {
00403
00404 $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
00405 include( $_fileName );
00406 ini_set( 'apc.cache_by_default', $_apcEnabled );
00407
00408 if ( $_fileType == 'core' || $_fileType == 'extension' ) {
00409 $data = compact( self::$allKeys );
00410 } elseif ( $_fileType == 'aliases' ) {
00411 $data = compact( 'aliases' );
00412 } else {
00413 throw new MWException( __METHOD__.": Invalid file type: $_fileType" );
00414 }
00415 return $data;
00416 }
00417
00422 protected function mergeItem( $key, &$value, $fallbackValue ) {
00423 if ( !is_null( $value ) ) {
00424 if ( !is_null( $fallbackValue ) ) {
00425 if ( in_array( $key, self::$mergeableMapKeys ) ) {
00426 $value = $value + $fallbackValue;
00427 } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
00428 $value = array_unique( array_merge( $fallbackValue, $value ) );
00429 } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
00430 $value = array_merge_recursive( $value, $fallbackValue );
00431 } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
00432 if ( !empty( $value['inherit'] ) ) {
00433 $value = array_merge( $fallbackValue, $value );
00434 }
00435 if ( isset( $value['inherit'] ) ) {
00436 unset( $value['inherit'] );
00437 }
00438 }
00439 }
00440 } else {
00441 $value = $fallbackValue;
00442 }
00443 }
00444
00453 protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
00454 $used = false;
00455 foreach ( $codeSequence as $code ) {
00456 if ( isset( $fallbackValue[$code] ) ) {
00457 $this->mergeItem( $key, $value, $fallbackValue[$code] );
00458 $used = true;
00459 }
00460 }
00461 return $used;
00462 }
00463
00468 public function recache( $code ) {
00469 static $recursionGuard = array();
00470 global $wgExtensionMessagesFiles, $wgExtensionAliasesFiles;
00471 wfProfileIn( __METHOD__ );
00472
00473 if ( !$code ) {
00474 throw new MWException( "Invalid language code requested" );
00475 }
00476 $this->recachedLangs[$code] = true;
00477
00478 # Initial values
00479 $initialData = array_combine(
00480 self::$allKeys,
00481 array_fill( 0, count( self::$allKeys ), null ) );
00482 $coreData = $initialData;
00483 $deps = array();
00484
00485 # Load the primary localisation from the source file
00486 $fileName = Language::getMessagesFileName( $code );
00487 if ( !file_exists( $fileName ) ) {
00488 wfDebug( __METHOD__.": no localisation file for $code, using fallback to en\n" );
00489 $coreData['fallback'] = 'en';
00490 } else {
00491 $deps[] = new FileDependency( $fileName );
00492 $data = $this->readPHPFile( $fileName, 'core' );
00493 wfDebug( __METHOD__.": got localisation for $code from source\n" );
00494
00495 # Merge primary localisation
00496 foreach ( $data as $key => $value ) {
00497 $this->mergeItem( $key, $coreData[$key], $value );
00498 }
00499 }
00500
00501 # Fill in the fallback if it's not there already
00502 if ( is_null( $coreData['fallback'] ) ) {
00503 $coreData['fallback'] = $code === 'en' ? false : 'en';
00504 }
00505
00506 if ( $coreData['fallback'] !== false ) {
00507 # Guard against circular references
00508 if ( isset( $recursionGuard[$code] ) ) {
00509 throw new MWException( "Error: Circular fallback reference in language code $code" );
00510 }
00511 $recursionGuard[$code] = true;
00512
00513 # Load the fallback localisation item by item and merge it
00514 $deps = array_merge( $deps, $this->getItem( $coreData['fallback'], 'deps' ) );
00515 foreach ( self::$allKeys as $key ) {
00516 if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
00517 $fallbackValue = $this->getItem( $coreData['fallback'], $key );
00518 $this->mergeItem( $key, $coreData[$key], $fallbackValue );
00519 }
00520 }
00521 $fallbackSequence = $this->getItem( $coreData['fallback'], 'fallbackSequence' );
00522 array_unshift( $fallbackSequence, $coreData['fallback'] );
00523 $coreData['fallbackSequence'] = $fallbackSequence;
00524 unset( $recursionGuard[$code] );
00525 } else {
00526 $coreData['fallbackSequence'] = array();
00527 }
00528 $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] );
00529
00530 # Load the extension localisations
00531 # This is done after the core because we know the fallback sequence now.
00532 # But it has a higher precedence for merging so that we can support things
00533 # like site-specific message overrides.
00534 $allData = $initialData;
00535 foreach ( $wgExtensionMessagesFiles as $fileName ) {
00536 $data = $this->readPHPFile( $fileName, 'extension' );
00537 $used = false;
00538 foreach ( $data as $key => $item ) {
00539 if( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
00540 $used = true;
00541 }
00542 }
00543 if ( $used ) {
00544 $deps[] = new FileDependency( $fileName );
00545 }
00546 }
00547
00548 # Load deprecated $wgExtensionAliasesFiles
00549 foreach ( $wgExtensionAliasesFiles as $fileName ) {
00550 $data = $this->readPHPFile( $fileName, 'aliases' );
00551 if ( !isset( $data['aliases'] ) ) {
00552 continue;
00553 }
00554 $used = $this->mergeExtensionItem( $codeSequence, 'specialPageAliases',
00555 $allData['specialPageAliases'], $data['aliases'] );
00556 if ( $used ) {
00557 $deps[] = new FileDependency( $fileName );
00558 }
00559 }
00560
00561 # Merge core data into extension data
00562 foreach ( $coreData as $key => $item ) {
00563 $this->mergeItem( $key, $allData[$key], $item );
00564 }
00565
00566 # Add cache dependencies for any referenced globals
00567 $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
00568 $deps['wgExtensionAliasesFiles'] = new GlobalDependency( 'wgExtensionAliasesFiles' );
00569 $deps['version'] = new ConstantDependency( 'MW_LC_VERSION' );
00570
00571 # Add dependencies to the cache entry
00572 $allData['deps'] = $deps;
00573
00574 # Replace spaces with underscores in namespace names
00575 $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
00576
00577 # And do the same for special page aliases. $page is an array.
00578 foreach ( $allData['specialPageAliases'] as &$page ) {
00579 $page = str_replace( ' ', '_', $page );
00580 }
00581 # Decouple the reference to prevent accidental damage
00582 unset($page);
00583
00584 # Fix broken defaultUserOptionOverrides
00585 if ( !is_array( $allData['defaultUserOptionOverrides'] ) ) {
00586 $allData['defaultUserOptionOverrides'] = array();
00587 }
00588
00589 # Set the list keys
00590 $allData['list'] = array();
00591 foreach ( self::$splitKeys as $key ) {
00592 $allData['list'][$key] = array_keys( $allData[$key] );
00593 }
00594
00595 # Run hooks
00596 wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) );
00597
00598 if ( is_null( $allData['defaultUserOptionOverrides'] ) ) {
00599 throw new MWException( __METHOD__.': Localisation data failed sanity check! ' .
00600 'Check that your languages/messages/MessagesEn.php file is intact.' );
00601 }
00602
00603 # Set the preload key
00604 $allData['preload'] = $this->buildPreload( $allData );
00605
00606 # Save to the process cache and register the items loaded
00607 $this->data[$code] = $allData;
00608 foreach ( $allData as $key => $item ) {
00609 $this->loadedItems[$code][$key] = true;
00610 }
00611
00612 # Save to the persistent cache
00613 $this->store->startWrite( $code );
00614 foreach ( $allData as $key => $value ) {
00615 if ( in_array( $key, self::$splitKeys ) ) {
00616 foreach ( $value as $subkey => $subvalue ) {
00617 $this->store->set( "$key:$subkey", $subvalue );
00618 }
00619 } else {
00620 $this->store->set( $key, $value );
00621 }
00622 }
00623 $this->store->finishWrite();
00624
00625 wfProfileOut( __METHOD__ );
00626 }
00627
00634 protected function buildPreload( $data ) {
00635 $preload = array( 'messages' => array() );
00636 foreach ( self::$preloadedKeys as $key ) {
00637 $preload[$key] = $data[$key];
00638 }
00639 foreach ( $data['preloadedMessages'] as $subkey ) {
00640 if ( isset( $data['messages'][$subkey] ) ) {
00641 $subitem = $data['messages'][$subkey];
00642 } else {
00643 $subitem = null;
00644 }
00645 $preload['messages'][$subkey] = $subitem;
00646 }
00647 return $preload;
00648 }
00649
00654 public function unload( $code ) {
00655 unset( $this->data[$code] );
00656 unset( $this->loadedItems[$code] );
00657 unset( $this->loadedSubitems[$code] );
00658 unset( $this->initialisedLangs[$code] );
00659
00660
00661 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
00662 if ( $fbCode === $code ) {
00663 $this->unload( $shallowCode );
00664 }
00665 }
00666 }
00667
00671 public function unloadAll() {
00672 foreach ( $this->initialisedLangs as $lang => $unused ) {
00673 $this->unload( $lang );
00674 }
00675 }
00676
00682 public function addLegacyMessages( $messages ) {
00683 foreach ( $messages as $lang => $langMessages ) {
00684 if ( isset( $this->legacyData[$lang]['messages'] ) ) {
00685 $this->legacyData[$lang]['messages'] =
00686 $langMessages + $this->legacyData[$lang]['messages'];
00687 } else {
00688 $this->legacyData[$lang]['messages'] = $langMessages;
00689 }
00690 }
00691 }
00692
00696 public function disableBackend() {
00697 $this->store = new LCStore_Null;
00698 $this->manualRecache = false;
00699 }
00700 }
00701
00719 interface LCStore {
00725 public function get( $code, $key );
00726
00731 public function startWrite( $code );
00732
00736 public function finishWrite();
00737
00742 public function set( $key, $value );
00743
00744 }
00745
00750 class LCStore_DB implements LCStore {
00751 var $currentLang;
00752 var $writesDone = false;
00753 var $dbw, $batch;
00754 var $readOnly = false;
00755
00756 public function get( $code, $key ) {
00757 if ( $this->writesDone ) {
00758 $db = wfGetDB( DB_MASTER );
00759 } else {
00760 $db = wfGetDB( DB_SLAVE );
00761 }
00762 $row = $db->selectRow( 'l10n_cache', array( 'lc_value' ),
00763 array( 'lc_lang' => $code, 'lc_key' => $key ), __METHOD__ );
00764 if ( $row ) {
00765 return unserialize( $row->lc_value );
00766 } else {
00767 return null;
00768 }
00769 }
00770
00771 public function startWrite( $code ) {
00772 if ( $this->readOnly ) {
00773 return;
00774 }
00775 if ( !$code ) {
00776 throw new MWException( __METHOD__.": Invalid language \"$code\"" );
00777 }
00778 $this->dbw = wfGetDB( DB_MASTER );
00779 try {
00780 $this->dbw->begin();
00781 $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ );
00782 } catch ( DBQueryError $e ) {
00783 if ( $this->dbw->wasReadOnlyError() ) {
00784 $this->readOnly = true;
00785 $this->dbw->rollback();
00786 $this->dbw->ignoreErrors( false );
00787 return;
00788 } else {
00789 throw $e;
00790 }
00791 }
00792 $this->currentLang = $code;
00793 $this->batch = array();
00794 }
00795
00796 public function finishWrite() {
00797 if ( $this->readOnly ) {
00798 return;
00799 }
00800 if ( $this->batch ) {
00801 $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
00802 }
00803 $this->dbw->commit();
00804 $this->currentLang = null;
00805 $this->dbw = null;
00806 $this->batch = array();
00807 $this->writesDone = true;
00808 }
00809
00810 public function set( $key, $value ) {
00811 if ( $this->readOnly ) {
00812 return;
00813 }
00814 if ( is_null( $this->currentLang ) ) {
00815 throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
00816 }
00817 $this->batch[] = array(
00818 'lc_lang' => $this->currentLang,
00819 'lc_key' => $key,
00820 'lc_value' => serialize( $value ) );
00821 if ( count( $this->batch ) >= 100 ) {
00822 $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
00823 $this->batch = array();
00824 }
00825 }
00826 }
00827
00840 class LCStore_CDB implements LCStore {
00841 var $readers, $writer, $currentLang, $directory;
00842
00843 function __construct( $conf = array() ) {
00844 global $wgCacheDirectory;
00845 if ( isset( $conf['directory'] ) ) {
00846 $this->directory = $conf['directory'];
00847 } else {
00848 $this->directory = $wgCacheDirectory;
00849 }
00850 }
00851
00852 public function get( $code, $key ) {
00853 if ( !isset( $this->readers[$code] ) ) {
00854 $fileName = $this->getFileName( $code );
00855 if ( !file_exists( $fileName ) ) {
00856 $this->readers[$code] = false;
00857 } else {
00858 $this->readers[$code] = CdbReader::open( $fileName );
00859 }
00860 }
00861 if ( !$this->readers[$code] ) {
00862 return null;
00863 } else {
00864 $value = $this->readers[$code]->get( $key );
00865 if ( $value === false ) {
00866 return null;
00867 }
00868 return unserialize( $value );
00869 }
00870 }
00871
00872 public function startWrite( $code ) {
00873 if ( !file_exists( $this->directory ) ) {
00874 if ( !wfMkdirParents( $this->directory ) ) {
00875 throw new MWException( "Unable to create the localisation store " .
00876 "directory \"{$this->directory}\"" );
00877 }
00878 }
00879
00880 if( !empty($this->readers[$code]) ) {
00881 $this->readers[$code]->close();
00882 }
00883 $this->writer = CdbWriter::open( $this->getFileName( $code ) );
00884 $this->currentLang = $code;
00885 }
00886
00887 public function finishWrite() {
00888
00889 $this->writer->close();
00890 $this->writer = null;
00891 unset( $this->readers[$this->currentLang] );
00892 $this->currentLang = null;
00893 }
00894
00895 public function set( $key, $value ) {
00896 if ( is_null( $this->writer ) ) {
00897 throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
00898 }
00899 $this->writer->set( $key, serialize( $value ) );
00900 }
00901
00902 protected function getFileName( $code ) {
00903 if ( !$code || strpos( $code, '/' ) !== false ) {
00904 throw new MWException( __METHOD__.": Invalid language \"$code\"" );
00905 }
00906 return "{$this->directory}/l10n_cache-$code.cdb";
00907 }
00908 }
00909
00913 class LCStore_Null implements LCStore {
00914 public function get( $code, $key ) {
00915 return null;
00916 }
00917
00918 public function startWrite( $code ) {}
00919 public function finishWrite() {}
00920 public function set( $key, $value ) {}
00921 }
00922
00927 class LocalisationCache_BulkLoad extends LocalisationCache {
00932 var $fileCache = array();
00933
00939 var $mruLangs = array();
00940
00944 var $maxLoadedLangs = 10;
00945
00946 protected function readPHPFile( $fileName, $fileType ) {
00947 $serialize = $fileType === 'core';
00948 if ( !isset( $this->fileCache[$fileName][$fileType] ) ) {
00949 $data = parent::readPHPFile( $fileName, $fileType );
00950 if ( $serialize ) {
00951 $encData = serialize( $data );
00952 } else {
00953 $encData = $data;
00954 }
00955 $this->fileCache[$fileName][$fileType] = $encData;
00956 return $data;
00957 } elseif ( $serialize ) {
00958 return unserialize( $this->fileCache[$fileName][$fileType] );
00959 } else {
00960 return $this->fileCache[$fileName][$fileType];
00961 }
00962 }
00963
00964 public function getItem( $code, $key ) {
00965 unset( $this->mruLangs[$code] );
00966 $this->mruLangs[$code] = true;
00967 return parent::getItem( $code, $key );
00968 }
00969
00970 public function getSubitem( $code, $key, $subkey ) {
00971 unset( $this->mruLangs[$code] );
00972 $this->mruLangs[$code] = true;
00973 return parent::getSubitem( $code, $key, $subkey );
00974 }
00975
00976 public function recache( $code ) {
00977 parent::recache( $code );
00978 unset( $this->mruLangs[$code] );
00979 $this->mruLangs[$code] = true;
00980 $this->trimCache();
00981 }
00982
00983 public function unload( $code ) {
00984 unset( $this->mruLangs[$code] );
00985 parent::unload( $code );
00986 }
00987
00991 protected function trimCache() {
00992 while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) {
00993 reset( $this->mruLangs );
00994 $code = key( $this->mruLangs );
00995 wfDebug( __METHOD__.": unloading $code\n" );
00996 $this->unload( $code );
00997 }
00998 }
00999 }