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