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