MediaWiki
REL1_19
|
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 }