23 define(
'MW_LC_VERSION', 2 );
106 'fallback',
'namespaceNames',
'bookstoreList',
107 'magicWords',
'messages',
'rtl',
'capitalizeAllNouns',
'digitTransformTable',
108 'separatorTransformTable',
'fallback8bitEncoding',
'linkPrefixExtension',
109 'linkTrail',
'linkPrefixCharset',
'namespaceAliases',
110 'dateFormats',
'datePreferences',
'datePreferenceMigrationMap',
111 'defaultDateFormat',
'extraUserToggles',
'specialPageAliases',
112 'imageFiles',
'preloadedMessages',
'namespaceGenderAliases',
113 'digitGroupingPattern',
'pluralRules',
'pluralRuleTypes',
'compiledPluralRules',
121 'dateFormats',
'imageFiles',
'preloadedMessages'
191 $storeConf =
array();
192 if ( !empty(
$conf[
'storeClass'] ) ) {
193 $storeClass =
$conf[
'storeClass'];
195 switch (
$conf[
'store'] ) {
198 $storeClass =
'LCStoreCDB';
201 $storeClass =
'LCStoreDB';
204 $storeClass =
'LCStoreAccel';
207 $storeClass = $wgCacheDirectory ?
'LCStoreCDB' :
'LCStoreDB';
211 'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.' );
215 wfDebugLog(
'caches', get_class( $this ) .
": using store $storeClass" );
216 if ( !empty(
$conf[
'storeDirectory'] ) ) {
217 $storeConf[
'directory'] =
$conf[
'storeDirectory'];
220 $this->store =
new $storeClass( $storeConf );
221 foreach (
array(
'manualRecache',
'forceRecache' )
as $var ) {
222 if ( isset(
$conf[$var] ) ) {
223 $this->$var =
$conf[$var];
235 if ( $this->mergeableKeys ===
null ) {
236 $this->mergeableKeys = array_flip( array_merge(
237 self::$mergeableMapKeys,
238 self::$mergeableListKeys,
239 self::$mergeableAliasListKeys,
240 self::$optionalMergeKeys,
245 return isset( $this->mergeableKeys[$key] );
257 public function getItem( $code, $key ) {
258 if ( !isset( $this->loadedItems[$code][$key] ) ) {
264 if ( $key ===
'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
265 return $this->shallowFallbacks[$code];
268 return $this->
data[$code][$key];
278 public function getSubitem( $code, $key, $subkey ) {
279 if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
280 !isset( $this->loadedItems[$code][$key] )
287 if ( isset( $this->
data[$code][$key][$subkey] ) ) {
288 return $this->
data[$code][$key][$subkey];
307 if ( in_array( $key, self::$splitKeys ) ) {
308 return $this->
getSubitem( $code,
'list', $key );
310 $item = $this->
getItem( $code, $key );
311 if ( is_array( $item ) ) {
312 return array_keys( $item );
324 protected function loadItem( $code, $key ) {
325 if ( !isset( $this->initialisedLangs[$code] ) ) {
330 if ( isset( $this->loadedItems[$code][$key] ) ) {
334 if ( isset( $this->shallowFallbacks[$code] ) ) {
335 $this->
loadItem( $this->shallowFallbacks[$code], $key );
340 if ( in_array( $key, self::$splitKeys ) ) {
341 $subkeyList = $this->
getSubitem( $code,
'list', $key );
342 foreach ( $subkeyList
as $subkey ) {
343 if ( isset( $this->
data[$code][$key][$subkey] ) ) {
346 $this->
data[$code][$key][$subkey] = $this->
getSubitem( $code, $key, $subkey );
349 $this->
data[$code][$key] = $this->store->get( $code, $key );
352 $this->loadedItems[$code][$key] =
true;
361 protected function loadSubitem( $code, $key, $subkey ) {
362 if ( !in_array( $key, self::$splitKeys ) ) {
368 if ( !isset( $this->initialisedLangs[$code] ) ) {
373 if ( isset( $this->loadedItems[$code][$key] ) ||
374 isset( $this->loadedSubitems[$code][$key][$subkey] )
379 if ( isset( $this->shallowFallbacks[$code] ) ) {
380 $this->
loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
385 $value = $this->store->get( $code,
"$key:$subkey" );
387 $this->loadedSubitems[$code][$key][$subkey] =
true;
398 if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
399 wfDebug( __METHOD__ .
"($code): forced reload\n" );
404 $deps = $this->store->get( $code,
'deps' );
405 $keys = $this->store->get( $code,
'list' );
406 $preload = $this->store->get( $code,
'preload' );
408 if ( $deps ===
null ||
$keys ===
null || $preload ===
null ) {
409 wfDebug( __METHOD__ .
"($code): cache missing, need to make one\n" );
414 foreach ( $deps
as $dep ) {
420 wfDebug( __METHOD__ .
"($code): cache for $code expired due to " .
421 get_class( $dep ) .
"\n" );
436 if ( isset( $this->initialisedLangs[$code] ) ) {
440 $this->initialisedLangs[$code] =
true;
442 # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
449 # Recache the data if necessary
450 if ( !$this->manualRecache && $this->
isExpired( $code ) ) {
453 } elseif ( $code ===
'en' ) {
454 throw new MWException(
'MessagesEn.php is missing.' );
463 $preload = $this->
getItem( $code,
'preload' );
464 if ( $preload ===
null ) {
465 if ( $this->manualRecache ) {
467 if ( $code ===
'en' ) {
468 throw new MWException(
'No localisation cache found for English. ' .
469 'Please run maintenance/rebuildLocalisationCache.php.' );
475 throw new MWException(
'Invalid or missing localisation cache.' );
478 $this->
data[$code] = $preload;
479 foreach ( $preload
as $key => $item ) {
480 if ( in_array( $key, self::$splitKeys ) ) {
481 foreach ( $item
as $subkey => $subitem ) {
482 $this->loadedSubitems[$code][$key][$subkey] =
true;
485 $this->loadedItems[$code][$key] =
true;
497 $this->
data[$primaryCode] =& $this->
data[$fallbackCode];
498 $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
499 $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
500 $this->shallowFallbacks[$primaryCode] = $fallbackCode;
510 protected function readPHPFile( $_fileName, $_fileType ) {
514 $_apcEnabled = ini_set(
'apc.cache_by_default',
'0' );
520 ini_set(
'apc.cache_by_default', $_apcEnabled );
523 if ( $_fileType ==
'core' || $_fileType ==
'extension' ) {
524 $data = compact( self::$allKeys );
525 } elseif ( $_fileType ==
'aliases' ) {
526 $data = compact(
'aliases' );
529 throw new MWException( __METHOD__ .
": Invalid file type: $_fileType" );
545 if ( !is_readable( $fileName ) ) {
551 $json = file_get_contents( $fileName );
552 if ( $json ===
false ) {
559 if (
$data ===
null ) {
562 throw new MWException( __METHOD__ .
": Invalid JSON file: $fileName" );
566 foreach (
$data as $key => $unused ) {
567 if ( $key ===
'' || $key[0] ===
'@' ) {
568 unset(
$data[$key] );
584 if ( $rules ===
null ) {
595 return $compiledRules;
604 if ( $this->pluralRules ===
null ) {
607 if ( !isset( $this->pluralRules[$code] ) ) {
610 return $this->pluralRules[$code];
620 if ( $this->pluralRuleTypes ===
null ) {
623 if ( !isset( $this->pluralRuleTypes[$code] ) ) {
626 return $this->pluralRuleTypes[$code];
635 $cldrPlural =
"$IP/languages/data/plurals.xml";
636 $mwPlural =
"$IP/languages/data/plurals-mediawiki.xml";
639 if ( file_exists( $mwPlural ) ) {
650 $doc =
new DOMDocument;
651 $doc->load( $fileName );
652 $rulesets = $doc->getElementsByTagName(
"pluralRules" );
653 foreach ( $rulesets
as $ruleset ) {
654 $codes = $ruleset->getAttribute(
'locales' );
656 $ruleTypes =
array();
657 $ruleElements = $ruleset->getElementsByTagName(
"pluralRule" );
658 foreach ( $ruleElements
as $elt ) {
659 $ruleType = $elt->getAttribute(
'count' );
660 if ( $ruleType ===
'other' ) {
664 $rules[] = $elt->nodeValue;
665 $ruleTypes[] = $ruleType;
667 foreach ( explode(
' ', $codes )
as $code ) {
668 $this->pluralRules[$code] = $rules;
669 $this->pluralRuleTypes[$code] = $ruleTypes;
686 if ( !file_exists( $fileName ) ) {
693 # Load CLDR plural rules for JavaScript
697 # Load plural rule types
700 $deps[
'plurals'] =
new FileDependency(
"$IP/languages/data/plurals.xml" );
701 $deps[
'plurals-mw'] =
new FileDependency(
"$IP/languages/data/plurals-mediawiki.xml" );
716 if ( !is_null(
$value ) ) {
717 if ( !is_null( $fallbackValue ) ) {
718 if ( in_array( $key, self::$mergeableMapKeys ) ) {
720 } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
721 $value = array_unique( array_merge( $fallbackValue,
$value ) );
722 } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
723 $value = array_merge_recursive(
$value, $fallbackValue );
724 } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
725 if ( !empty(
$value[
'inherit'] ) ) {
729 if ( isset(
$value[
'inherit'] ) ) {
730 unset(
$value[
'inherit'] );
732 } elseif ( in_array( $key, self::$magicWordKeys ) ) {
746 foreach ( $fallbackValue
as $magicName => $fallbackInfo ) {
747 if ( !isset(
$value[$magicName] ) ) {
748 $value[$magicName] = $fallbackInfo;
750 $oldSynonyms = array_slice( $fallbackInfo, 1 );
751 $newSynonyms = array_slice(
$value[$magicName], 1 );
752 $synonyms = array_values( array_unique( array_merge(
753 $newSynonyms, $oldSynonyms ) ) );
754 $value[$magicName] = array_merge(
array( $fallbackInfo[0] ), $synonyms );
774 foreach ( $codeSequence
as $code ) {
775 if ( isset( $fallbackValue[$code] ) ) {
790 public function recache( $code ) {
796 throw new MWException(
"Invalid language code requested" );
798 $this->recachedLangs[$code] =
true;
801 $initialData = array_combine(
803 array_fill( 0, count( self::$allKeys ),
null ) );
804 $coreData = $initialData;
807 # Load the primary localisation from the source file
809 if (
$data ===
false ) {
810 wfDebug( __METHOD__ .
": no localisation file for $code, using fallback to en\n" );
811 $coreData[
'fallback'] =
'en';
813 wfDebug( __METHOD__ .
": got localisation for $code from source\n" );
815 # Merge primary localisation
821 # Fill in the fallback if it's not there already
822 if ( is_null( $coreData[
'fallback'] ) ) {
823 $coreData[
'fallback'] = $code ===
'en' ?
false :
'en';
825 if ( $coreData[
'fallback'] ===
false ) {
826 $coreData[
'fallbackSequence'] =
array();
828 $coreData[
'fallbackSequence'] = array_map(
'trim', explode(
',', $coreData[
'fallback'] ) );
829 $len = count( $coreData[
'fallbackSequence'] );
831 # Ensure that the sequence ends at en
832 if ( $coreData[
'fallbackSequence'][$len - 1] !==
'en' ) {
833 $coreData[
'fallbackSequence'][] =
'en';
836 # Load the fallback localisation item by item and merge it
837 foreach ( $coreData[
'fallbackSequence']
as $fbCode ) {
838 # Load the secondary localisation from the source file to
839 # avoid infinite cycles on cyclic fallbacks
841 if ( $fbData ===
false ) {
845 foreach ( self::$allKeys
as $key ) {
846 if ( !isset( $fbData[$key] ) ) {
850 if ( is_null( $coreData[$key] ) || $this->
isMergeableKey( $key ) ) {
851 $this->
mergeItem( $key, $coreData[$key], $fbData[$key] );
857 $codeSequence = array_merge(
array( $code ), $coreData[
'fallbackSequence'] );
859 # Load core messages and the extension localisations.
861 $allData = $initialData;
862 foreach ( $wgMessagesDirs
as $dirs ) {
864 foreach ( $codeSequence
as $csCode ) {
865 $fileName =
"$dir/$csCode.json";
868 foreach (
$data as $key => $item ) {
869 $this->
mergeItem( $key, $allData[$key], $item );
878 if ( isset( $wgMessagesDirs[$extension] ) ) {
879 # Already loaded the JSON files for this extension; skip the PHP shim
886 foreach (
$data as $key => $item ) {
897 # Merge core data into extension data
898 foreach ( $coreData
as $key => $item ) {
899 $this->
mergeItem( $key, $allData[$key], $item );
903 # Add cache dependencies for any referenced globals
904 $deps[
'wgExtensionMessagesFiles'] =
new GlobalDependency(
'wgExtensionMessagesFiles' );
908 # Add dependencies to the cache entry
909 $allData[
'deps'] = $deps;
911 # Replace spaces with underscores in namespace names
912 $allData[
'namespaceNames'] = str_replace(
' ',
'_', $allData[
'namespaceNames'] );
914 # And do the same for special page aliases. $page is an array.
915 foreach ( $allData[
'specialPageAliases']
as &$page ) {
916 $page = str_replace(
' ',
'_', $page );
918 # Decouple the reference to prevent accidental damage
921 # If there were no plural rules, return an empty array
922 if ( $allData[
'pluralRules'] ===
null ) {
923 $allData[
'pluralRules'] =
array();
925 if ( $allData[
'compiledPluralRules'] ===
null ) {
926 $allData[
'compiledPluralRules'] =
array();
928 # If there were no plural rule types, return an empty array
929 if ( $allData[
'pluralRuleTypes'] ===
null ) {
930 $allData[
'pluralRuleTypes'] =
array();
934 $allData[
'list'] =
array();
935 foreach ( self::$splitKeys
as $key ) {
936 $allData[
'list'][$key] = array_keys( $allData[$key] );
940 wfRunHooks(
'LocalisationCacheRecache',
array( $this, $code, &$allData, &$purgeBlobs ) );
942 if ( is_null( $allData[
'namespaceNames'] ) ) {
944 throw new MWException( __METHOD__ .
': Localisation data failed sanity check! ' .
945 'Check that your languages/messages/MessagesEn.php file is intact.' );
948 # Set the preload key
951 # Save to the process cache and register the items loaded
952 $this->
data[$code] = $allData;
953 foreach ( $allData
as $key => $item ) {
954 $this->loadedItems[$code][$key] =
true;
957 # Save to the persistent cache
959 $this->store->startWrite( $code );
960 foreach ( $allData
as $key =>
$value ) {
961 if ( in_array( $key, self::$splitKeys ) ) {
962 foreach (
$value as $subkey => $subvalue ) {
963 $this->store->set(
"$key:$subkey", $subvalue );
966 $this->store->set( $key,
$value );
969 $this->store->finishWrite();
972 # Clear out the MessageBlobStore
973 # HACK: If using a null (i.e. disabled) storage backend, we
974 # can't write to the MessageBlobStore either
975 if ( $purgeBlobs && !$this->store instanceof
LCStoreNull ) {
992 foreach ( self::$preloadedKeys
as $key ) {
993 $preload[$key] =
$data[$key];
996 foreach (
$data[
'preloadedMessages']
as $subkey ) {
997 if ( isset(
$data[
'messages'][$subkey] ) ) {
998 $subitem =
$data[
'messages'][$subkey];
1002 $preload[
'messages'][$subkey] = $subitem;
1013 public function unload( $code ) {
1014 unset( $this->
data[$code] );
1015 unset( $this->loadedItems[$code] );
1016 unset( $this->loadedSubitems[$code] );
1017 unset( $this->initialisedLangs[$code] );
1018 unset( $this->shallowFallbacks[$code] );
1020 foreach ( $this->shallowFallbacks
as $shallowCode => $fbCode ) {
1021 if ( $fbCode === $code ) {
1022 $this->
unload( $shallowCode );
1031 foreach ( $this->initialisedLangs
as $lang => $unused ) {
1041 $this->manualRecache =
false;
1068 function get( $code, $key );
1087 function set( $key,
$value );
1103 public function get( $code, $key ) {
1104 $k =
wfMemcKey(
'l10n', $code,
'k', $key );
1105 $r = $this->
cache->get( $k );
1107 return $r ===
false ? null : $r;
1115 $this->
cache->delete( $k );
1118 $this->currentLang = $code;
1119 $this->keys =
array();
1123 if ( $this->currentLang ) {
1124 $k =
wfMemcKey(
'l10n', $this->currentLang,
'l' );
1125 $this->
cache->set( $k, array_keys( $this->keys ) );
1127 $this->currentLang =
null;
1128 $this->keys =
array();
1131 public function set( $key,
$value ) {
1132 if ( $this->currentLang ) {
1133 $k =
wfMemcKey(
'l10n', $this->currentLang,
'k', $key );
1134 $this->keys[$k] =
true;
1155 public function get( $code, $key ) {
1156 if ( $this->writesDone ) {
1161 $row = $db->selectRow(
'l10n_cache',
array(
'lc_value' ),
1162 array(
'lc_lang' => $code,
'lc_key' => $key ), __METHOD__ );
1164 return unserialize( $db->decodeBlob( $row->lc_value ) );
1171 if ( $this->readOnly ) {
1176 throw new MWException( __METHOD__ .
": Invalid language \"$code\"" );
1181 $this->dbw->begin( __METHOD__ );
1182 $this->dbw->delete(
'l10n_cache',
array(
'lc_lang' => $code ), __METHOD__ );
1184 if ( $this->dbw->wasReadOnlyError() ) {
1185 $this->readOnly =
true;
1186 $this->dbw->rollback( __METHOD__ );
1194 $this->currentLang = $code;
1199 if ( $this->readOnly ) {
1203 if ( $this->
batch ) {
1204 $this->dbw->insert(
'l10n_cache', $this->
batch, __METHOD__ );
1207 $this->dbw->commit( __METHOD__ );
1208 $this->currentLang =
null;
1211 $this->writesDone =
true;
1214 public function set( $key,
$value ) {
1215 if ( $this->readOnly ) {
1219 if ( is_null( $this->currentLang ) ) {
1220 throw new MWException( __CLASS__ .
': must call startWrite() before calling set()' );
1224 'lc_lang' => $this->currentLang,
1226 'lc_value' => $this->dbw->encodeBlob( serialize(
$value ) ) );
1228 if ( count( $this->
batch ) >= 100 ) {
1229 $this->dbw->insert(
'l10n_cache', $this->
batch, __METHOD__ );
1261 global $wgCacheDirectory;
1263 if ( isset( $conf[
'directory'] ) ) {
1270 public function get( $code, $key ) {
1271 if ( !isset( $this->readers[$code] ) ) {
1274 $this->readers[$code] =
false;
1275 if ( file_exists( $fileName ) ) {
1279 wfDebug( __METHOD__ .
": unable to open cdb file for reading\n" );
1284 if ( !$this->readers[$code] ) {
1289 $value = $this->readers[$code]->get( $key );
1291 wfDebug( __METHOD__ .
": CdbException caught, error message was "
1292 .
$e->getMessage() .
"\n" );
1294 if (
$value ===
false ) {
1298 return unserialize(
$value );
1303 if ( !file_exists( $this->
directory ) ) {
1305 throw new MWException(
"Unable to create the localisation store " .
1306 "directory \"{$this->directory}\"" );
1311 if ( !empty( $this->readers[$code] ) ) {
1312 $this->readers[$code]->close();
1320 $this->currentLang = $code;
1331 unset( $this->readers[$this->currentLang] );
1332 $this->currentLang =
null;
1335 public function set( $key,
$value ) {
1336 if ( is_null( $this->
writer ) ) {
1337 throw new MWException( __CLASS__ .
': must call startWrite() before calling set()' );
1347 if ( strval( $code ) ===
'' || strpos( $code,
'/' ) !==
false ) {
1348 throw new MWException( __METHOD__ .
": Invalid language \"$code\"" );
1351 return "{$this->directory}/l10n_cache-$code.cdb";
1359 public function get( $code, $key ) {
1369 public function set( $key,
$value ) {
1401 protected function readPHPFile( $fileName, $fileType ) {
1402 $serialize = $fileType ===
'core';
1403 if ( !isset( $this->fileCache[$fileName][$fileType] ) ) {
1404 $data = parent::readPHPFile( $fileName, $fileType );
1407 $encData = serialize(
$data );
1412 $this->fileCache[$fileName][$fileType] = $encData;
1415 } elseif ( $serialize ) {
1416 return unserialize( $this->fileCache[$fileName][$fileType] );
1418 return $this->fileCache[$fileName][$fileType];
1427 public function getItem( $code, $key ) {
1428 unset( $this->mruLangs[$code] );
1429 $this->mruLangs[$code] =
true;
1431 return parent::getItem( $code, $key );
1440 public function getSubitem( $code, $key, $subkey ) {
1441 unset( $this->mruLangs[$code] );
1442 $this->mruLangs[$code] =
true;
1444 return parent::getSubitem( $code, $key, $subkey );
1450 public function recache( $code ) {
1451 parent::recache( $code );
1452 unset( $this->mruLangs[$code] );
1453 $this->mruLangs[$code] =
true;
1460 public function unload( $code ) {
1461 unset( $this->mruLangs[$code] );
1462 parent::unload( $code );
1469 while ( count( $this->
data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) {
1470 reset( $this->mruLangs );
1471 $code =
key( $this->mruLangs );
1472 wfDebug( __METHOD__ .
": unloading $code\n" );