MediaWiki
REL1_21
|
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' ); 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 global $IP; 00523 00524 if ( $this->pluralRules === null ) { 00525 $cldrPlural = "$IP/languages/data/plurals.xml"; 00526 $mwPlural = "$IP/languages/data/plurals-mediawiki.xml"; 00527 // Load CLDR plural rules 00528 $this->loadPluralFile( $cldrPlural ); 00529 if ( file_exists( $mwPlural ) ) { 00530 // Override or extend 00531 $this->loadPluralFile( $mwPlural ); 00532 } 00533 } 00534 if ( !isset( $this->pluralRules[$code] ) ) { 00535 return null; 00536 } else { 00537 return $this->pluralRules[$code]; 00538 } 00539 } 00540 00545 protected function loadPluralFile( $fileName ) { 00546 $doc = new DOMDocument; 00547 $doc->load( $fileName ); 00548 $rulesets = $doc->getElementsByTagName( "pluralRules" ); 00549 foreach ( $rulesets as $ruleset ) { 00550 $codes = $ruleset->getAttribute( 'locales' ); 00551 $rules = array(); 00552 $ruleElements = $ruleset->getElementsByTagName( "pluralRule" ); 00553 foreach ( $ruleElements as $elt ) { 00554 $rules[] = $elt->nodeValue; 00555 } 00556 foreach ( explode( ' ', $codes ) as $code ) { 00557 $this->pluralRules[$code] = $rules; 00558 } 00559 } 00560 } 00561 00567 protected function readSourceFilesAndRegisterDeps( $code, &$deps ) { 00568 global $IP; 00569 00570 $fileName = Language::getMessagesFileName( $code ); 00571 if ( !file_exists( $fileName ) ) { 00572 return false; 00573 } 00574 00575 $deps[] = new FileDependency( $fileName ); 00576 $data = $this->readPHPFile( $fileName, 'core' ); 00577 00578 # Load CLDR plural rules for JavaScript 00579 $data['pluralRules'] = $this->getPluralRules( $code ); 00580 # And for PHP 00581 $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code ); 00582 00583 $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" ); 00584 $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" ); 00585 00586 return $data; 00587 } 00588 00596 protected function mergeItem( $key, &$value, $fallbackValue ) { 00597 if ( !is_null( $value ) ) { 00598 if ( !is_null( $fallbackValue ) ) { 00599 if ( in_array( $key, self::$mergeableMapKeys ) ) { 00600 $value = $value + $fallbackValue; 00601 } elseif ( in_array( $key, self::$mergeableListKeys ) ) { 00602 $value = array_unique( array_merge( $fallbackValue, $value ) ); 00603 } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) { 00604 $value = array_merge_recursive( $value, $fallbackValue ); 00605 } elseif ( in_array( $key, self::$optionalMergeKeys ) ) { 00606 if ( !empty( $value['inherit'] ) ) { 00607 $value = array_merge( $fallbackValue, $value ); 00608 } 00609 00610 if ( isset( $value['inherit'] ) ) { 00611 unset( $value['inherit'] ); 00612 } 00613 } elseif ( in_array( $key, self::$magicWordKeys ) ) { 00614 $this->mergeMagicWords( $value, $fallbackValue ); 00615 } 00616 } 00617 } else { 00618 $value = $fallbackValue; 00619 } 00620 } 00621 00626 protected function mergeMagicWords( &$value, $fallbackValue ) { 00627 foreach ( $fallbackValue as $magicName => $fallbackInfo ) { 00628 if ( !isset( $value[$magicName] ) ) { 00629 $value[$magicName] = $fallbackInfo; 00630 } else { 00631 $oldSynonyms = array_slice( $fallbackInfo, 1 ); 00632 $newSynonyms = array_slice( $value[$magicName], 1 ); 00633 $synonyms = array_values( array_unique( array_merge( 00634 $newSynonyms, $oldSynonyms ) ) ); 00635 $value[$magicName] = array_merge( array( $fallbackInfo[0] ), $synonyms ); 00636 } 00637 } 00638 } 00639 00653 protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) { 00654 $used = false; 00655 foreach ( $codeSequence as $code ) { 00656 if ( isset( $fallbackValue[$code] ) ) { 00657 $this->mergeItem( $key, $value, $fallbackValue[$code] ); 00658 $used = true; 00659 } 00660 } 00661 00662 return $used; 00663 } 00664 00671 public function recache( $code ) { 00672 global $wgExtensionMessagesFiles; 00673 wfProfileIn( __METHOD__ ); 00674 00675 if ( !$code ) { 00676 throw new MWException( "Invalid language code requested" ); 00677 } 00678 $this->recachedLangs[$code] = true; 00679 00680 # Initial values 00681 $initialData = array_combine( 00682 self::$allKeys, 00683 array_fill( 0, count( self::$allKeys ), null ) ); 00684 $coreData = $initialData; 00685 $deps = array(); 00686 00687 # Load the primary localisation from the source file 00688 $data = $this->readSourceFilesAndRegisterDeps( $code, $deps ); 00689 if ( $data === false ) { 00690 wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" ); 00691 $coreData['fallback'] = 'en'; 00692 } else { 00693 wfDebug( __METHOD__ . ": got localisation for $code from source\n" ); 00694 00695 # Merge primary localisation 00696 foreach ( $data as $key => $value ) { 00697 $this->mergeItem( $key, $coreData[$key], $value ); 00698 } 00699 00700 } 00701 00702 # Fill in the fallback if it's not there already 00703 if ( is_null( $coreData['fallback'] ) ) { 00704 $coreData['fallback'] = $code === 'en' ? false : 'en'; 00705 } 00706 if ( $coreData['fallback'] === false ) { 00707 $coreData['fallbackSequence'] = array(); 00708 } else { 00709 $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) ); 00710 $len = count( $coreData['fallbackSequence'] ); 00711 00712 # Ensure that the sequence ends at en 00713 if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) { 00714 $coreData['fallbackSequence'][] = 'en'; 00715 } 00716 00717 # Load the fallback localisation item by item and merge it 00718 foreach ( $coreData['fallbackSequence'] as $fbCode ) { 00719 # Load the secondary localisation from the source file to 00720 # avoid infinite cycles on cyclic fallbacks 00721 $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps ); 00722 if ( $fbData === false ) { 00723 continue; 00724 } 00725 00726 foreach ( self::$allKeys as $key ) { 00727 if ( !isset( $fbData[$key] ) ) { 00728 continue; 00729 } 00730 00731 if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) { 00732 $this->mergeItem( $key, $coreData[$key], $fbData[$key] ); 00733 } 00734 } 00735 } 00736 } 00737 00738 $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] ); 00739 00740 # Load the extension localisations 00741 # This is done after the core because we know the fallback sequence now. 00742 # But it has a higher precedence for merging so that we can support things 00743 # like site-specific message overrides. 00744 $allData = $initialData; 00745 foreach ( $wgExtensionMessagesFiles as $fileName ) { 00746 $data = $this->readPHPFile( $fileName, 'extension' ); 00747 $used = false; 00748 00749 foreach ( $data as $key => $item ) { 00750 if ( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) { 00751 $used = true; 00752 } 00753 } 00754 00755 if ( $used ) { 00756 $deps[] = new FileDependency( $fileName ); 00757 } 00758 } 00759 00760 # Merge core data into extension data 00761 foreach ( $coreData as $key => $item ) { 00762 $this->mergeItem( $key, $allData[$key], $item ); 00763 } 00764 00765 # Add cache dependencies for any referenced globals 00766 $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' ); 00767 $deps['version'] = new ConstantDependency( 'MW_LC_VERSION' ); 00768 00769 # Add dependencies to the cache entry 00770 $allData['deps'] = $deps; 00771 00772 # Replace spaces with underscores in namespace names 00773 $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] ); 00774 00775 # And do the same for special page aliases. $page is an array. 00776 foreach ( $allData['specialPageAliases'] as &$page ) { 00777 $page = str_replace( ' ', '_', $page ); 00778 } 00779 # Decouple the reference to prevent accidental damage 00780 unset( $page ); 00781 00782 # If there were no plural rules, return an empty array 00783 if ( $allData['pluralRules'] === null ) { 00784 $allData['pluralRules'] = array(); 00785 } 00786 if ( $allData['compiledPluralRules'] === null ) { 00787 $allData['compiledPluralRules'] = array(); 00788 } 00789 00790 # Set the list keys 00791 $allData['list'] = array(); 00792 foreach ( self::$splitKeys as $key ) { 00793 $allData['list'][$key] = array_keys( $allData[$key] ); 00794 } 00795 # Run hooks 00796 wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) ); 00797 00798 if ( is_null( $allData['namespaceNames'] ) ) { 00799 throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' . 00800 'Check that your languages/messages/MessagesEn.php file is intact.' ); 00801 } 00802 00803 # Set the preload key 00804 $allData['preload'] = $this->buildPreload( $allData ); 00805 00806 # Save to the process cache and register the items loaded 00807 $this->data[$code] = $allData; 00808 foreach ( $allData as $key => $item ) { 00809 $this->loadedItems[$code][$key] = true; 00810 } 00811 00812 # Save to the persistent cache 00813 $this->store->startWrite( $code ); 00814 foreach ( $allData as $key => $value ) { 00815 if ( in_array( $key, self::$splitKeys ) ) { 00816 foreach ( $value as $subkey => $subvalue ) { 00817 $this->store->set( "$key:$subkey", $subvalue ); 00818 } 00819 } else { 00820 $this->store->set( $key, $value ); 00821 } 00822 } 00823 $this->store->finishWrite(); 00824 00825 # Clear out the MessageBlobStore 00826 # HACK: If using a null (i.e. disabled) storage backend, we 00827 # can't write to the MessageBlobStore either 00828 if ( !$this->store instanceof LCStore_Null ) { 00829 MessageBlobStore::clear(); 00830 } 00831 00832 wfProfileOut( __METHOD__ ); 00833 } 00834 00843 protected function buildPreload( $data ) { 00844 $preload = array( 'messages' => array() ); 00845 foreach ( self::$preloadedKeys as $key ) { 00846 $preload[$key] = $data[$key]; 00847 } 00848 00849 foreach ( $data['preloadedMessages'] as $subkey ) { 00850 if ( isset( $data['messages'][$subkey] ) ) { 00851 $subitem = $data['messages'][$subkey]; 00852 } else { 00853 $subitem = null; 00854 } 00855 $preload['messages'][$subkey] = $subitem; 00856 } 00857 00858 return $preload; 00859 } 00860 00866 public function unload( $code ) { 00867 unset( $this->data[$code] ); 00868 unset( $this->loadedItems[$code] ); 00869 unset( $this->loadedSubitems[$code] ); 00870 unset( $this->initialisedLangs[$code] ); 00871 00872 foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) { 00873 if ( $fbCode === $code ) { 00874 $this->unload( $shallowCode ); 00875 } 00876 } 00877 } 00878 00882 public function unloadAll() { 00883 foreach ( $this->initialisedLangs as $lang => $unused ) { 00884 $this->unload( $lang ); 00885 } 00886 } 00887 00891 public function disableBackend() { 00892 $this->store = new LCStore_Null; 00893 $this->manualRecache = false; 00894 } 00895 } 00896 00914 interface LCStore { 00920 function get( $code, $key ); 00921 00926 function startWrite( $code ); 00927 00931 function finishWrite(); 00932 00939 function set( $key, $value ); 00940 } 00941 00947 class LCStore_Accel implements LCStore { 00948 var $currentLang; 00949 var $keys; 00950 00951 public function __construct() { 00952 $this->cache = wfGetCache( CACHE_ACCEL ); 00953 } 00954 00955 public function get( $code, $key ) { 00956 $k = wfMemcKey( 'l10n', $code, 'k', $key ); 00957 $r = $this->cache->get( $k ); 00958 return $r === false ? null : $r; 00959 } 00960 00961 public function startWrite( $code ) { 00962 $k = wfMemcKey( 'l10n', $code, 'l' ); 00963 $keys = $this->cache->get( $k ); 00964 if ( $keys ) { 00965 foreach ( $keys as $k ) { 00966 $this->cache->delete( $k ); 00967 } 00968 } 00969 $this->currentLang = $code; 00970 $this->keys = array(); 00971 } 00972 00973 public function finishWrite() { 00974 if ( $this->currentLang ) { 00975 $k = wfMemcKey( 'l10n', $this->currentLang, 'l' ); 00976 $this->cache->set( $k, array_keys( $this->keys ) ); 00977 } 00978 $this->currentLang = null; 00979 $this->keys = array(); 00980 } 00981 00982 public function set( $key, $value ) { 00983 if ( $this->currentLang ) { 00984 $k = wfMemcKey( 'l10n', $this->currentLang, 'k', $key ); 00985 $this->keys[$k] = true; 00986 $this->cache->set( $k, $value ); 00987 } 00988 } 00989 } 00990 00995 class LCStore_DB implements LCStore { 00996 var $currentLang; 00997 var $writesDone = false; 00998 01002 var $dbw; 01003 var $batch; 01004 var $readOnly = false; 01005 01006 public function get( $code, $key ) { 01007 if ( $this->writesDone ) { 01008 $db = wfGetDB( DB_MASTER ); 01009 } else { 01010 $db = wfGetDB( DB_SLAVE ); 01011 } 01012 $row = $db->selectRow( 'l10n_cache', array( 'lc_value' ), 01013 array( 'lc_lang' => $code, 'lc_key' => $key ), __METHOD__ ); 01014 if ( $row ) { 01015 return unserialize( $row->lc_value ); 01016 } else { 01017 return null; 01018 } 01019 } 01020 01021 public function startWrite( $code ) { 01022 if ( $this->readOnly ) { 01023 return; 01024 } 01025 01026 if ( !$code ) { 01027 throw new MWException( __METHOD__ . ": Invalid language \"$code\"" ); 01028 } 01029 01030 $this->dbw = wfGetDB( DB_MASTER ); 01031 try { 01032 $this->dbw->begin( __METHOD__ ); 01033 $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ ); 01034 } catch ( DBQueryError $e ) { 01035 if ( $this->dbw->wasReadOnlyError() ) { 01036 $this->readOnly = true; 01037 $this->dbw->rollback( __METHOD__ ); 01038 return; 01039 } else { 01040 throw $e; 01041 } 01042 } 01043 01044 $this->currentLang = $code; 01045 $this->batch = array(); 01046 } 01047 01048 public function finishWrite() { 01049 if ( $this->readOnly ) { 01050 return; 01051 } 01052 01053 if ( $this->batch ) { 01054 $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ ); 01055 } 01056 01057 $this->dbw->commit( __METHOD__ ); 01058 $this->currentLang = null; 01059 $this->dbw = null; 01060 $this->batch = array(); 01061 $this->writesDone = true; 01062 } 01063 01064 public function set( $key, $value ) { 01065 if ( $this->readOnly ) { 01066 return; 01067 } 01068 01069 if ( is_null( $this->currentLang ) ) { 01070 throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' ); 01071 } 01072 01073 $this->batch[] = array( 01074 'lc_lang' => $this->currentLang, 01075 'lc_key' => $key, 01076 'lc_value' => serialize( $value ) ); 01077 01078 if ( count( $this->batch ) >= 100 ) { 01079 $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ ); 01080 $this->batch = array(); 01081 } 01082 } 01083 } 01084 01097 class LCStore_CDB implements LCStore { 01098 var $readers, $writer, $currentLang, $directory; 01099 01100 function __construct( $conf = array() ) { 01101 global $wgCacheDirectory; 01102 01103 if ( isset( $conf['directory'] ) ) { 01104 $this->directory = $conf['directory']; 01105 } else { 01106 $this->directory = $wgCacheDirectory; 01107 } 01108 } 01109 01110 public function get( $code, $key ) { 01111 if ( !isset( $this->readers[$code] ) ) { 01112 $fileName = $this->getFileName( $code ); 01113 01114 if ( !file_exists( $fileName ) ) { 01115 $this->readers[$code] = false; 01116 } else { 01117 $this->readers[$code] = CdbReader::open( $fileName ); 01118 } 01119 } 01120 01121 if ( !$this->readers[$code] ) { 01122 return null; 01123 } else { 01124 $value = $this->readers[$code]->get( $key ); 01125 01126 if ( $value === false ) { 01127 return null; 01128 } 01129 return unserialize( $value ); 01130 } 01131 } 01132 01133 public function startWrite( $code ) { 01134 if ( !file_exists( $this->directory ) ) { 01135 if ( !wfMkdirParents( $this->directory, null, __METHOD__ ) ) { 01136 throw new MWException( "Unable to create the localisation store " . 01137 "directory \"{$this->directory}\"" ); 01138 } 01139 } 01140 01141 // Close reader to stop permission errors on write 01142 if ( !empty( $this->readers[$code] ) ) { 01143 $this->readers[$code]->close(); 01144 } 01145 01146 $this->writer = CdbWriter::open( $this->getFileName( $code ) ); 01147 $this->currentLang = $code; 01148 } 01149 01150 public function finishWrite() { 01151 // Close the writer 01152 $this->writer->close(); 01153 $this->writer = null; 01154 unset( $this->readers[$this->currentLang] ); 01155 $this->currentLang = null; 01156 } 01157 01158 public function set( $key, $value ) { 01159 if ( is_null( $this->writer ) ) { 01160 throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' ); 01161 } 01162 $this->writer->set( $key, serialize( $value ) ); 01163 } 01164 01165 protected function getFileName( $code ) { 01166 if ( strval( $code ) === '' || strpos( $code, '/' ) !== false ) { 01167 throw new MWException( __METHOD__ . ": Invalid language \"$code\"" ); 01168 } 01169 return "{$this->directory}/l10n_cache-$code.cdb"; 01170 } 01171 } 01172 01176 class LCStore_Null implements LCStore { 01177 public function get( $code, $key ) { 01178 return null; 01179 } 01180 01181 public function startWrite( $code ) {} 01182 public function finishWrite() {} 01183 public function set( $key, $value ) {} 01184 } 01185 01190 class LocalisationCache_BulkLoad extends LocalisationCache { 01195 var $fileCache = array(); 01196 01202 var $mruLangs = array(); 01203 01207 var $maxLoadedLangs = 10; 01208 01214 protected function readPHPFile( $fileName, $fileType ) { 01215 $serialize = $fileType === 'core'; 01216 if ( !isset( $this->fileCache[$fileName][$fileType] ) ) { 01217 $data = parent::readPHPFile( $fileName, $fileType ); 01218 01219 if ( $serialize ) { 01220 $encData = serialize( $data ); 01221 } else { 01222 $encData = $data; 01223 } 01224 01225 $this->fileCache[$fileName][$fileType] = $encData; 01226 01227 return $data; 01228 } elseif ( $serialize ) { 01229 return unserialize( $this->fileCache[$fileName][$fileType] ); 01230 } else { 01231 return $this->fileCache[$fileName][$fileType]; 01232 } 01233 } 01234 01240 public function getItem( $code, $key ) { 01241 unset( $this->mruLangs[$code] ); 01242 $this->mruLangs[$code] = true; 01243 return parent::getItem( $code, $key ); 01244 } 01245 01252 public function getSubitem( $code, $key, $subkey ) { 01253 unset( $this->mruLangs[$code] ); 01254 $this->mruLangs[$code] = true; 01255 return parent::getSubitem( $code, $key, $subkey ); 01256 } 01257 01261 public function recache( $code ) { 01262 parent::recache( $code ); 01263 unset( $this->mruLangs[$code] ); 01264 $this->mruLangs[$code] = true; 01265 $this->trimCache(); 01266 } 01267 01271 public function unload( $code ) { 01272 unset( $this->mruLangs[$code] ); 01273 parent::unload( $code ); 01274 } 01275 01279 protected function trimCache() { 01280 while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) { 01281 reset( $this->mruLangs ); 01282 $code = key( $this->mruLangs ); 01283 wfDebug( __METHOD__ . ": unloading $code\n" ); 01284 $this->unload( $code ); 01285 } 01286 } 01287 01288 }