MediaWiki
REL1_22
|
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', 'pluralRuleTypes', '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 00175 var $pluralRuleTypes = null; 00176 00177 var $mergeableKeys = null; 00178 00187 function __construct( $conf ) { 00188 global $wgCacheDirectory; 00189 00190 $this->conf = $conf; 00191 $storeConf = array(); 00192 if ( !empty( $conf['storeClass'] ) ) { 00193 $storeClass = $conf['storeClass']; 00194 } else { 00195 switch ( $conf['store'] ) { 00196 case 'files': 00197 case 'file': 00198 $storeClass = 'LCStore_CDB'; 00199 break; 00200 case 'db': 00201 $storeClass = 'LCStore_DB'; 00202 break; 00203 case 'accel': 00204 $storeClass = 'LCStore_Accel'; 00205 break; 00206 case 'detect': 00207 $storeClass = $wgCacheDirectory ? 'LCStore_CDB' : 'LCStore_DB'; 00208 break; 00209 default: 00210 throw new MWException( 00211 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.' ); 00212 } 00213 } 00214 00215 wfDebug( get_class( $this ) . ": using store $storeClass\n" ); 00216 if ( !empty( $conf['storeDirectory'] ) ) { 00217 $storeConf['directory'] = $conf['storeDirectory']; 00218 } 00219 00220 $this->store = new $storeClass( $storeConf ); 00221 foreach ( array( 'manualRecache', 'forceRecache' ) as $var ) { 00222 if ( isset( $conf[$var] ) ) { 00223 $this->$var = $conf[$var]; 00224 } 00225 } 00226 } 00227 00234 public function isMergeableKey( $key ) { 00235 if ( $this->mergeableKeys === null ) { 00236 $this->mergeableKeys = array_flip( array_merge( 00237 self::$mergeableMapKeys, 00238 self::$mergeableListKeys, 00239 self::$mergeableAliasListKeys, 00240 self::$optionalMergeKeys, 00241 self::$magicWordKeys 00242 ) ); 00243 } 00244 return isset( $this->mergeableKeys[$key] ); 00245 } 00246 00256 public function getItem( $code, $key ) { 00257 if ( !isset( $this->loadedItems[$code][$key] ) ) { 00258 wfProfileIn( __METHOD__ . '-load' ); 00259 $this->loadItem( $code, $key ); 00260 wfProfileOut( __METHOD__ . '-load' ); 00261 } 00262 00263 if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) { 00264 return $this->shallowFallbacks[$code]; 00265 } 00266 00267 return $this->data[$code][$key]; 00268 } 00269 00277 public function getSubitem( $code, $key, $subkey ) { 00278 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) && 00279 !isset( $this->loadedItems[$code][$key] ) ) { 00280 wfProfileIn( __METHOD__ . '-load' ); 00281 $this->loadSubitem( $code, $key, $subkey ); 00282 wfProfileOut( __METHOD__ . '-load' ); 00283 } 00284 00285 if ( isset( $this->data[$code][$key][$subkey] ) ) { 00286 return $this->data[$code][$key][$subkey]; 00287 } else { 00288 return null; 00289 } 00290 } 00291 00304 public function getSubitemList( $code, $key ) { 00305 if ( in_array( $key, self::$splitKeys ) ) { 00306 return $this->getSubitem( $code, 'list', $key ); 00307 } else { 00308 $item = $this->getItem( $code, $key ); 00309 if ( is_array( $item ) ) { 00310 return array_keys( $item ); 00311 } else { 00312 return false; 00313 } 00314 } 00315 } 00316 00322 protected function loadItem( $code, $key ) { 00323 if ( !isset( $this->initialisedLangs[$code] ) ) { 00324 $this->initLanguage( $code ); 00325 } 00326 00327 // Check to see if initLanguage() loaded it for us 00328 if ( isset( $this->loadedItems[$code][$key] ) ) { 00329 return; 00330 } 00331 00332 if ( isset( $this->shallowFallbacks[$code] ) ) { 00333 $this->loadItem( $this->shallowFallbacks[$code], $key ); 00334 return; 00335 } 00336 00337 if ( in_array( $key, self::$splitKeys ) ) { 00338 $subkeyList = $this->getSubitem( $code, 'list', $key ); 00339 foreach ( $subkeyList as $subkey ) { 00340 if ( isset( $this->data[$code][$key][$subkey] ) ) { 00341 continue; 00342 } 00343 $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey ); 00344 } 00345 } else { 00346 $this->data[$code][$key] = $this->store->get( $code, $key ); 00347 } 00348 00349 $this->loadedItems[$code][$key] = true; 00350 } 00351 00358 protected function loadSubitem( $code, $key, $subkey ) { 00359 if ( !in_array( $key, self::$splitKeys ) ) { 00360 $this->loadItem( $code, $key ); 00361 return; 00362 } 00363 00364 if ( !isset( $this->initialisedLangs[$code] ) ) { 00365 $this->initLanguage( $code ); 00366 } 00367 00368 // Check to see if initLanguage() loaded it for us 00369 if ( isset( $this->loadedItems[$code][$key] ) || 00370 isset( $this->loadedSubitems[$code][$key][$subkey] ) ) { 00371 return; 00372 } 00373 00374 if ( isset( $this->shallowFallbacks[$code] ) ) { 00375 $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey ); 00376 return; 00377 } 00378 00379 $value = $this->store->get( $code, "$key:$subkey" ); 00380 $this->data[$code][$key][$subkey] = $value; 00381 $this->loadedSubitems[$code][$key][$subkey] = true; 00382 } 00383 00391 public function isExpired( $code ) { 00392 if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) { 00393 wfDebug( __METHOD__ . "($code): forced reload\n" ); 00394 return true; 00395 } 00396 00397 $deps = $this->store->get( $code, 'deps' ); 00398 $keys = $this->store->get( $code, 'list' ); 00399 $preload = $this->store->get( $code, 'preload' ); 00400 // Different keys may expire separately, at least in LCStore_Accel 00401 if ( $deps === null || $keys === null || $preload === null ) { 00402 wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" ); 00403 return true; 00404 } 00405 00406 foreach ( $deps as $dep ) { 00407 // Because we're unserializing stuff from cache, we 00408 // could receive objects of classes that don't exist 00409 // anymore (e.g. uninstalled extensions) 00410 // When this happens, always expire the cache 00411 if ( !$dep instanceof CacheDependency || $dep->isExpired() ) { 00412 wfDebug( __METHOD__ . "($code): cache for $code expired due to " . 00413 get_class( $dep ) . "\n" ); 00414 return true; 00415 } 00416 } 00417 00418 return false; 00419 } 00420 00426 protected function initLanguage( $code ) { 00427 if ( isset( $this->initialisedLangs[$code] ) ) { 00428 return; 00429 } 00430 00431 $this->initialisedLangs[$code] = true; 00432 00433 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback 00434 if ( !Language::isValidBuiltInCode( $code ) ) { 00435 $this->initShallowFallback( $code, 'en' ); 00436 return; 00437 } 00438 00439 # Recache the data if necessary 00440 if ( !$this->manualRecache && $this->isExpired( $code ) ) { 00441 if ( file_exists( Language::getMessagesFileName( $code ) ) ) { 00442 $this->recache( $code ); 00443 } elseif ( $code === 'en' ) { 00444 throw new MWException( 'MessagesEn.php is missing.' ); 00445 } else { 00446 $this->initShallowFallback( $code, 'en' ); 00447 } 00448 return; 00449 } 00450 00451 # Preload some stuff 00452 $preload = $this->getItem( $code, 'preload' ); 00453 if ( $preload === null ) { 00454 if ( $this->manualRecache ) { 00455 // No Messages*.php file. Do shallow fallback to en. 00456 if ( $code === 'en' ) { 00457 throw new MWException( 'No localisation cache found for English. ' . 00458 'Please run maintenance/rebuildLocalisationCache.php.' ); 00459 } 00460 $this->initShallowFallback( $code, 'en' ); 00461 return; 00462 } else { 00463 throw new MWException( 'Invalid or missing localisation cache.' ); 00464 } 00465 } 00466 $this->data[$code] = $preload; 00467 foreach ( $preload as $key => $item ) { 00468 if ( in_array( $key, self::$splitKeys ) ) { 00469 foreach ( $item as $subkey => $subitem ) { 00470 $this->loadedSubitems[$code][$key][$subkey] = true; 00471 } 00472 } else { 00473 $this->loadedItems[$code][$key] = true; 00474 } 00475 } 00476 } 00477 00484 public function initShallowFallback( $primaryCode, $fallbackCode ) { 00485 $this->data[$primaryCode] =& $this->data[$fallbackCode]; 00486 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode]; 00487 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode]; 00488 $this->shallowFallbacks[$primaryCode] = $fallbackCode; 00489 } 00490 00498 protected function readPHPFile( $_fileName, $_fileType ) { 00499 wfProfileIn( __METHOD__ ); 00500 // Disable APC caching 00501 $_apcEnabled = ini_set( 'apc.cache_by_default', '0' ); 00502 include $_fileName; 00503 ini_set( 'apc.cache_by_default', $_apcEnabled ); 00504 00505 if ( $_fileType == 'core' || $_fileType == 'extension' ) { 00506 $data = compact( self::$allKeys ); 00507 } elseif ( $_fileType == 'aliases' ) { 00508 $data = compact( 'aliases' ); 00509 } else { 00510 wfProfileOut( __METHOD__ ); 00511 throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" ); 00512 } 00513 wfProfileOut( __METHOD__ ); 00514 return $data; 00515 } 00516 00521 public function getCompiledPluralRules( $code ) { 00522 $rules = $this->getPluralRules( $code ); 00523 if ( $rules === null ) { 00524 return null; 00525 } 00526 try { 00527 $compiledRules = CLDRPluralRuleEvaluator::compile( $rules ); 00528 } catch ( CLDRPluralRuleError $e ) { 00529 wfDebugLog( 'l10n', $e->getMessage() . "\n" ); 00530 return array(); 00531 } 00532 return $compiledRules; 00533 } 00534 00540 public function getPluralRules( $code ) { 00541 if ( $this->pluralRules === null ) { 00542 $this->loadPluralFiles(); 00543 } 00544 if ( !isset( $this->pluralRules[$code] ) ) { 00545 return null; 00546 } else { 00547 return $this->pluralRules[$code]; 00548 } 00549 } 00550 00556 public function getPluralRuleTypes( $code ) { 00557 if ( $this->pluralRuleTypes === null ) { 00558 $this->loadPluralFiles(); 00559 } 00560 if ( !isset( $this->pluralRuleTypes[$code] ) ) { 00561 return null; 00562 } else { 00563 return $this->pluralRuleTypes[$code]; 00564 } 00565 } 00566 00570 protected function loadPluralFiles() { 00571 global $IP; 00572 $cldrPlural = "$IP/languages/data/plurals.xml"; 00573 $mwPlural = "$IP/languages/data/plurals-mediawiki.xml"; 00574 // Load CLDR plural rules 00575 $this->loadPluralFile( $cldrPlural ); 00576 if ( file_exists( $mwPlural ) ) { 00577 // Override or extend 00578 $this->loadPluralFile( $mwPlural ); 00579 } 00580 } 00581 00586 protected function loadPluralFile( $fileName ) { 00587 $doc = new DOMDocument; 00588 $doc->load( $fileName ); 00589 $rulesets = $doc->getElementsByTagName( "pluralRules" ); 00590 foreach ( $rulesets as $ruleset ) { 00591 $codes = $ruleset->getAttribute( 'locales' ); 00592 $rules = array(); 00593 $ruleTypes = array(); 00594 $ruleElements = $ruleset->getElementsByTagName( "pluralRule" ); 00595 foreach ( $ruleElements as $elt ) { 00596 $ruleType = $elt->getAttribute( 'count' ); 00597 if ( $ruleType === 'other' ) { 00598 // Don't record "other" rules, which have an empty condition 00599 continue; 00600 } 00601 $rules[] = $elt->nodeValue; 00602 $ruleTypes[] = $ruleType; 00603 } 00604 foreach ( explode( ' ', $codes ) as $code ) { 00605 $this->pluralRules[$code] = $rules; 00606 $this->pluralRuleTypes[$code] = $ruleTypes; 00607 } 00608 } 00609 } 00610 00616 protected function readSourceFilesAndRegisterDeps( $code, &$deps ) { 00617 global $IP; 00618 wfProfileIn( __METHOD__ ); 00619 00620 $fileName = Language::getMessagesFileName( $code ); 00621 if ( !file_exists( $fileName ) ) { 00622 wfProfileOut( __METHOD__ ); 00623 return false; 00624 } 00625 00626 $deps[] = new FileDependency( $fileName ); 00627 $data = $this->readPHPFile( $fileName, 'core' ); 00628 00629 # Load CLDR plural rules for JavaScript 00630 $data['pluralRules'] = $this->getPluralRules( $code ); 00631 # And for PHP 00632 $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code ); 00633 # Load plural rule types 00634 $data['pluralRuleTypes'] = $this->getPluralRuleTypes( $code ); 00635 00636 $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" ); 00637 $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" ); 00638 00639 wfProfileOut( __METHOD__ ); 00640 return $data; 00641 } 00642 00650 protected function mergeItem( $key, &$value, $fallbackValue ) { 00651 if ( !is_null( $value ) ) { 00652 if ( !is_null( $fallbackValue ) ) { 00653 if ( in_array( $key, self::$mergeableMapKeys ) ) { 00654 $value = $value + $fallbackValue; 00655 } elseif ( in_array( $key, self::$mergeableListKeys ) ) { 00656 $value = array_unique( array_merge( $fallbackValue, $value ) ); 00657 } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) { 00658 $value = array_merge_recursive( $value, $fallbackValue ); 00659 } elseif ( in_array( $key, self::$optionalMergeKeys ) ) { 00660 if ( !empty( $value['inherit'] ) ) { 00661 $value = array_merge( $fallbackValue, $value ); 00662 } 00663 00664 if ( isset( $value['inherit'] ) ) { 00665 unset( $value['inherit'] ); 00666 } 00667 } elseif ( in_array( $key, self::$magicWordKeys ) ) { 00668 $this->mergeMagicWords( $value, $fallbackValue ); 00669 } 00670 } 00671 } else { 00672 $value = $fallbackValue; 00673 } 00674 } 00675 00680 protected function mergeMagicWords( &$value, $fallbackValue ) { 00681 foreach ( $fallbackValue as $magicName => $fallbackInfo ) { 00682 if ( !isset( $value[$magicName] ) ) { 00683 $value[$magicName] = $fallbackInfo; 00684 } else { 00685 $oldSynonyms = array_slice( $fallbackInfo, 1 ); 00686 $newSynonyms = array_slice( $value[$magicName], 1 ); 00687 $synonyms = array_values( array_unique( array_merge( 00688 $newSynonyms, $oldSynonyms ) ) ); 00689 $value[$magicName] = array_merge( array( $fallbackInfo[0] ), $synonyms ); 00690 } 00691 } 00692 } 00693 00707 protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) { 00708 $used = false; 00709 foreach ( $codeSequence as $code ) { 00710 if ( isset( $fallbackValue[$code] ) ) { 00711 $this->mergeItem( $key, $value, $fallbackValue[$code] ); 00712 $used = true; 00713 } 00714 } 00715 00716 return $used; 00717 } 00718 00725 public function recache( $code ) { 00726 global $wgExtensionMessagesFiles; 00727 wfProfileIn( __METHOD__ ); 00728 00729 if ( !$code ) { 00730 wfProfileOut( __METHOD__ ); 00731 throw new MWException( "Invalid language code requested" ); 00732 } 00733 $this->recachedLangs[$code] = true; 00734 00735 # Initial values 00736 $initialData = array_combine( 00737 self::$allKeys, 00738 array_fill( 0, count( self::$allKeys ), null ) ); 00739 $coreData = $initialData; 00740 $deps = array(); 00741 00742 # Load the primary localisation from the source file 00743 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps ); 00744 if ( $data === false ) { 00745 wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" ); 00746 $coreData['fallback'] = 'en'; 00747 } else { 00748 wfDebug( __METHOD__ . ": got localisation for $code from source\n" ); 00749 00750 # Merge primary localisation 00751 foreach ( $data as $key => $value ) { 00752 $this->mergeItem( $key, $coreData[$key], $value ); 00753 } 00754 00755 } 00756 00757 # Fill in the fallback if it's not there already 00758 if ( is_null( $coreData['fallback'] ) ) { 00759 $coreData['fallback'] = $code === 'en' ? false : 'en'; 00760 } 00761 if ( $coreData['fallback'] === false ) { 00762 $coreData['fallbackSequence'] = array(); 00763 } else { 00764 $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) ); 00765 $len = count( $coreData['fallbackSequence'] ); 00766 00767 # Ensure that the sequence ends at en 00768 if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) { 00769 $coreData['fallbackSequence'][] = 'en'; 00770 } 00771 00772 # Load the fallback localisation item by item and merge it 00773 foreach ( $coreData['fallbackSequence'] as $fbCode ) { 00774 # Load the secondary localisation from the source file to 00775 # avoid infinite cycles on cyclic fallbacks 00776 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps ); 00777 if ( $fbData === false ) { 00778 continue; 00779 } 00780 00781 foreach ( self::$allKeys as $key ) { 00782 if ( !isset( $fbData[$key] ) ) { 00783 continue; 00784 } 00785 00786 if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) { 00787 $this->mergeItem( $key, $coreData[$key], $fbData[$key] ); 00788 } 00789 } 00790 } 00791 } 00792 00793 $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] ); 00794 00795 # Load the extension localisations 00796 # This is done after the core because we know the fallback sequence now. 00797 # But it has a higher precedence for merging so that we can support things 00798 # like site-specific message overrides. 00799 wfProfileIn( __METHOD__ . '-extensions' ); 00800 $allData = $initialData; 00801 foreach ( $wgExtensionMessagesFiles as $fileName ) { 00802 $data = $this->readPHPFile( $fileName, 'extension' ); 00803 $used = false; 00804 00805 foreach ( $data as $key => $item ) { 00806 if ( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) { 00807 $used = true; 00808 } 00809 } 00810 00811 if ( $used ) { 00812 $deps[] = new FileDependency( $fileName ); 00813 } 00814 } 00815 00816 # Merge core data into extension data 00817 foreach ( $coreData as $key => $item ) { 00818 $this->mergeItem( $key, $allData[$key], $item ); 00819 } 00820 wfProfileOut( __METHOD__ . '-extensions' ); 00821 00822 # Add cache dependencies for any referenced globals 00823 $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' ); 00824 $deps['version'] = new ConstantDependency( 'MW_LC_VERSION' ); 00825 00826 # Add dependencies to the cache entry 00827 $allData['deps'] = $deps; 00828 00829 # Replace spaces with underscores in namespace names 00830 $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] ); 00831 00832 # And do the same for special page aliases. $page is an array. 00833 foreach ( $allData['specialPageAliases'] as &$page ) { 00834 $page = str_replace( ' ', '_', $page ); 00835 } 00836 # Decouple the reference to prevent accidental damage 00837 unset( $page ); 00838 00839 # If there were no plural rules, return an empty array 00840 if ( $allData['pluralRules'] === null ) { 00841 $allData['pluralRules'] = array(); 00842 } 00843 if ( $allData['compiledPluralRules'] === null ) { 00844 $allData['compiledPluralRules'] = array(); 00845 } 00846 # If there were no plural rule types, return an empty array 00847 if ( $allData['pluralRuleTypes'] === null ) { 00848 $allData['pluralRuleTypes'] = array(); 00849 } 00850 00851 # Set the list keys 00852 $allData['list'] = array(); 00853 foreach ( self::$splitKeys as $key ) { 00854 $allData['list'][$key] = array_keys( $allData[$key] ); 00855 } 00856 # Run hooks 00857 wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) ); 00858 00859 if ( is_null( $allData['namespaceNames'] ) ) { 00860 wfProfileOut( __METHOD__ ); 00861 throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' . 00862 'Check that your languages/messages/MessagesEn.php file is intact.' ); 00863 } 00864 00865 # Set the preload key 00866 $allData['preload'] = $this->buildPreload( $allData ); 00867 00868 # Save to the process cache and register the items loaded 00869 $this->data[$code] = $allData; 00870 foreach ( $allData as $key => $item ) { 00871 $this->loadedItems[$code][$key] = true; 00872 } 00873 00874 # Save to the persistent cache 00875 wfProfileIn( __METHOD__ . '-write' ); 00876 $this->store->startWrite( $code ); 00877 foreach ( $allData as $key => $value ) { 00878 if ( in_array( $key, self::$splitKeys ) ) { 00879 foreach ( $value as $subkey => $subvalue ) { 00880 $this->store->set( "$key:$subkey", $subvalue ); 00881 } 00882 } else { 00883 $this->store->set( $key, $value ); 00884 } 00885 } 00886 $this->store->finishWrite(); 00887 wfProfileOut( __METHOD__ . '-write' ); 00888 00889 # Clear out the MessageBlobStore 00890 # HACK: If using a null (i.e. disabled) storage backend, we 00891 # can't write to the MessageBlobStore either 00892 if ( !$this->store instanceof LCStore_Null ) { 00893 MessageBlobStore::clear(); 00894 } 00895 00896 wfProfileOut( __METHOD__ ); 00897 } 00898 00907 protected function buildPreload( $data ) { 00908 $preload = array( 'messages' => array() ); 00909 foreach ( self::$preloadedKeys as $key ) { 00910 $preload[$key] = $data[$key]; 00911 } 00912 00913 foreach ( $data['preloadedMessages'] as $subkey ) { 00914 if ( isset( $data['messages'][$subkey] ) ) { 00915 $subitem = $data['messages'][$subkey]; 00916 } else { 00917 $subitem = null; 00918 } 00919 $preload['messages'][$subkey] = $subitem; 00920 } 00921 00922 return $preload; 00923 } 00924 00930 public function unload( $code ) { 00931 unset( $this->data[$code] ); 00932 unset( $this->loadedItems[$code] ); 00933 unset( $this->loadedSubitems[$code] ); 00934 unset( $this->initialisedLangs[$code] ); 00935 unset( $this->shallowFallbacks[$code] ); 00936 00937 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) { 00938 if ( $fbCode === $code ) { 00939 $this->unload( $shallowCode ); 00940 } 00941 } 00942 } 00943 00947 public function unloadAll() { 00948 foreach ( $this->initialisedLangs as $lang => $unused ) { 00949 $this->unload( $lang ); 00950 } 00951 } 00952 00956 public function disableBackend() { 00957 $this->store = new LCStore_Null; 00958 $this->manualRecache = false; 00959 } 00960 00966 public function getInitialisedLanguages() { 00967 return $this->initialisedLangs; 00968 } 00969 00975 public function setInitialisedLanguages( $languages = array() ) { 00976 $this->initialisedLangs = $languages; 00977 } 00978 00979 } 00980 00998 interface LCStore { 01004 function get( $code, $key ); 01005 01010 function startWrite( $code ); 01011 01015 function finishWrite(); 01016 01023 function set( $key, $value ); 01024 } 01025 01031 class LCStore_Accel implements LCStore { 01032 var $currentLang; 01033 var $keys; 01034 01035 public function __construct() { 01036 $this->cache = wfGetCache( CACHE_ACCEL ); 01037 } 01038 01039 public function get( $code, $key ) { 01040 $k = wfMemcKey( 'l10n', $code, 'k', $key ); 01041 $r = $this->cache->get( $k ); 01042 return $r === false ? null : $r; 01043 } 01044 01045 public function startWrite( $code ) { 01046 $k = wfMemcKey( 'l10n', $code, 'l' ); 01047 $keys = $this->cache->get( $k ); 01048 if ( $keys ) { 01049 foreach ( $keys as $k ) { 01050 $this->cache->delete( $k ); 01051 } 01052 } 01053 $this->currentLang = $code; 01054 $this->keys = array(); 01055 } 01056 01057 public function finishWrite() { 01058 if ( $this->currentLang ) { 01059 $k = wfMemcKey( 'l10n', $this->currentLang, 'l' ); 01060 $this->cache->set( $k, array_keys( $this->keys ) ); 01061 } 01062 $this->currentLang = null; 01063 $this->keys = array(); 01064 } 01065 01066 public function set( $key, $value ) { 01067 if ( $this->currentLang ) { 01068 $k = wfMemcKey( 'l10n', $this->currentLang, 'k', $key ); 01069 $this->keys[$k] = true; 01070 $this->cache->set( $k, $value ); 01071 } 01072 } 01073 } 01074 01079 class LCStore_DB implements LCStore { 01080 var $currentLang; 01081 var $writesDone = false; 01082 01086 var $dbw; 01087 var $batch; 01088 var $readOnly = false; 01089 01090 public function get( $code, $key ) { 01091 if ( $this->writesDone ) { 01092 $db = wfGetDB( DB_MASTER ); 01093 } else { 01094 $db = wfGetDB( DB_SLAVE ); 01095 } 01096 $row = $db->selectRow( 'l10n_cache', array( 'lc_value' ), 01097 array( 'lc_lang' => $code, 'lc_key' => $key ), __METHOD__ ); 01098 if ( $row ) { 01099 return unserialize( $row->lc_value ); 01100 } else { 01101 return null; 01102 } 01103 } 01104 01105 public function startWrite( $code ) { 01106 if ( $this->readOnly ) { 01107 return; 01108 } 01109 01110 if ( !$code ) { 01111 throw new MWException( __METHOD__ . ": Invalid language \"$code\"" ); 01112 } 01113 01114 $this->dbw = wfGetDB( DB_MASTER ); 01115 try { 01116 $this->dbw->begin( __METHOD__ ); 01117 $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ ); 01118 } catch ( DBQueryError $e ) { 01119 if ( $this->dbw->wasReadOnlyError() ) { 01120 $this->readOnly = true; 01121 $this->dbw->rollback( __METHOD__ ); 01122 return; 01123 } else { 01124 throw $e; 01125 } 01126 } 01127 01128 $this->currentLang = $code; 01129 $this->batch = array(); 01130 } 01131 01132 public function finishWrite() { 01133 if ( $this->readOnly ) { 01134 return; 01135 } 01136 01137 if ( $this->batch ) { 01138 $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ ); 01139 } 01140 01141 $this->dbw->commit( __METHOD__ ); 01142 $this->currentLang = null; 01143 $this->dbw = null; 01144 $this->batch = array(); 01145 $this->writesDone = true; 01146 } 01147 01148 public function set( $key, $value ) { 01149 if ( $this->readOnly ) { 01150 return; 01151 } 01152 01153 if ( is_null( $this->currentLang ) ) { 01154 throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' ); 01155 } 01156 01157 $this->batch[] = array( 01158 'lc_lang' => $this->currentLang, 01159 'lc_key' => $key, 01160 'lc_value' => serialize( $value ) ); 01161 01162 if ( count( $this->batch ) >= 100 ) { 01163 $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ ); 01164 $this->batch = array(); 01165 } 01166 } 01167 } 01168 01181 class LCStore_CDB implements LCStore { 01182 var $readers, $writer, $currentLang, $directory; 01183 01184 function __construct( $conf = array() ) { 01185 global $wgCacheDirectory; 01186 01187 if ( isset( $conf['directory'] ) ) { 01188 $this->directory = $conf['directory']; 01189 } else { 01190 $this->directory = $wgCacheDirectory; 01191 } 01192 } 01193 01194 public function get( $code, $key ) { 01195 if ( !isset( $this->readers[$code] ) ) { 01196 $fileName = $this->getFileName( $code ); 01197 01198 if ( !file_exists( $fileName ) ) { 01199 $this->readers[$code] = false; 01200 } else { 01201 $this->readers[$code] = CdbReader::open( $fileName ); 01202 } 01203 } 01204 01205 if ( !$this->readers[$code] ) { 01206 return null; 01207 } else { 01208 $value = $this->readers[$code]->get( $key ); 01209 01210 if ( $value === false ) { 01211 return null; 01212 } 01213 return unserialize( $value ); 01214 } 01215 } 01216 01217 public function startWrite( $code ) { 01218 if ( !file_exists( $this->directory ) ) { 01219 if ( !wfMkdirParents( $this->directory, null, __METHOD__ ) ) { 01220 throw new MWException( "Unable to create the localisation store " . 01221 "directory \"{$this->directory}\"" ); 01222 } 01223 } 01224 01225 // Close reader to stop permission errors on write 01226 if ( !empty( $this->readers[$code] ) ) { 01227 $this->readers[$code]->close(); 01228 } 01229 01230 $this->writer = CdbWriter::open( $this->getFileName( $code ) ); 01231 $this->currentLang = $code; 01232 } 01233 01234 public function finishWrite() { 01235 // Close the writer 01236 $this->writer->close(); 01237 $this->writer = null; 01238 unset( $this->readers[$this->currentLang] ); 01239 $this->currentLang = null; 01240 } 01241 01242 public function set( $key, $value ) { 01243 if ( is_null( $this->writer ) ) { 01244 throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' ); 01245 } 01246 $this->writer->set( $key, serialize( $value ) ); 01247 } 01248 01249 protected function getFileName( $code ) { 01250 if ( strval( $code ) === '' || strpos( $code, '/' ) !== false ) { 01251 throw new MWException( __METHOD__ . ": Invalid language \"$code\"" ); 01252 } 01253 return "{$this->directory}/l10n_cache-$code.cdb"; 01254 } 01255 } 01256 01260 class LCStore_Null implements LCStore { 01261 public function get( $code, $key ) { 01262 return null; 01263 } 01264 01265 public function startWrite( $code ) {} 01266 public function finishWrite() {} 01267 public function set( $key, $value ) {} 01268 } 01269 01274 class LocalisationCache_BulkLoad extends LocalisationCache { 01279 var $fileCache = array(); 01280 01286 var $mruLangs = array(); 01287 01291 var $maxLoadedLangs = 10; 01292 01298 protected function readPHPFile( $fileName, $fileType ) { 01299 $serialize = $fileType === 'core'; 01300 if ( !isset( $this->fileCache[$fileName][$fileType] ) ) { 01301 $data = parent::readPHPFile( $fileName, $fileType ); 01302 01303 if ( $serialize ) { 01304 $encData = serialize( $data ); 01305 } else { 01306 $encData = $data; 01307 } 01308 01309 $this->fileCache[$fileName][$fileType] = $encData; 01310 01311 return $data; 01312 } elseif ( $serialize ) { 01313 return unserialize( $this->fileCache[$fileName][$fileType] ); 01314 } else { 01315 return $this->fileCache[$fileName][$fileType]; 01316 } 01317 } 01318 01324 public function getItem( $code, $key ) { 01325 unset( $this->mruLangs[$code] ); 01326 $this->mruLangs[$code] = true; 01327 return parent::getItem( $code, $key ); 01328 } 01329 01336 public function getSubitem( $code, $key, $subkey ) { 01337 unset( $this->mruLangs[$code] ); 01338 $this->mruLangs[$code] = true; 01339 return parent::getSubitem( $code, $key, $subkey ); 01340 } 01341 01345 public function recache( $code ) { 01346 parent::recache( $code ); 01347 unset( $this->mruLangs[$code] ); 01348 $this->mruLangs[$code] = true; 01349 $this->trimCache(); 01350 } 01351 01355 public function unload( $code ) { 01356 unset( $this->mruLangs[$code] ); 01357 parent::unload( $code ); 01358 } 01359 01363 protected function trimCache() { 01364 while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) { 01365 reset( $this->mruLangs ); 01366 $code = key( $this->mruLangs ); 01367 wfDebug( __METHOD__ . ": unloading $code\n" ); 01368 $this->unload( $code ); 01369 } 01370 } 01371 }