Index: trunk/phase3/maintenance/archives/patch-l10n_cache.sql |
— | — | @@ -0,0 +1,8 @@ |
| 2 | +-- Table for storing localisation data |
| 3 | +CREATE TABLE /*_*/l10n_cache ( |
| 4 | + lc_lang varbinary(32) NOT NULL, |
| 5 | + lc_key varchar(255) NOT NULL, |
| 6 | + lc_value mediumblob NOT NULL |
| 7 | +); |
| 8 | +CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key); |
| 9 | + |
Property changes on: trunk/phase3/maintenance/archives/patch-l10n_cache.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 10 | + native |
Index: trunk/phase3/maintenance/updaters.inc |
— | — | @@ -161,6 +161,7 @@ |
162 | 162 | array( 'add_table', 'log_search', 'patch-log_search.sql' ), |
163 | 163 | array( 'do_log_search_population' ), |
164 | 164 | array( 'add_field', 'logging', 'log_user_text', 'patch-log_user_text.sql' ), |
| 165 | + array( 'add_table', 'l10n_cache', 'patch-l10n_cache.sql' ), |
165 | 166 | ), |
166 | 167 | |
167 | 168 | 'sqlite' => array( |
— | — | @@ -180,6 +181,7 @@ |
181 | 182 | array( 'add_table', 'log_search', 'patch-log_search.sql' ), |
182 | 183 | array( 'do_log_search_population' ), |
183 | 184 | array( 'add_field', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ), |
| 185 | + array( 'add_table', 'l10n_cache', 'patch-l10n_cache.sql' ), |
184 | 186 | ), |
185 | 187 | ); |
186 | 188 | |
Index: trunk/phase3/maintenance/rebuildLocalisationCache.php |
— | — | @@ -0,0 +1,41 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Rebuild the localisation cache. Useful if you disabled automatic updates |
| 6 | + * using $wgLocalisationCacheConf['manualRecache'] = true; |
| 7 | + * |
| 8 | + * Usage: |
| 9 | + * php rebuildLocalisationCache.php [--force] |
| 10 | + * |
| 11 | + * Use --force to rebuild all files, even the ones that are not out of date. |
| 12 | + */ |
| 13 | + |
| 14 | +require( dirname(__FILE__).'/commandLine.inc' ); |
| 15 | +ini_set( 'memory_limit', '200M' ); |
| 16 | + |
| 17 | +$force = isset( $options['force'] ); |
| 18 | + |
| 19 | +$conf = $wgLocalisationCacheConf; |
| 20 | +$conf['manualRecache'] = false; // Allow fallbacks to create CDB files |
| 21 | +if ( $force ) { |
| 22 | + $conf['forceRecache'] = true; |
| 23 | +} |
| 24 | +$lc = new LocalisationCache_BulkLoad( $conf ); |
| 25 | + |
| 26 | +$codes = array_keys( Language::getLanguageNames( true ) ); |
| 27 | +sort( $codes ); |
| 28 | +$numRebuilt = 0; |
| 29 | +foreach ( $codes as $code ) { |
| 30 | + if ( $force || $lc->isExpired( $code ) ) { |
| 31 | + echo "Rebuilding $code...\n"; |
| 32 | + $lc->recache( $code ); |
| 33 | + $numRebuilt++; |
| 34 | + } |
| 35 | +} |
| 36 | +echo "$numRebuilt languages rebuilt out of " . count( $codes ) . ".\n"; |
| 37 | +if ( $numRebuilt == 0 ) { |
| 38 | + echo "Use --force to rebuild the caches which are still fresh.\n"; |
| 39 | +} |
| 40 | + |
| 41 | + |
| 42 | + |
Property changes on: trunk/phase3/maintenance/rebuildLocalisationCache.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 43 | + native |
Index: trunk/phase3/maintenance/tables.sql |
— | — | @@ -1310,4 +1310,15 @@ |
1311 | 1311 | vt_tag varchar(255) NOT NULL PRIMARY KEY |
1312 | 1312 | ) /*$wgDBTableOptions*/; |
1313 | 1313 | |
| 1314 | +-- Table for storing localisation data |
| 1315 | +CREATE TABLE /*_*/l10n_cache ( |
| 1316 | + -- Language code |
| 1317 | + lc_lang varbinary(32) NOT NULL, |
| 1318 | + -- Cache key |
| 1319 | + lc_key varchar(255) NOT NULL, |
| 1320 | + -- Value |
| 1321 | + lc_value mediumblob NOT NULL |
| 1322 | +); |
| 1323 | +CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key); |
| 1324 | + |
1314 | 1325 | -- vim: sw=2 sts=2 et |
Index: trunk/phase3/docs/hooks.txt |
— | — | @@ -833,13 +833,15 @@ |
834 | 834 | &$result: Set this to either true (passes) or the key for a message error |
835 | 835 | $user: User the password is being validated for |
836 | 836 | |
837 | | -'LanguageGetMagic': Use this to define synonyms of magic words depending |
838 | | -of the language |
| 837 | +'LanguageGetMagic': DEPRECATED, use $magicWords in a file listed in |
| 838 | +$wgExtensionMessagesFiles instead. |
| 839 | +Use this to define synonyms of magic words depending of the language |
839 | 840 | $magicExtensions: associative array of magic words synonyms |
840 | 841 | $lang: laguage code (string) |
841 | 842 | |
842 | | -'LanguageGetSpecialPageAliases': Use to define aliases of special pages |
843 | | -names depending of the language |
| 843 | +'LanguageGetSpecialPageAliases': DEPRECATED, use $specialPageAliases in a file |
| 844 | +listed in $wgExtensionMessagesFiles instead. |
| 845 | +Use to define aliases of special pages names depending of the language |
844 | 846 | $specialPageAliases: associative array of magic words synonyms |
845 | 847 | $lang: laguage code (string) |
846 | 848 | |
— | — | @@ -900,10 +902,6 @@ |
901 | 903 | 'ListDefinedTags': When trying to find all defined tags. |
902 | 904 | &$tags: The list of tags. |
903 | 905 | |
904 | | -'LoadAllMessages': called by MessageCache::loadAllMessages() to load extensions |
905 | | -messages |
906 | | -&$messageCache: The MessageCache object |
907 | | - |
908 | 906 | 'LoadExtensionSchemaUpdates': called by maintenance/updaters.inc when upgrading |
909 | 907 | database schema |
910 | 908 | |
— | — | @@ -1000,13 +998,6 @@ |
1001 | 999 | $title: name of the page changed. |
1002 | 1000 | $text: new contents of the page. |
1003 | 1001 | |
1004 | | -'MessageNotInMwNs': When trying to get a message that isn't found in the |
1005 | | -MediaWiki namespace (but before checking the message files) |
1006 | | -&$message: message's content; can be changed |
1007 | | -$lckey: message's name |
1008 | | -$langcode: language code |
1009 | | -$isFullKey: specifies whether $lckey is a two part key "msg/lang" |
1010 | | - |
1011 | 1002 | 'MonoBookTemplateToolboxEnd': Called by Monobook skin after toolbox links have |
1012 | 1003 | been rendered (useful for adding more) |
1013 | 1004 | Note: this is only run for the Monobook skin. To add items to the toolbox |
Index: trunk/phase3/cache/.htaccess |
— | — | @@ -0,0 +1 @@ |
| 2 | +Deny from all |
Index: trunk/phase3/includes/GlobalFunctions.php |
— | — | @@ -2932,42 +2932,9 @@ |
2933 | 2933 | |
2934 | 2934 | /** |
2935 | 2935 | * Load an extension messages file |
2936 | | - * |
2937 | | - * @param string $extensionName Name of extension to load messages from\for. |
2938 | | - * @param string $langcode Language to load messages for, or false for default |
2939 | | - * behvaiour (en, content language and user language). |
2940 | | - * @since r24808 (v1.11) Using this method of loading extension messages will not work |
2941 | | - * on MediaWiki prior to that |
| 2936 | + * @deprecated |
2942 | 2937 | */ |
2943 | 2938 | function wfLoadExtensionMessages( $extensionName, $langcode = false ) { |
2944 | | - global $wgExtensionMessagesFiles, $wgMessageCache, $wgLang, $wgContLang; |
2945 | | - |
2946 | | - #For recording whether extension message files have been loaded in a given language. |
2947 | | - static $loaded = array(); |
2948 | | - |
2949 | | - if( !array_key_exists( $extensionName, $loaded ) ) { |
2950 | | - $loaded[$extensionName] = array(); |
2951 | | - } |
2952 | | - |
2953 | | - if ( !isset($wgExtensionMessagesFiles[$extensionName]) ) { |
2954 | | - throw new MWException( "Messages file for extensions $extensionName is not defined" ); |
2955 | | - } |
2956 | | - |
2957 | | - if( !$langcode && !array_key_exists( '*', $loaded[$extensionName] ) ) { |
2958 | | - # Just do en, content language and user language. |
2959 | | - $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], false ); |
2960 | | - # Mark that they have been loaded. |
2961 | | - $loaded[$extensionName]['en'] = true; |
2962 | | - $loaded[$extensionName][$wgLang->getCode()] = true; |
2963 | | - $loaded[$extensionName][$wgContLang->getCode()] = true; |
2964 | | - # Mark that this part has been done to avoid weird if statements. |
2965 | | - $loaded[$extensionName]['*'] = true; |
2966 | | - } elseif( is_string( $langcode ) && !array_key_exists( $langcode, $loaded[$extensionName] ) ) { |
2967 | | - # Load messages for specified language. |
2968 | | - $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName], $langcode ); |
2969 | | - # Mark that they have been loaded. |
2970 | | - $loaded[$extensionName][$langcode] = true; |
2971 | | - } |
2972 | 2939 | } |
2973 | 2940 | |
2974 | 2941 | /** |
Index: trunk/phase3/includes/LocalisationCache.php |
— | — | @@ -0,0 +1,880 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +define( 'MW_LC_VERSION', 1 ); |
| 5 | + |
| 6 | +/** |
| 7 | + * Class for caching the contents of localisation files, Messages*.php |
| 8 | + * and *.i18n.php. |
| 9 | + * |
| 10 | + * An instance of this class is available using Language::getLocalisationCache(). |
| 11 | + * |
| 12 | + * The values retrieved from here are merged, containing items from extension |
| 13 | + * files, core messages files and the language fallback sequence (e.g. zh-cn -> |
| 14 | + * zh-hans -> en ). Some common errors are corrected, for example namespace |
| 15 | + * names with spaces instead of underscores, but heavyweight processing, such |
| 16 | + * as grammatical transformation, is done by the caller. |
| 17 | + */ |
| 18 | +class LocalisationCache { |
| 19 | + /** Configuration associative array */ |
| 20 | + var $conf; |
| 21 | + |
| 22 | + /** |
| 23 | + * True if recaching should only be done on an explicit call to recache(). |
| 24 | + * Setting this reduces the overhead of cache freshness checking, which |
| 25 | + * requires doing a stat() for every extension i18n file. |
| 26 | + */ |
| 27 | + var $manualRecache = false; |
| 28 | + |
| 29 | + /** |
| 30 | + * True to treat all files as expired until they are regenerated by this object. |
| 31 | + */ |
| 32 | + var $forceRecache = false; |
| 33 | + |
| 34 | + /** |
| 35 | + * The cache data. 3-d array, where the first key is the language code, |
| 36 | + * the second key is the item key e.g. 'messages', and the third key is |
| 37 | + * an item specific subkey index. Some items are not arrays and so for those |
| 38 | + * items, there are no subkeys. |
| 39 | + */ |
| 40 | + var $data = array(); |
| 41 | + |
| 42 | + /** |
| 43 | + * The persistent store object. An instance of LCStore. |
| 44 | + */ |
| 45 | + var $store; |
| 46 | + |
| 47 | + /** |
| 48 | + * A 2-d associative array, code/key, where presence indicates that the item |
| 49 | + * is loaded. Value arbitrary. |
| 50 | + * |
| 51 | + * For split items, if set, this indicates that all of the subitems have been |
| 52 | + * loaded. |
| 53 | + */ |
| 54 | + var $loadedItems = array(); |
| 55 | + |
| 56 | + /** |
| 57 | + * A 3-d associative array, code/key/subkey, where presence indicates that |
| 58 | + * the subitem is loaded. Only used for the split items, i.e. messages. |
| 59 | + */ |
| 60 | + var $loadedSubitems = array(); |
| 61 | + |
| 62 | + /** |
| 63 | + * An array where presence of a key indicates that that language has been |
| 64 | + * initialised. Initialisation includes checking for cache expiry and doing |
| 65 | + * any necessary updates. |
| 66 | + */ |
| 67 | + var $initialisedLangs = array(); |
| 68 | + |
| 69 | + /** |
| 70 | + * An array mapping non-existent pseudo-languages to fallback languages. This |
| 71 | + * is filled by initShallowFallback() when data is requested from a language |
| 72 | + * that lacks a Messages*.php file. |
| 73 | + */ |
| 74 | + var $shallowFallbacks = array(); |
| 75 | + |
| 76 | + /** |
| 77 | + * An array where the keys are codes that have been recached by this instance. |
| 78 | + */ |
| 79 | + var $recachedLangs = array(); |
| 80 | + |
| 81 | + /** |
| 82 | + * All item keys |
| 83 | + */ |
| 84 | + static public $allKeys = array( |
| 85 | + 'fallback', 'namespaceNames', 'mathNames', 'bookstoreList', |
| 86 | + 'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable', |
| 87 | + 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension', |
| 88 | + 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases', |
| 89 | + 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap', |
| 90 | + 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases', |
| 91 | + 'imageFiles', 'preloadedMessages', |
| 92 | + ); |
| 93 | + |
| 94 | + /** |
| 95 | + * Keys for items which consist of associative arrays, which may be merged |
| 96 | + * by a fallback sequence. |
| 97 | + */ |
| 98 | + static public $mergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames', |
| 99 | + 'dateFormats', 'defaultUserOptionOverrides', 'magicWords', 'imageFiles', |
| 100 | + 'preloadedMessages', |
| 101 | + ); |
| 102 | + |
| 103 | + /** |
| 104 | + * Keys for items which are a numbered array. |
| 105 | + */ |
| 106 | + static public $mergeableListKeys = array( 'extraUserToggles' ); |
| 107 | + |
| 108 | + /** |
| 109 | + * Keys for items which contain an array of arrays of equivalent aliases |
| 110 | + * for each subitem. The aliases may be merged by a fallback sequence. |
| 111 | + */ |
| 112 | + static public $mergeableAliasListKeys = array( 'specialPageAliases' ); |
| 113 | + |
| 114 | + /** |
| 115 | + * Keys for items which contain an associative array, and may be merged if |
| 116 | + * the primary value contains the special array key "inherit". That array |
| 117 | + * key is removed after the first merge. |
| 118 | + */ |
| 119 | + static public $optionalMergeKeys = array( 'bookstoreList' ); |
| 120 | + |
| 121 | + /** |
| 122 | + * Keys for items where the subitems are stored in the backend separately. |
| 123 | + */ |
| 124 | + static public $splitKeys = array( 'messages' ); |
| 125 | + |
| 126 | + /** |
| 127 | + * Keys which are loaded automatically by initLanguage() |
| 128 | + */ |
| 129 | + static public $preloadedKeys = array( 'dateFormats', 'namespaceNames', |
| 130 | + 'defaultUserOptionOverrides' ); |
| 131 | + |
| 132 | + /** |
| 133 | + * Constructor. |
| 134 | + * For constructor parameters, see the documentation in DefaultSettings.php |
| 135 | + * for $wgLocalisationCacheConf. |
| 136 | + */ |
| 137 | + function __construct( $conf ) { |
| 138 | + global $wgCacheDirectory; |
| 139 | + |
| 140 | + $this->conf = $conf; |
| 141 | + $this->data = array(); |
| 142 | + $this->loadedItems = array(); |
| 143 | + $this->loadedSubitems = array(); |
| 144 | + $this->initialisedLangs = array(); |
| 145 | + if ( !empty( $conf['storeClass'] ) ) { |
| 146 | + $storeClass = $conf['storeClass']; |
| 147 | + } else { |
| 148 | + switch ( $conf['store'] ) { |
| 149 | + case 'files': |
| 150 | + case 'file': |
| 151 | + $storeClass = 'LCStore_CDB'; |
| 152 | + break; |
| 153 | + case 'db': |
| 154 | + $storeClass = 'LCStore_DB'; |
| 155 | + break; |
| 156 | + case 'detect': |
| 157 | + $storeClass = $wgCacheDirectory ? 'LCStore_CDB' : 'LCStore_DB'; |
| 158 | + break; |
| 159 | + default: |
| 160 | + throw new MWException( |
| 161 | + 'Please set $wgLocalisationConf[\'store\'] to something sensible.' ); |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + wfDebug( get_class( $this ) . ": using store $storeClass\n" ); |
| 166 | + $this->store = new $storeClass; |
| 167 | + foreach ( array( 'manualRecache', 'forceRecache' ) as $var ) { |
| 168 | + if ( isset( $conf[$var] ) ) { |
| 169 | + $this->$var = $conf[$var]; |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + /** |
| 175 | + * Returns true if the given key is mergeable, that is, if it is an associative |
| 176 | + * array which can be merged through a fallback sequence. |
| 177 | + */ |
| 178 | + public function isMergeableKey( $key ) { |
| 179 | + if ( !isset( $this->mergeableKeys ) ) { |
| 180 | + $this->mergeableKeys = array_flip( array_merge( |
| 181 | + self::$mergeableMapKeys, |
| 182 | + self::$mergeableListKeys, |
| 183 | + self::$mergeableAliasListKeys, |
| 184 | + self::$optionalMergeKeys |
| 185 | + ) ); |
| 186 | + } |
| 187 | + return isset( $this->mergeableKeys[$key] ); |
| 188 | + } |
| 189 | + |
| 190 | + /** |
| 191 | + * Get a cache item. |
| 192 | + * |
| 193 | + * Warning: this may be slow for split items (messages), since it will |
| 194 | + * need to fetch all of the subitems from the cache individually. |
| 195 | + */ |
| 196 | + public function getItem( $code, $key ) { |
| 197 | + if ( !isset( $this->loadedItems[$code][$key] ) ) { |
| 198 | + wfProfileIn( __METHOD__.'-load' ); |
| 199 | + $this->loadItem( $code, $key ); |
| 200 | + wfProfileOut( __METHOD__.'-load' ); |
| 201 | + } |
| 202 | + if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) { |
| 203 | + return $this->shallowFallbacks[$code]; |
| 204 | + } |
| 205 | + return $this->data[$code][$key]; |
| 206 | + } |
| 207 | + |
| 208 | + /** |
| 209 | + * Get a subitem, for instance a single message for a given language. |
| 210 | + */ |
| 211 | + public function getSubitem( $code, $key, $subkey ) { |
| 212 | + if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) ) { |
| 213 | + if ( isset( $this->loadedItems[$code][$key] ) ) { |
| 214 | + if ( isset( $this->data[$code][$key][$subkey] ) ) { |
| 215 | + return $this->data[$code][$key][$subkey]; |
| 216 | + } else { |
| 217 | + return null; |
| 218 | + } |
| 219 | + } else { |
| 220 | + wfProfileIn( __METHOD__.'-load' ); |
| 221 | + $this->loadSubitem( $code, $key, $subkey ); |
| 222 | + wfProfileOut( __METHOD__.'-load' ); |
| 223 | + } |
| 224 | + } |
| 225 | + return $this->data[$code][$key][$subkey]; |
| 226 | + } |
| 227 | + |
| 228 | + /** |
| 229 | + * Load an item into the cache. |
| 230 | + */ |
| 231 | + protected function loadItem( $code, $key ) { |
| 232 | + if ( !isset( $this->initialisedLangs[$code] ) ) { |
| 233 | + $this->initLanguage( $code ); |
| 234 | + } |
| 235 | + // Check to see if initLanguage() loaded it for us |
| 236 | + if ( isset( $this->loadedItems[$code][$key] ) ) { |
| 237 | + return; |
| 238 | + } |
| 239 | + if ( isset( $this->shallowFallbacks[$code] ) ) { |
| 240 | + $this->loadItem( $this->shallowFallbacks[$code], $key ); |
| 241 | + return; |
| 242 | + } |
| 243 | + if ( in_array( $key, self::$splitKeys ) ) { |
| 244 | + $subkeyList = $this->getSubitem( $code, 'list', $key ); |
| 245 | + foreach ( $subkeyList as $subkey ) { |
| 246 | + if ( isset( $this->data[$code][$key][$subkey] ) ) { |
| 247 | + continue; |
| 248 | + } |
| 249 | + $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey ); |
| 250 | + } |
| 251 | + } else { |
| 252 | + $this->data[$code][$key] = $this->store->get( $code, $key ); |
| 253 | + } |
| 254 | + $this->loadedItems[$code][$key] = true; |
| 255 | + } |
| 256 | + |
| 257 | + /** |
| 258 | + * Load a subitem into the cache |
| 259 | + */ |
| 260 | + protected function loadSubitem( $code, $key, $subkey ) { |
| 261 | + if ( !in_array( $key, self::$splitKeys ) ) { |
| 262 | + $this->loadItem( $code, $key ); |
| 263 | + return; |
| 264 | + } |
| 265 | + if ( !isset( $this->initialisedLangs[$code] ) ) { |
| 266 | + $this->initLanguage( $code ); |
| 267 | + } |
| 268 | + // Check to see if initLanguage() loaded it for us |
| 269 | + if ( isset( $this->loadedSubitems[$code][$key][$subkey] ) ) { |
| 270 | + return; |
| 271 | + } |
| 272 | + if ( isset( $this->shallowFallbacks[$code] ) ) { |
| 273 | + $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey ); |
| 274 | + return; |
| 275 | + } |
| 276 | + $value = $this->store->get( $code, "$key:$subkey" ); |
| 277 | + $this->data[$code][$key][$subkey] = $value; |
| 278 | + $this->loadedSubitems[$code][$key][$subkey] = true; |
| 279 | + } |
| 280 | + |
| 281 | + /** |
| 282 | + * Returns true if the cache identified by $code is missing or expired. |
| 283 | + */ |
| 284 | + public function isExpired( $code ) { |
| 285 | + if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) { |
| 286 | + wfDebug( __METHOD__."($code): forced reload\n" ); |
| 287 | + return true; |
| 288 | + } |
| 289 | + |
| 290 | + $deps = $this->store->get( $code, 'deps' ); |
| 291 | + if ( $deps === null ) { |
| 292 | + wfDebug( __METHOD__."($code): cache missing, need to make one\n" ); |
| 293 | + return true; |
| 294 | + } |
| 295 | + foreach ( $deps as $dep ) { |
| 296 | + if ( $dep->isExpired() ) { |
| 297 | + wfDebug( __METHOD__."($code): cache for $code expired due to " . |
| 298 | + get_class( $dep ) . "\n" ); |
| 299 | + return true; |
| 300 | + } |
| 301 | + } |
| 302 | + return false; |
| 303 | + } |
| 304 | + |
| 305 | + /** |
| 306 | + * Initialise a language in this object. Rebuild the cache if necessary. |
| 307 | + */ |
| 308 | + protected function initLanguage( $code ) { |
| 309 | + if ( isset( $this->initialisedLangs[$code] ) ) { |
| 310 | + return; |
| 311 | + } |
| 312 | + $this->initialisedLangs[$code] = true; |
| 313 | + |
| 314 | + # Recache the data if necessary |
| 315 | + if ( !$this->manualRecache && $this->isExpired( $code ) ) { |
| 316 | + if ( file_exists( Language::getMessagesFileName( $code ) ) ) { |
| 317 | + $this->recache( $code ); |
| 318 | + } elseif ( $code === 'en' ) { |
| 319 | + throw new MWException( 'MessagesEn.php is missing.' ); |
| 320 | + } else { |
| 321 | + $this->initShallowFallback( $code, 'en' ); |
| 322 | + } |
| 323 | + return; |
| 324 | + } |
| 325 | + |
| 326 | + # Preload some stuff |
| 327 | + $preload = $this->getItem( $code, 'preload' ); |
| 328 | + if ( $preload === null ) { |
| 329 | + if ( $this->manualRecache ) { |
| 330 | + // No Messages*.php file. Do shallow fallback to en. |
| 331 | + if ( $code === 'en' ) { |
| 332 | + throw new MWException( 'No localisation cache found for English. ' . |
| 333 | + 'Please run maintenance/rebuildLocalisationCache.php.' ); |
| 334 | + } |
| 335 | + $this->initShallowFallback( $code, 'en' ); |
| 336 | + return; |
| 337 | + } else { |
| 338 | + throw new MWException( 'Invalid or missing localisation cache.' ); |
| 339 | + } |
| 340 | + } |
| 341 | + $this->data[$code] = $preload; |
| 342 | + foreach ( $preload as $key => $item ) { |
| 343 | + if ( in_array( $key, self::$splitKeys ) ) { |
| 344 | + foreach ( $item as $subkey => $subitem ) { |
| 345 | + $this->loadedSubitems[$code][$key][$subkey] = true; |
| 346 | + } |
| 347 | + } else { |
| 348 | + $this->loadedItems[$code][$key] = true; |
| 349 | + } |
| 350 | + } |
| 351 | + } |
| 352 | + |
| 353 | + /** |
| 354 | + * Create a fallback from one language to another, without creating a |
| 355 | + * complete persistent cache. |
| 356 | + */ |
| 357 | + public function initShallowFallback( $primaryCode, $fallbackCode ) { |
| 358 | + $this->data[$primaryCode] =& $this->data[$fallbackCode]; |
| 359 | + $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode]; |
| 360 | + $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode]; |
| 361 | + $this->shallowFallbacks[$primaryCode] = $fallbackCode; |
| 362 | + } |
| 363 | + |
| 364 | + /** |
| 365 | + * Read a PHP file containing localisation data. |
| 366 | + */ |
| 367 | + protected function readPHPFile( $_fileName, $_fileType ) { |
| 368 | + // Disable APC caching |
| 369 | + $_apcEnabled = ini_set( 'apc.enabled', '0' ); |
| 370 | + include( $_fileName ); |
| 371 | + ini_set( 'apc.enabled', $_apcEnabled ); |
| 372 | + |
| 373 | + if ( $_fileType == 'core' || $_fileType == 'extension' ) { |
| 374 | + $data = compact( self::$allKeys ); |
| 375 | + } elseif ( $_fileType == 'aliases' ) { |
| 376 | + $data = compact( 'aliases' ); |
| 377 | + } else { |
| 378 | + throw new MWException( __METHOD__.": Invalid file type: $_fileType" ); |
| 379 | + } |
| 380 | + return $data; |
| 381 | + } |
| 382 | + |
| 383 | + /** |
| 384 | + * Merge two localisation values, a primary and a fallback, overwriting the |
| 385 | + * primary value in place. |
| 386 | + */ |
| 387 | + protected function mergeItem( $key, &$value, $fallbackValue ) { |
| 388 | + if ( !is_null( $value ) ) { |
| 389 | + if ( !is_null( $fallbackValue ) ) { |
| 390 | + if ( in_array( $key, self::$mergeableMapKeys ) ) { |
| 391 | + $value = $value + $fallbackValue; |
| 392 | + } elseif ( in_array( $key, self::$mergeableListKeys ) ) { |
| 393 | + $value = array_unique( array_merge( $fallbackValue, $value ) ); |
| 394 | + } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) { |
| 395 | + $value = array_merge_recursive( $value, $fallbackValue ); |
| 396 | + } elseif ( in_array( $key, self::$optionalMergeKeys ) ) { |
| 397 | + if ( !empty( $value['inherit'] ) ) { |
| 398 | + $value = array_merge( $fallbackValue, $value ); |
| 399 | + } |
| 400 | + if ( isset( $value['inherit'] ) ) { |
| 401 | + unset( $value['inherit'] ); |
| 402 | + } |
| 403 | + } |
| 404 | + } |
| 405 | + } else { |
| 406 | + $value = $fallbackValue; |
| 407 | + } |
| 408 | + } |
| 409 | + |
| 410 | + /** |
| 411 | + * Given an array mapping language code to localisation value, such as is |
| 412 | + * found in extension *.i18n.php files, iterate through a fallback sequence |
| 413 | + * to merge the given data with an existing primary value. |
| 414 | + * |
| 415 | + * Returns true if any data from the extension array was used, false |
| 416 | + * otherwise. |
| 417 | + */ |
| 418 | + protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) { |
| 419 | + $used = false; |
| 420 | + foreach ( $codeSequence as $code ) { |
| 421 | + if ( isset( $fallbackValue[$code] ) ) { |
| 422 | + $this->mergeItem( $key, $value, $fallbackValue[$code] ); |
| 423 | + $used = true; |
| 424 | + } |
| 425 | + } |
| 426 | + return $used; |
| 427 | + } |
| 428 | + |
| 429 | + /** |
| 430 | + * Load localisation data for a given language for both core and extensions |
| 431 | + * and save it to the persistent cache store and the process cache |
| 432 | + */ |
| 433 | + public function recache( $code ) { |
| 434 | + static $recursionGuard = array(); |
| 435 | + global $wgExtensionMessagesFiles, $wgExtensionAliasesFiles; |
| 436 | + wfProfileIn( __METHOD__ ); |
| 437 | + |
| 438 | + if ( !$code ) { |
| 439 | + throw new MWException( "Invalid language code requested" ); |
| 440 | + } |
| 441 | + $this->recachedLangs[$code] = true; |
| 442 | + |
| 443 | + # Initial values |
| 444 | + $initialData = array_combine( |
| 445 | + self::$allKeys, |
| 446 | + array_fill( 0, count( self::$allKeys ), null ) ); |
| 447 | + $coreData = $initialData; |
| 448 | + $deps = array(); |
| 449 | + |
| 450 | + # Load the primary localisation from the source file |
| 451 | + $fileName = Language::getMessagesFileName( $code ); |
| 452 | + if ( !file_exists( $fileName ) ) { |
| 453 | + wfDebug( __METHOD__.": no localisation file for $code, using fallback to en\n" ); |
| 454 | + $coreData['fallback'] = 'en'; |
| 455 | + } else { |
| 456 | + $deps[] = new FileDependency( $fileName ); |
| 457 | + $data = $this->readPHPFile( $fileName, 'core' ); |
| 458 | + wfDebug( __METHOD__.": got localisation for $code from source\n" ); |
| 459 | + |
| 460 | + # Merge primary localisation |
| 461 | + foreach ( $data as $key => $value ) { |
| 462 | + $this->mergeItem( $key, $coreData[$key], $value ); |
| 463 | + } |
| 464 | + } |
| 465 | + |
| 466 | + # Fill in the fallback if it's not there already |
| 467 | + if ( is_null( $coreData['fallback'] ) ) { |
| 468 | + $coreData['fallback'] = $code === 'en' ? false : 'en'; |
| 469 | + } |
| 470 | + |
| 471 | + if ( $coreData['fallback'] !== false ) { |
| 472 | + # Guard against circular references |
| 473 | + if ( isset( $recursionGuard[$code] ) ) { |
| 474 | + throw new MWException( "Error: Circular fallback reference in language code $code" ); |
| 475 | + } |
| 476 | + $recursionGuard[$code] = true; |
| 477 | + |
| 478 | + # Load the fallback localisation item by item and merge it |
| 479 | + $deps = array_merge( $deps, $this->getItem( $coreData['fallback'], 'deps' ) ); |
| 480 | + foreach ( self::$allKeys as $key ) { |
| 481 | + if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) { |
| 482 | + $fallbackValue = $this->getItem( $coreData['fallback'], $key ); |
| 483 | + $this->mergeItem( $key, $coreData[$key], $fallbackValue ); |
| 484 | + } |
| 485 | + } |
| 486 | + $fallbackSequence = $this->getItem( $coreData['fallback'], 'fallbackSequence' ); |
| 487 | + array_unshift( $fallbackSequence, $coreData['fallback'] ); |
| 488 | + $coreData['fallbackSequence'] = $fallbackSequence; |
| 489 | + unset( $recursionGuard[$code] ); |
| 490 | + } else { |
| 491 | + $coreData['fallbackSequence'] = array(); |
| 492 | + } |
| 493 | + $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] ); |
| 494 | + |
| 495 | + # Load the extension localisations |
| 496 | + # This is done after the core because we know the fallback sequence now. |
| 497 | + # But it has a higher precedence for merging so that we can support things |
| 498 | + # like site-specific message overrides. |
| 499 | + $allData = $initialData; |
| 500 | + foreach ( $wgExtensionMessagesFiles as $fileName ) { |
| 501 | + $data = $this->readPHPFile( $fileName, 'extension' ); |
| 502 | + $used = false; |
| 503 | + foreach ( $data as $key => $item ) { |
| 504 | + $used = $used || |
| 505 | + $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ); |
| 506 | + } |
| 507 | + if ( $used ) { |
| 508 | + $deps[] = new FileDependency( $fileName ); |
| 509 | + } |
| 510 | + } |
| 511 | + |
| 512 | + # Load deprecated $wgExtensionAliasesFiles |
| 513 | + foreach ( $wgExtensionAliasesFiles as $fileName ) { |
| 514 | + $data = $this->readPHPFile( $fileName, 'aliases' ); |
| 515 | + if ( !isset( $data['aliases'] ) ) { |
| 516 | + continue; |
| 517 | + } |
| 518 | + $used = $this->mergeExtensionItem( $codeSequence, 'specialPageAliases', |
| 519 | + $allData['specialPageAliases'], $data['aliases'] ); |
| 520 | + if ( $used ) { |
| 521 | + $deps[] = new FileDependency( $fileName ); |
| 522 | + } |
| 523 | + } |
| 524 | + |
| 525 | + # Merge core data into extension data |
| 526 | + foreach ( $coreData as $key => $item ) { |
| 527 | + $this->mergeItem( $key, $allData[$key], $item ); |
| 528 | + } |
| 529 | + |
| 530 | + # Add cache dependencies for any referenced globals |
| 531 | + $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' ); |
| 532 | + $deps['wgExtensionAliasesFiles'] = new GlobalDependency( 'wgExtensionAliasesFiles' ); |
| 533 | + $deps['version'] = new ConstantDependency( 'MW_LC_VERSION' ); |
| 534 | + |
| 535 | + # Add dependencies to the cache entry |
| 536 | + $allData['deps'] = $deps; |
| 537 | + |
| 538 | + # Replace spaces with underscores in namespace names |
| 539 | + $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] ); |
| 540 | + |
| 541 | + # And do the same for special page aliases. $page is an array. |
| 542 | + foreach ( $allData['specialPageAliases'] as &$page ) { |
| 543 | + $page = str_replace( ' ', '_', $page ); |
| 544 | + } |
| 545 | + # Decouple the reference to prevent accidental damage |
| 546 | + unset($page); |
| 547 | + |
| 548 | + # Fix broken defaultUserOptionOverrides |
| 549 | + if ( !is_array( $allData['defaultUserOptionOverrides'] ) ) { |
| 550 | + $allData['defaultUserOptionOverrides'] = array(); |
| 551 | + } |
| 552 | + |
| 553 | + # Set the preload key |
| 554 | + $allData['preload'] = $this->buildPreload( $allData ); |
| 555 | + |
| 556 | + # Set the list keys |
| 557 | + $allData['list'] = array(); |
| 558 | + foreach ( self::$splitKeys as $key ) { |
| 559 | + $allData['list'][$key] = array_keys( $allData[$key] ); |
| 560 | + } |
| 561 | + |
| 562 | + # Run hooks |
| 563 | + wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) ); |
| 564 | + |
| 565 | + if ( is_null( $allData['defaultUserOptionOverrides'] ) ) { |
| 566 | + throw new MWException( __METHOD__.': Localisation data failed sanity check! ' . |
| 567 | + 'Check that your languages/messages/MessagesEn.php file is intact.' ); |
| 568 | + } |
| 569 | + |
| 570 | + # Save to the process cache and register the items loaded |
| 571 | + $this->data[$code] = $allData; |
| 572 | + foreach ( $allData as $key => $item ) { |
| 573 | + $this->loadedItems[$code][$key] = true; |
| 574 | + } |
| 575 | + |
| 576 | + # Save to the persistent cache |
| 577 | + $this->store->startWrite( $code ); |
| 578 | + foreach ( $allData as $key => $value ) { |
| 579 | + if ( in_array( $key, self::$splitKeys ) ) { |
| 580 | + foreach ( $value as $subkey => $subvalue ) { |
| 581 | + $this->store->set( "$key:$subkey", $subvalue ); |
| 582 | + } |
| 583 | + } else { |
| 584 | + $this->store->set( $key, $value ); |
| 585 | + } |
| 586 | + } |
| 587 | + $this->store->finishWrite(); |
| 588 | + |
| 589 | + wfProfileOut( __METHOD__ ); |
| 590 | + } |
| 591 | + |
| 592 | + /** |
| 593 | + * Build the preload item from the given pre-cache data. |
| 594 | + * |
| 595 | + * The preload item will be loaded automatically, improving performance |
| 596 | + * for the commonly-requested items it contains. |
| 597 | + */ |
| 598 | + protected function buildPreload( $data ) { |
| 599 | + $preload = array( 'messages' => array() ); |
| 600 | + foreach ( self::$preloadedKeys as $key ) { |
| 601 | + $preload[$key] = $data[$key]; |
| 602 | + } |
| 603 | + foreach ( $data['preloadedMessages'] as $subkey ) { |
| 604 | + if ( isset( $data['messages'][$subkey] ) ) { |
| 605 | + $subitem = $data['messages'][$subkey]; |
| 606 | + } else { |
| 607 | + $subitem = null; |
| 608 | + } |
| 609 | + $preload['messages'][$subkey] = $subitem; |
| 610 | + } |
| 611 | + return $preload; |
| 612 | + } |
| 613 | + |
| 614 | + /** |
| 615 | + * Unload the data for a given language from the object cache. |
| 616 | + * Reduces memory usage. |
| 617 | + */ |
| 618 | + public function unload( $code ) { |
| 619 | + unset( $this->data[$code] ); |
| 620 | + unset( $this->loadedItems[$code] ); |
| 621 | + unset( $this->loadedSubitems[$code] ); |
| 622 | + unset( $this->initialisedLangs[$code] ); |
| 623 | + foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) { |
| 624 | + if ( $fbCode === $code ) { |
| 625 | + $this->unload( $shallowCode ); |
| 626 | + } |
| 627 | + } |
| 628 | + } |
| 629 | +} |
| 630 | + |
| 631 | +/** |
| 632 | + * Interface for the persistence layer of LocalisationCache. |
| 633 | + * |
| 634 | + * The persistence layer is two-level hierarchical cache. The first level |
| 635 | + * is the language, the second level is the item or subitem. |
| 636 | + * |
| 637 | + * Since the data for a whole language is rebuilt in one operation, it needs |
| 638 | + * to have a fast and atomic method for deleting or replacing all of the |
| 639 | + * current data for a given language. The interface reflects this bulk update |
| 640 | + * operation. Callers writing to the cache must first call startWrite(), then |
| 641 | + * will call set() a couple of thousand times, then will call finishWrite() |
| 642 | + * to commit the operation. When finishWrite() is called, the cache is |
| 643 | + * expected to delete all data previously stored for that language. |
| 644 | + * |
| 645 | + * The values stored are PHP variables suitable for serialize(). Implementations |
| 646 | + * of LCStore are responsible for serializing and unserializing. |
| 647 | + */ |
| 648 | +interface LCStore { |
| 649 | + /** |
| 650 | + * Get a value. |
| 651 | + * @param $code Language code |
| 652 | + * @param $key Cache key |
| 653 | + */ |
| 654 | + public function get( $code, $key ); |
| 655 | + |
| 656 | + /** |
| 657 | + * Start a write transaction. |
| 658 | + * @param $code Language code |
| 659 | + */ |
| 660 | + public function startWrite( $code ); |
| 661 | + |
| 662 | + /** |
| 663 | + * Finish a write transaction. |
| 664 | + */ |
| 665 | + public function finishWrite(); |
| 666 | + |
| 667 | + /** |
| 668 | + * Set a key to a given value. startWrite() must be called before this |
| 669 | + * is called, and finishWrite() must be called afterwards. |
| 670 | + */ |
| 671 | + public function set( $key, $value ); |
| 672 | + |
| 673 | +} |
| 674 | + |
| 675 | +/** |
| 676 | + * LCStore implementation which uses the standard DB functions to store data. |
| 677 | + * This will work on any MediaWiki installation. |
| 678 | + */ |
| 679 | +class LCStore_DB implements LCStore { |
| 680 | + var $currentLang; |
| 681 | + var $writesDone = false; |
| 682 | + var $dbw, $batch; |
| 683 | + |
| 684 | + public function get( $code, $key ) { |
| 685 | + if ( $this->writesDone ) { |
| 686 | + $db = wfGetDB( DB_MASTER ); |
| 687 | + } else { |
| 688 | + $db = wfGetDB( DB_SLAVE ); |
| 689 | + } |
| 690 | + $row = $db->selectRow( 'l10n_cache', array( 'lc_value' ), |
| 691 | + array( 'lc_lang' => $code, 'lc_key' => $key ), __METHOD__ ); |
| 692 | + if ( $row ) { |
| 693 | + return unserialize( $row->lc_value ); |
| 694 | + } else { |
| 695 | + return null; |
| 696 | + } |
| 697 | + } |
| 698 | + |
| 699 | + public function startWrite( $code ) { |
| 700 | + if ( !$code ) { |
| 701 | + throw new MWException( __METHOD__.": Invalid language \"$code\"" ); |
| 702 | + } |
| 703 | + $this->dbw = wfGetDB( DB_MASTER ); |
| 704 | + $this->dbw->begin(); |
| 705 | + $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ ); |
| 706 | + $this->currentLang = $code; |
| 707 | + $this->batch = array(); |
| 708 | + } |
| 709 | + |
| 710 | + public function finishWrite() { |
| 711 | + if ( $this->batch ) { |
| 712 | + $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ ); |
| 713 | + } |
| 714 | + $this->dbw->commit(); |
| 715 | + $this->currentLang = null; |
| 716 | + $this->dbw = null; |
| 717 | + $this->batch = array(); |
| 718 | + $this->writesDone = true; |
| 719 | + } |
| 720 | + |
| 721 | + public function set( $key, $value ) { |
| 722 | + if ( is_null( $this->currentLang ) ) { |
| 723 | + throw new MWException( __CLASS__.': must call startWrite() before calling set()' ); |
| 724 | + } |
| 725 | + $this->batch[] = array( |
| 726 | + 'lc_lang' => $this->currentLang, |
| 727 | + 'lc_key' => $key, |
| 728 | + 'lc_value' => serialize( $value ) ); |
| 729 | + if ( count( $this->batch ) >= 100 ) { |
| 730 | + $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ ); |
| 731 | + $this->batch = array(); |
| 732 | + } |
| 733 | + } |
| 734 | +} |
| 735 | + |
| 736 | +/** |
| 737 | + * LCStore implementation which stores data as a collection of CDB files in the |
| 738 | + * directory given by $wgCacheDirectory. If $wgCacheDirectory is not set, this |
| 739 | + * will throw an exception. |
| 740 | + * |
| 741 | + * Profiling indicates that on Linux, this implementation outperforms MySQL if |
| 742 | + * the directory is on a local filesystem and there is ample kernel cache |
| 743 | + * space. The performance advantage is greater when the DBA extension is |
| 744 | + * available than it is with the PHP port. |
| 745 | + * |
| 746 | + * See Cdb.php and http://cr.yp.to/cdb.html |
| 747 | + */ |
| 748 | +class LCStore_CDB implements LCStore { |
| 749 | + var $readers, $writer, $currentLang; |
| 750 | + |
| 751 | + public function get( $code, $key ) { |
| 752 | + if ( !isset( $this->readers[$code] ) ) { |
| 753 | + $fileName = $this->getFileName( $code ); |
| 754 | + if ( !file_exists( $fileName ) ) { |
| 755 | + $this->readers[$code] = false; |
| 756 | + } else { |
| 757 | + $this->readers[$code] = CdbReader::open( $fileName ); |
| 758 | + } |
| 759 | + } |
| 760 | + if ( !$this->readers[$code] ) { |
| 761 | + return null; |
| 762 | + } else { |
| 763 | + $value = $this->readers[$code]->get( $key ); |
| 764 | + if ( $value === false ) { |
| 765 | + return null; |
| 766 | + } |
| 767 | + return unserialize( $value ); |
| 768 | + } |
| 769 | + } |
| 770 | + |
| 771 | + public function startWrite( $code ) { |
| 772 | + $this->writer = CdbWriter::open( $this->getFileName( $code ) ); |
| 773 | + $this->currentLang = $code; |
| 774 | + } |
| 775 | + |
| 776 | + public function finishWrite() { |
| 777 | + // Close the writer |
| 778 | + $this->writer->close(); |
| 779 | + $this->writer = null; |
| 780 | + |
| 781 | + // Reopen the reader |
| 782 | + if ( !empty( $this->readers[$this->currentLang] ) ) { |
| 783 | + $this->readers[$this->currentLang]->close(); |
| 784 | + } |
| 785 | + unset( $this->readers[$this->currentLang] ); |
| 786 | + $this->currentLang = null; |
| 787 | + } |
| 788 | + |
| 789 | + public function set( $key, $value ) { |
| 790 | + if ( is_null( $this->writer ) ) { |
| 791 | + throw new MWException( __CLASS__.': must call startWrite() before calling set()' ); |
| 792 | + } |
| 793 | + $this->writer->set( $key, serialize( $value ) ); |
| 794 | + } |
| 795 | + |
| 796 | + protected function getFileName( $code ) { |
| 797 | + global $wgCacheDirectory; |
| 798 | + if ( !$code || strpos( $code, '/' ) !== false ) { |
| 799 | + throw new MWException( __METHOD__.": Invalid language \"$code\"" ); |
| 800 | + } |
| 801 | + return "$wgCacheDirectory/l10n_cache-$code.cdb"; |
| 802 | + } |
| 803 | +} |
| 804 | + |
| 805 | +/** |
| 806 | + * A localisation cache optimised for loading large amounts of data for many |
| 807 | + * languages. Used by rebuildLocalisationCache.php. |
| 808 | + */ |
| 809 | +class LocalisationCache_BulkLoad extends LocalisationCache { |
| 810 | + /** |
| 811 | + * A cache of the contents of data files. |
| 812 | + * Core files are serialized to avoid using ~1GB of RAM during a recache. |
| 813 | + */ |
| 814 | + var $fileCache = array(); |
| 815 | + |
| 816 | + /** |
| 817 | + * Most recently used languages. Uses the linked-list aspect of PHP hashtables |
| 818 | + * to keep the most recently used language codes at the end of the array, and |
| 819 | + * the language codes that are ready to be deleted at the beginning. |
| 820 | + */ |
| 821 | + var $mruLangs = array(); |
| 822 | + |
| 823 | + /** |
| 824 | + * Maximum number of languages that may be loaded into $this->data |
| 825 | + */ |
| 826 | + var $maxLoadedLangs = 10; |
| 827 | + |
| 828 | + protected function readPHPFile( $fileName, $fileType ) { |
| 829 | + $serialize = $fileType === 'core'; |
| 830 | + if ( !isset( $this->fileCache[$fileName][$fileType] ) ) { |
| 831 | + $data = parent::readPHPFile( $fileName, $fileType ); |
| 832 | + if ( $serialize ) { |
| 833 | + $encData = serialize( $data ); |
| 834 | + } else { |
| 835 | + $encData = $data; |
| 836 | + } |
| 837 | + $this->fileCache[$fileName][$fileType] = $encData; |
| 838 | + return $data; |
| 839 | + } elseif ( $serialize ) { |
| 840 | + return unserialize( $this->fileCache[$fileName][$fileType] ); |
| 841 | + } else { |
| 842 | + return $this->fileCache[$fileName][$fileType]; |
| 843 | + } |
| 844 | + } |
| 845 | + |
| 846 | + public function getItem( $code, $key ) { |
| 847 | + unset( $this->mruLangs[$code] ); |
| 848 | + $this->mruLangs[$code] = true; |
| 849 | + return parent::getItem( $code, $key ); |
| 850 | + } |
| 851 | + |
| 852 | + public function getSubitem( $code, $key, $subkey ) { |
| 853 | + unset( $this->mruLangs[$code] ); |
| 854 | + $this->mruLangs[$code] = true; |
| 855 | + return parent::getSubitem( $code, $key, $subkey ); |
| 856 | + } |
| 857 | + |
| 858 | + public function recache( $code ) { |
| 859 | + parent::recache( $code ); |
| 860 | + unset( $this->mruLangs[$code] ); |
| 861 | + $this->mruLangs[$code] = true; |
| 862 | + $this->trimCache(); |
| 863 | + } |
| 864 | + |
| 865 | + public function unload( $code ) { |
| 866 | + unset( $this->mruLangs[$code] ); |
| 867 | + parent::unload( $code ); |
| 868 | + } |
| 869 | + |
| 870 | + /** |
| 871 | + * Unload cached languages until there are less than $this->maxLoadedLangs |
| 872 | + */ |
| 873 | + protected function trimCache() { |
| 874 | + while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) { |
| 875 | + reset( $this->mruLangs ); |
| 876 | + $code = key( $this->mruLangs ); |
| 877 | + wfDebug( __METHOD__.": unloading $code\n" ); |
| 878 | + $this->unload( $code ); |
| 879 | + } |
| 880 | + } |
| 881 | +} |
Property changes on: trunk/phase3/includes/LocalisationCache.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 882 | + native |
Index: trunk/phase3/includes/MessageCache.php |
— | — | @@ -23,9 +23,6 @@ |
24 | 24 | |
25 | 25 | var $mUseCache, $mDisable, $mExpiry; |
26 | 26 | var $mKeys, $mParserOptions, $mParser; |
27 | | - var $mExtensionMessages = array(); |
28 | | - var $mInitialised = false; |
29 | | - var $mAllMessagesLoaded = array(); // Extension messages |
30 | 27 | |
31 | 28 | // Variable for tracking which variables are loaded |
32 | 29 | var $mLoadedLanguages = array(); |
— | — | @@ -37,7 +34,6 @@ |
38 | 35 | $this->mExpiry = $expiry; |
39 | 36 | $this->mDisableTransform = false; |
40 | 37 | $this->mKeys = false; # initialised on demand |
41 | | - $this->mInitialised = true; |
42 | 38 | $this->mParser = null; |
43 | 39 | } |
44 | 40 | |
— | — | @@ -62,9 +58,9 @@ |
63 | 59 | * @return false on failure. |
64 | 60 | */ |
65 | 61 | function loadFromLocal( $hash, $code ) { |
66 | | - global $wgLocalMessageCache, $wgLocalMessageCacheSerialized; |
| 62 | + global $wgCacheDirectory, $wgLocalMessageCacheSerialized; |
67 | 63 | |
68 | | - $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code"; |
| 64 | + $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code"; |
69 | 65 | |
70 | 66 | # Check file existence |
71 | 67 | wfSuppressWarnings(); |
— | — | @@ -106,10 +102,10 @@ |
107 | 103 | * Save the cache to a local file. |
108 | 104 | */ |
109 | 105 | function saveToLocal( $serialized, $hash, $code ) { |
110 | | - global $wgLocalMessageCache; |
| 106 | + global $wgCacheDirectory; |
111 | 107 | |
112 | | - $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code"; |
113 | | - wfMkdirParents( $wgLocalMessageCache ); // might fail |
| 108 | + $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code"; |
| 109 | + wfMkdirParents( $wgCacheDirectory ); // might fail |
114 | 110 | |
115 | 111 | wfSuppressWarnings(); |
116 | 112 | $file = fopen( $filename, 'w' ); |
— | — | @@ -126,11 +122,11 @@ |
127 | 123 | } |
128 | 124 | |
129 | 125 | function saveToScript( $array, $hash, $code ) { |
130 | | - global $wgLocalMessageCache; |
| 126 | + global $wgCacheDirectory; |
131 | 127 | |
132 | | - $filename = "$wgLocalMessageCache/messages-" . wfWikiID() . "-$code"; |
| 128 | + $filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code"; |
133 | 129 | $tempFilename = $filename . '.tmp'; |
134 | | - wfMkdirParents( $wgLocalMessageCache ); // might fail |
| 130 | + wfMkdirParents( $wgCacheDirectory ); // might fail |
135 | 131 | |
136 | 132 | wfSuppressWarnings(); |
137 | 133 | $file = fopen( $tempFilename, 'w'); |
— | — | @@ -174,7 +170,7 @@ |
175 | 171 | |
176 | 172 | /** |
177 | 173 | * Loads messages from caches or from database in this order: |
178 | | - * (1) local message cache (if $wgLocalMessageCache is enabled) |
| 174 | + * (1) local message cache (if $wgUseLocalMessageCache is enabled) |
179 | 175 | * (2) memcached |
180 | 176 | * (3) from the database. |
181 | 177 | * |
— | — | @@ -191,7 +187,7 @@ |
192 | 188 | * @param $code String: language to which load messages |
193 | 189 | */ |
194 | 190 | function load( $code = false ) { |
195 | | - global $wgLocalMessageCache; |
| 191 | + global $wgUseLocalMessageCache; |
196 | 192 | |
197 | 193 | if ( !$this->mUseCache ) { |
198 | 194 | return true; |
— | — | @@ -227,7 +223,7 @@ |
228 | 224 | # (1) local cache |
229 | 225 | # Hash of the contents is stored in memcache, to detect if local cache goes |
230 | 226 | # out of date (due to update in other thread?) |
231 | | - if ( $wgLocalMessageCache !== false ) { |
| 227 | + if ( $wgUseLocalMessageCache ) { |
232 | 228 | wfProfileIn( __METHOD__ . '-fromlocal' ); |
233 | 229 | |
234 | 230 | $hash = $this->mMemc->get( wfMemcKey( 'messages', $code, 'hash' ) ); |
— | — | @@ -423,7 +419,7 @@ |
424 | 420 | */ |
425 | 421 | protected function saveToCaches( $cache, $memc = true, $code = false ) { |
426 | 422 | wfProfileIn( __METHOD__ ); |
427 | | - global $wgLocalMessageCache, $wgLocalMessageCacheSerialized; |
| 423 | + global $wgUseLocalMessageCache, $wgLocalMessageCacheSerialized; |
428 | 424 | |
429 | 425 | $cacheKey = wfMemcKey( 'messages', $code ); |
430 | 426 | |
— | — | @@ -440,7 +436,7 @@ |
441 | 437 | } |
442 | 438 | |
443 | 439 | # Save to local cache |
444 | | - if ( $wgLocalMessageCache !== false ) { |
| 440 | + if ( $wgUseLocalMessageCache ) { |
445 | 441 | $serialized = serialize( $cache ); |
446 | 442 | $hash = md5( $serialized ); |
447 | 443 | $this->mMemc->set( wfMemcKey( 'messages', $code, 'hash' ), $hash, $this->mExpiry ); |
— | — | @@ -508,36 +504,22 @@ |
509 | 505 | $lang = wfGetLangObj( $langcode ); |
510 | 506 | $langcode = $lang->getCode(); |
511 | 507 | |
512 | | - # If uninitialised, someone is trying to call this halfway through Setup.php |
513 | | - if( !$this->mInitialised ) { |
514 | | - return '<' . htmlspecialchars($key) . '>'; |
515 | | - } |
516 | | - |
517 | 508 | $message = false; |
518 | 509 | |
519 | 510 | # Normalise title-case input |
520 | | - $lckey = $wgContLang->lcfirst( $key ); |
521 | | - $lckey = str_replace( ' ', '_', $lckey ); |
| 511 | + $lckey = str_replace( ' ', '_', $key ); |
| 512 | + $lckey[0] = strtolower( $lckey[0] ); |
| 513 | + $uckey = ucfirst( $lckey ); |
522 | 514 | |
523 | 515 | # Try the MediaWiki namespace |
524 | 516 | if( !$this->mDisable && $useDB ) { |
525 | | - $title = $wgContLang->ucfirst( $lckey ); |
| 517 | + $title = $uckey; |
526 | 518 | if(!$isFullKey && ( $langcode != $wgContLanguageCode ) ) { |
527 | 519 | $title .= '/' . $langcode; |
528 | 520 | } |
529 | 521 | $message = $this->getMsgFromNamespace( $title, $langcode ); |
530 | 522 | } |
531 | | - if( $message === false ) |
532 | | - wfRunHooks( 'MessageNotInMwNs', array( &$message, $lckey, $langcode, $isFullKey ) ); |
533 | 523 | |
534 | | - # Try the extension array |
535 | | - if ( $message === false && isset( $this->mExtensionMessages[$langcode][$lckey] ) ) { |
536 | | - $message = $this->mExtensionMessages[$langcode][$lckey]; |
537 | | - } |
538 | | - if ( $message === false && isset( $this->mExtensionMessages['en'][$lckey] ) ) { |
539 | | - $message = $this->mExtensionMessages['en'][$lckey]; |
540 | | - } |
541 | | - |
542 | 524 | # Try the array in the language object |
543 | 525 | if ( $message === false ) { |
544 | 526 | $message = $lang->getMessage( $lckey ); |
— | — | @@ -547,19 +529,15 @@ |
548 | 530 | } |
549 | 531 | |
550 | 532 | # Try the array of another language |
551 | | - $pos = strrpos( $lckey, '/' ); |
552 | | - if( $message === false && $pos !== false) { |
553 | | - $mkey = substr( $lckey, 0, $pos ); |
554 | | - $code = substr( $lckey, $pos+1 ); |
555 | | - if ( $code ) { |
556 | | - # We may get calls for things that are http-urls from sidebar |
557 | | - # Let's not load nonexistent languages for those |
558 | | - $validCodes = array_keys( Language::getLanguageNames() ); |
559 | | - if ( in_array( $code, $validCodes ) ) { |
560 | | - $message = Language::getMessageFor( $mkey, $code ); |
561 | | - if ( is_null( $message ) ) { |
562 | | - $message = false; |
563 | | - } |
| 533 | + if( $message === false ) { |
| 534 | + $parts = explode( '/', $lckey ); |
| 535 | + # We may get calls for things that are http-urls from sidebar |
| 536 | + # Let's not load nonexistent languages for those |
| 537 | + # They usually have more than one slash. |
| 538 | + if ( count( $parts ) == 2 && $parts[1] !== '' ) { |
| 539 | + $message = Language::getMessageFor( $parts[0], $parts[1] ); |
| 540 | + if ( is_null( $message ) ) { |
| 541 | + $message = false; |
564 | 542 | } |
565 | 543 | } |
566 | 544 | } |
— | — | @@ -568,7 +546,7 @@ |
569 | 547 | if( ($message === false || $message === '-' ) && |
570 | 548 | !$this->mDisable && $useDB && |
571 | 549 | !$isFullKey && ($langcode != $wgContLanguageCode) ) { |
572 | | - $message = $this->getMsgFromNamespace( $wgContLang->ucfirst( $lckey ), $wgContLanguageCode ); |
| 550 | + $message = $this->getMsgFromNamespace( $uckey, $wgContLanguageCode ); |
573 | 551 | } |
574 | 552 | |
575 | 553 | # Final fallback |
— | — | @@ -662,7 +640,7 @@ |
663 | 641 | } |
664 | 642 | |
665 | 643 | function transform( $message, $interface = false, $language = null ) { |
666 | | - // Avoid creating parser if nothing to transfrom |
| 644 | + // Avoid creating parser if nothing to transform |
667 | 645 | if( strpos( $message, '{{' ) === false ) { |
668 | 646 | return $message; |
669 | 647 | } |
— | — | @@ -709,71 +687,6 @@ |
710 | 688 | } |
711 | 689 | |
712 | 690 | /** |
713 | | - * Add a message to the cache |
714 | | - * |
715 | | - * @param mixed $key |
716 | | - * @param mixed $value |
717 | | - * @param string $lang The messages language, English by default |
718 | | - */ |
719 | | - function addMessage( $key, $value, $lang = 'en' ) { |
720 | | - global $wgContLang; |
721 | | - # Normalise title-case input |
722 | | - $lckey = str_replace( ' ', '_', $wgContLang->lcfirst( $key ) ); |
723 | | - $this->mExtensionMessages[$lang][$lckey] = $value; |
724 | | - } |
725 | | - |
726 | | - /** |
727 | | - * Add an associative array of message to the cache |
728 | | - * |
729 | | - * @param array $messages An associative array of key => values to be added |
730 | | - * @param string $lang The messages language, English by default |
731 | | - */ |
732 | | - function addMessages( $messages, $lang = 'en' ) { |
733 | | - wfProfileIn( __METHOD__ ); |
734 | | - if ( !is_array( $messages ) ) { |
735 | | - throw new MWException( __METHOD__.': Invalid message array' ); |
736 | | - } |
737 | | - if ( isset( $this->mExtensionMessages[$lang] ) ) { |
738 | | - $this->mExtensionMessages[$lang] = $messages + $this->mExtensionMessages[$lang]; |
739 | | - } else { |
740 | | - $this->mExtensionMessages[$lang] = $messages; |
741 | | - } |
742 | | - wfProfileOut( __METHOD__ ); |
743 | | - } |
744 | | - |
745 | | - /** |
746 | | - * Add a 2-D array of messages by lang. Useful for extensions. |
747 | | - * |
748 | | - * @param array $messages The array to be added |
749 | | - */ |
750 | | - function addMessagesByLang( $messages ) { |
751 | | - wfProfileIn( __METHOD__ ); |
752 | | - foreach ( $messages as $key => $value ) { |
753 | | - $this->addMessages( $value, $key ); |
754 | | - } |
755 | | - wfProfileOut( __METHOD__ ); |
756 | | - } |
757 | | - |
758 | | - /** |
759 | | - * Get the extension messages for a specific language. Only English, interface |
760 | | - * and content language are guaranteed to be loaded. |
761 | | - * |
762 | | - * @param string $lang The messages language, English by default |
763 | | - */ |
764 | | - function getExtensionMessagesFor( $lang = 'en' ) { |
765 | | - wfProfileIn( __METHOD__ ); |
766 | | - $messages = array(); |
767 | | - if ( isset( $this->mExtensionMessages[$lang] ) ) { |
768 | | - $messages = $this->mExtensionMessages[$lang]; |
769 | | - } |
770 | | - if ( $lang != 'en' ) { |
771 | | - $messages = $messages + $this->mExtensionMessages['en']; |
772 | | - } |
773 | | - wfProfileOut( __METHOD__ ); |
774 | | - return $messages; |
775 | | - } |
776 | | - |
777 | | - /** |
778 | 691 | * Clear all stored messages. Mainly used after a mass rebuild. |
779 | 692 | */ |
780 | 693 | function clear() { |
— | — | @@ -788,83 +701,18 @@ |
789 | 702 | } |
790 | 703 | } |
791 | 704 | |
| 705 | + /** |
| 706 | + * @deprecated |
| 707 | + */ |
792 | 708 | function loadAllMessages( $lang = false ) { |
793 | | - global $wgExtensionMessagesFiles; |
794 | | - $key = $lang === false ? '*' : $lang; |
795 | | - if ( isset( $this->mAllMessagesLoaded[$key] ) ) { |
796 | | - return; |
797 | | - } |
798 | | - $this->mAllMessagesLoaded[$key] = true; |
799 | | - |
800 | | - # Some extensions will load their messages when you load their class file |
801 | | - wfLoadAllExtensions(); |
802 | | - # Others will respond to this hook |
803 | | - wfRunHooks( 'LoadAllMessages', array( $this ) ); |
804 | | - # Some register their messages in $wgExtensionMessagesFiles |
805 | | - foreach ( $wgExtensionMessagesFiles as $name => $file ) { |
806 | | - wfLoadExtensionMessages( $name, $lang ); |
807 | | - } |
808 | | - # Still others will respond to neither, they are EVIL. We sometimes need to know! |
809 | 709 | } |
810 | 710 | |
811 | 711 | /** |
812 | | - * Load messages from a given file |
813 | | - * |
814 | | - * @param string $filename Filename of file to load. |
815 | | - * @param string $langcode Language to load messages for, or false for |
816 | | - * default behvaiour (en, content language and user |
817 | | - * language). |
| 712 | + * @deprecated |
818 | 713 | */ |
819 | 714 | function loadMessagesFile( $filename, $langcode = false ) { |
820 | | - global $wgLang, $wgContLang; |
821 | | - wfProfileIn( __METHOD__ ); |
822 | | - $messages = $magicWords = false; |
823 | | - require( $filename ); |
824 | | - |
825 | | - $validCodes = Language::getLanguageNames(); |
826 | | - if( is_string( $langcode ) && array_key_exists( $langcode, $validCodes ) ) { |
827 | | - # Load messages for given language code. |
828 | | - $this->processMessagesArray( $messages, $langcode ); |
829 | | - } elseif( is_string( $langcode ) && !array_key_exists( $langcode, $validCodes ) ) { |
830 | | - wfDebug( "Invalid language '$langcode' code passed to MessageCache::loadMessagesFile()" ); |
831 | | - } else { |
832 | | - # Load only languages that are usually used, and merge all |
833 | | - # fallbacks, except English. |
834 | | - $langs = array_unique( array( 'en', $wgContLang->getCode(), $wgLang->getCode() ) ); |
835 | | - foreach( $langs as $code ) { |
836 | | - $this->processMessagesArray( $messages, $code ); |
837 | | - } |
838 | | - } |
839 | | - |
840 | | - if ( $magicWords !== false ) { |
841 | | - global $wgContLang; |
842 | | - $wgContLang->addMagicWordsByLang( $magicWords ); |
843 | | - } |
844 | | - wfProfileOut( __METHOD__ ); |
845 | 715 | } |
846 | 716 | |
847 | | - /** |
848 | | - * Process an array of messages, loading it into the message cache. |
849 | | - * |
850 | | - * @param array $messages Messages array. |
851 | | - * @param string $langcode Language code to process. |
852 | | - */ |
853 | | - function processMessagesArray( $messages, $langcode ) { |
854 | | - wfProfileIn( __METHOD__ ); |
855 | | - $fallbackCode = $langcode; |
856 | | - $mergedMessages = array(); |
857 | | - do { |
858 | | - if ( isset($messages[$fallbackCode]) ) { |
859 | | - $mergedMessages += $messages[$fallbackCode]; |
860 | | - } |
861 | | - $fallbackCode = Language::getFallbackfor( $fallbackCode ); |
862 | | - } while( $fallbackCode && $fallbackCode !== 'en' ); |
863 | | - |
864 | | - if ( !empty($mergedMessages) ) |
865 | | - $this->addMessages( $mergedMessages, $langcode ); |
866 | | - wfProfileOut( __METHOD__ ); |
867 | | - } |
868 | | - |
869 | 717 | public function figureMessage( $key ) { |
870 | 718 | global $wgContLanguageCode; |
871 | 719 | $pieces = explode( '/', $key ); |
Index: trunk/phase3/includes/CacheDependency.php |
— | — | @@ -134,6 +134,11 @@ |
135 | 135 | $this->timestamp = $timestamp; |
136 | 136 | } |
137 | 137 | |
| 138 | + function __sleep() { |
| 139 | + $this->loadDependencyValues(); |
| 140 | + return array( 'filename', 'timestamp' ); |
| 141 | + } |
| 142 | + |
138 | 143 | function loadDependencyValues() { |
139 | 144 | if ( is_null( $this->timestamp ) ) { |
140 | 145 | if ( !file_exists( $this->filename ) ) { |
Index: trunk/phase3/includes/api/ApiQuerySiteinfo.php |
— | — | @@ -185,8 +185,7 @@ |
186 | 186 | |
187 | 187 | protected function appendNamespaceAliases( $property ) { |
188 | 188 | global $wgNamespaceAliases, $wgContLang; |
189 | | - $wgContLang->load(); |
190 | | - $aliases = array_merge( $wgNamespaceAliases, $wgContLang->namespaceAliases ); |
| 189 | + $aliases = array_merge( $wgNamespaceAliases, $wgContLang->getNamespaceAliases() ); |
191 | 190 | $namespaces = $wgContLang->getNamespaces(); |
192 | 191 | $data = array(); |
193 | 192 | foreach( $aliases as $title => $ns ) { |
Index: trunk/phase3/includes/Hooks.php |
— | — | @@ -32,15 +32,16 @@ |
33 | 33 | |
34 | 34 | global $wgHooks; |
35 | 35 | |
| 36 | + // Return quickly in the most common case |
| 37 | + if ( !isset( $wgHooks[$event] ) ) { |
| 38 | + return true; |
| 39 | + } |
| 40 | + |
36 | 41 | if (!is_array($wgHooks)) { |
37 | 42 | throw new MWException("Global hooks array is not an array!\n"); |
38 | 43 | return false; |
39 | 44 | } |
40 | 45 | |
41 | | - if (!array_key_exists($event, $wgHooks)) { |
42 | | - return true; |
43 | | - } |
44 | | - |
45 | 46 | if (!is_array($wgHooks[$event])) { |
46 | 47 | throw new MWException("Hooks array for event '$event' is not an array!\n"); |
47 | 48 | return false; |
Index: trunk/phase3/includes/Cdb.php |
— | — | @@ -13,7 +13,7 @@ |
14 | 14 | if ( self::haveExtension() ) { |
15 | 15 | return new CdbReader_DBA( $fileName ); |
16 | 16 | } else { |
17 | | - wfDebug( 'Warning: no dba extension found, using emulation.' ); |
| 17 | + wfDebug( "Warning: no dba extension found, using emulation.\n" ); |
18 | 18 | return new CdbReader_PHP( $fileName ); |
19 | 19 | } |
20 | 20 | } |
— | — | @@ -61,7 +61,7 @@ |
62 | 62 | if ( CdbReader::haveExtension() ) { |
63 | 63 | return new CdbWriter_DBA( $fileName ); |
64 | 64 | } else { |
65 | | - wfDebug( 'Warning: no dba extension found, using emulation.' ); |
| 65 | + wfDebug( "Warning: no dba extension found, using emulation.\n" ); |
66 | 66 | return new CdbWriter_PHP( $fileName ); |
67 | 67 | } |
68 | 68 | } |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -115,6 +115,8 @@ |
116 | 116 | 'Interwiki' => 'includes/Interwiki.php', |
117 | 117 | 'IP' => 'includes/IP.php', |
118 | 118 | 'Job' => 'includes/JobQueue.php', |
| 119 | + 'LCStore_DB' => 'includes/LocalisationCache.php', |
| 120 | + 'LCStore_CDB' => 'includes/LocalisationCache.php', |
119 | 121 | 'License' => 'includes/Licenses.php', |
120 | 122 | 'Licenses' => 'includes/Licenses.php', |
121 | 123 | 'LinkBatch' => 'includes/LinkBatch.php', |
— | — | @@ -122,6 +124,8 @@ |
123 | 125 | 'Linker' => 'includes/Linker.php', |
124 | 126 | 'LinkFilter' => 'includes/LinkFilter.php', |
125 | 127 | 'LinksUpdate' => 'includes/LinksUpdate.php', |
| 128 | + 'LocalisationCache' => 'includes/LocalisationCache.php', |
| 129 | + 'LocalisationCache_BulkLoad' => 'includes/LocalisationCache.php', |
126 | 130 | 'LogPage' => 'includes/LogPage.php', |
127 | 131 | 'LogPager' => 'includes/LogEventsList.php', |
128 | 132 | 'LogEventsList' => 'includes/LogEventsList.php', |
Index: trunk/phase3/includes/MagicWord.php |
— | — | @@ -185,7 +185,7 @@ |
186 | 186 | */ |
187 | 187 | static function &get( $id ) { |
188 | 188 | wfProfileIn( __METHOD__ ); |
189 | | - if (!array_key_exists( $id, self::$mObjects ) ) { |
| 189 | + if ( !isset( self::$mObjects[$id] ) ) { |
190 | 190 | $mw = new MagicWord(); |
191 | 191 | $mw->load( $id ); |
192 | 192 | self::$mObjects[$id] = $mw; |
Index: trunk/phase3/includes/HTMLFileCache.php |
— | — | @@ -14,6 +14,7 @@ |
15 | 15 | * - $wgCachePages |
16 | 16 | * - $wgCacheEpoch |
17 | 17 | * - $wgUseFileCache |
| 18 | + * - $wgCacheDirectory |
18 | 19 | * - $wgFileCacheDirectory |
19 | 20 | * - $wgUseGzip |
20 | 21 | * |
— | — | @@ -30,7 +31,16 @@ |
31 | 32 | |
32 | 33 | public function fileCacheName() { |
33 | 34 | if( !$this->mFileCache ) { |
34 | | - global $wgFileCacheDirectory, $wgRequest; |
| 35 | + global $wgCacheDirectory, $wgFileCacheDirectory, $wgRequest; |
| 36 | + |
| 37 | + if ( $wgFileCacheDirectory ) { |
| 38 | + $dir = $wgFileCacheDirectory; |
| 39 | + } elseif ( $wgCacheDirectory ) { |
| 40 | + $dir = "$wgCacheDirectory/html"; |
| 41 | + } else { |
| 42 | + throw new MWException( 'Please set $wgCacheDirectory in LocalSettings.php if you wish to use the HTML file cache' ); |
| 43 | + } |
| 44 | + |
35 | 45 | # Store raw pages (like CSS hits) elsewhere |
36 | 46 | $subdir = ($this->mType === 'raw') ? 'raw/' : ''; |
37 | 47 | $key = $this->mTitle->getPrefixedDbkey(); |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -165,6 +165,12 @@ |
166 | 166 | /**@}*/ |
167 | 167 | |
168 | 168 | /** |
| 169 | + * Directory for caching data in the local filesystem. Should not be accessible |
| 170 | + * from the web.Set this to false to not use any local caches. |
| 171 | + */ |
| 172 | +$wgCacheDirectory = false; |
| 173 | + |
| 174 | +/** |
169 | 175 | * Default value for chmoding of new directories. |
170 | 176 | */ |
171 | 177 | $wgDirectoryMode = 0777; |
— | — | @@ -755,16 +761,36 @@ |
756 | 762 | /**@}*/ |
757 | 763 | |
758 | 764 | /** |
759 | | - * Directory for local copy of message cache, for use in addition to memcached |
| 765 | + * Set this to true to make a local copy of the message cache, for use in |
| 766 | + * addition to memcached. The files will be put in $wgCacheDirectory. |
760 | 767 | */ |
761 | | -$wgLocalMessageCache = false; |
| 768 | +$wgUseLocalMessageCache = false; |
| 769 | + |
762 | 770 | /** |
763 | | - * Defines format of local cache |
764 | | - * true - Serialized object |
765 | | - * false - PHP source file (Warning - security risk) |
| 771 | + * Localisation cache configuration. Associative array with keys: |
| 772 | + * class: The class to use. May be overridden by extensions. |
| 773 | + * |
| 774 | + * store: The location to store cache data. May be 'files', 'db' or |
| 775 | + * 'detect'. If set to "files", data will be in CDB files in |
| 776 | + * the directory specified by $wgCacheDirectory. If set to "db", |
| 777 | + * data will be stored to the database. If set to "detect", files |
| 778 | + * will be used if $wgCacheDirectory is set, otherwise the |
| 779 | + * database will be used. |
| 780 | + * |
| 781 | + * storeClass: The class name for the underlying storage. If set to a class |
| 782 | + * name, it overrides the "store" setting. |
| 783 | + * |
| 784 | + * manualRecache: Set this to true to disable cache updates on web requests. |
| 785 | + * Use maintenance/rebuildLocalisationCache.php instead. |
766 | 786 | */ |
767 | | -$wgLocalMessageCacheSerialized = true; |
| 787 | +$wgLocalisationCacheConf = array( |
| 788 | + 'class' => 'LocalisationCache', |
| 789 | + 'store' => 'detect', |
| 790 | + 'storeClass' => false, |
| 791 | + 'manualRecache' => false, |
| 792 | +); |
768 | 793 | |
| 794 | + |
769 | 795 | # Language settings |
770 | 796 | # |
771 | 797 | /** Site language code, should be one of ./languages/Language(.*).php */ |
— | — | @@ -872,20 +898,6 @@ |
873 | 899 | */ |
874 | 900 | $wgMaxMsgCacheEntrySize = 10000; |
875 | 901 | |
876 | | -/** |
877 | | - * If true, serialized versions of the messages arrays will be |
878 | | - * read from the 'serialized' subdirectory if they are present. |
879 | | - * Set to false to always use the Messages files, regardless of |
880 | | - * whether they are up to date or not. |
881 | | - */ |
882 | | -$wgEnableSerializedMessages = true; |
883 | | - |
884 | | -/** |
885 | | - * Set to false if you are thorough system admin who always remembers to keep |
886 | | - * serialized files up to date to save few mtime calls. |
887 | | - */ |
888 | | -$wgCheckSerialized = true; |
889 | | - |
890 | 902 | /** Whether to enable language variant conversion. */ |
891 | 903 | $wgDisableLangConversion = false; |
892 | 904 | |
— | — | @@ -1509,7 +1521,7 @@ |
1510 | 1522 | $wgUseFileCache = false; |
1511 | 1523 | |
1512 | 1524 | /** Directory where the cached page will be saved */ |
1513 | | -$wgFileCacheDirectory = false; ///< defaults to "{$wgUploadDirectory}/cache"; |
| 1525 | +$wgFileCacheDirectory = false; ///< defaults to "$wgCacheDirectory/html"; |
1514 | 1526 | |
1515 | 1527 | /** |
1516 | 1528 | * When using the file cache, we can store the cached HTML gzipped to save disk |
— | — | @@ -2550,11 +2562,16 @@ |
2551 | 2563 | $wgSkinExtensionFunctions = array(); |
2552 | 2564 | |
2553 | 2565 | /** |
2554 | | - * Extension messages files |
2555 | | - * Associative array mapping extension name to the filename where messages can be found. |
2556 | | - * The file must create a variable called $messages. |
2557 | | - * When the messages are needed, the extension should call wfLoadExtensionMessages(). |
| 2566 | + * Extension messages files. |
2558 | 2567 | * |
| 2568 | + * Associative array mapping extension name to the filename where messages can be |
| 2569 | + * found. The file should contain variable assignments. Any of the variables |
| 2570 | + * present in languages/messages/MessagesEn.php may be defined, but $messages |
| 2571 | + * is the most common. |
| 2572 | + * |
| 2573 | + * Variables defined in extensions will override conflicting variables defined |
| 2574 | + * in the core. |
| 2575 | + * |
2559 | 2576 | * Example: |
2560 | 2577 | * $wgExtensionMessagesFiles['ConfirmEdit'] = dirname(__FILE__).'/ConfirmEdit.i18n.php'; |
2561 | 2578 | * |
— | — | @@ -2563,13 +2580,7 @@ |
2564 | 2581 | |
2565 | 2582 | /** |
2566 | 2583 | * Aliases for special pages provided by extensions. |
2567 | | - * Associative array mapping special page to array of aliases. First alternative |
2568 | | - * for each special page will be used as the normalised name for it. English |
2569 | | - * aliases will be added to the end of the list so that they always work. The |
2570 | | - * file must define a variable $aliases. |
2571 | | - * |
2572 | | - * Example: |
2573 | | - * $wgExtensionAliasesFiles['Translate'] = dirname(__FILE__).'/Translate.alias.php'; |
| 2584 | + * @deprecated Use $specialPageAliases in a file referred to by $wgExtensionMessagesFiles |
2574 | 2585 | */ |
2575 | 2586 | $wgExtensionAliasesFiles = array(); |
2576 | 2587 | |
Index: trunk/phase3/includes/specials/SpecialAllmessages.php |
— | — | @@ -29,8 +29,7 @@ |
30 | 30 | |
31 | 31 | $wgMessageCache->loadAllMessages(); |
32 | 32 | |
33 | | - $sortedArray = array_merge( Language::getMessagesFor( 'en' ), |
34 | | - $wgMessageCache->getExtensionMessagesFor( 'en' ) ); |
| 33 | + $sortedArray = Language::getMessagesFor( 'en' ); |
35 | 34 | ksort( $sortedArray ); |
36 | 35 | |
37 | 36 | $messages = array(); |
Index: trunk/phase3/includes/Exception.php |
— | — | @@ -8,13 +8,13 @@ |
9 | 9 | * @ingroup Exception |
10 | 10 | */ |
11 | 11 | class MWException extends Exception { |
12 | | - |
13 | 12 | /** |
14 | 13 | * Should the exception use $wgOut to output the error ? |
15 | 14 | * @return bool |
16 | 15 | */ |
17 | 16 | function useOutputPage() { |
18 | | - return !empty( $GLOBALS['wgFullyInitialised'] ) && |
| 17 | + return $this->useMessageCache() && |
| 18 | + !empty( $GLOBALS['wgFullyInitialised'] ) && |
19 | 19 | ( !empty( $GLOBALS['wgArticle'] ) || ( !empty( $GLOBALS['wgOut'] ) && !$GLOBALS['wgOut']->isArticle() ) ) && |
20 | 20 | !empty( $GLOBALS['wgTitle'] ); |
21 | 21 | } |
— | — | @@ -25,6 +25,11 @@ |
26 | 26 | */ |
27 | 27 | function useMessageCache() { |
28 | 28 | global $wgLang; |
| 29 | + foreach ( $this->getTrace() as $frame ) { |
| 30 | + if ( $frame['class'] == 'LocalisationCache' ) { |
| 31 | + return false; |
| 32 | + } |
| 33 | + } |
29 | 34 | return is_object( $wgLang ); |
30 | 35 | } |
31 | 36 | |
Index: trunk/phase3/serialized/serialize-localisation.php |
— | — | @@ -1,35 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -$wgNoDBParam = true; |
5 | | -$optionsWithArgs = array( 'o' ); |
6 | | -require_once( dirname(__FILE__).'/../maintenance/commandLine.inc' ); |
7 | | -require_once( dirname(__FILE__).'/serialize.php' ); |
8 | | - |
9 | | -$stderr = fopen( 'php://stderr', 'w' ); |
10 | | -if ( !isset( $args[0] ) ) { |
11 | | - fwrite( $stderr, "No input file specified\n" ); |
12 | | - exit( 1 ); |
13 | | -} |
14 | | -$file = $args[0]; |
15 | | -$code = str_replace( 'Messages', '', basename( $file ) ); |
16 | | -$code = str_replace( '.php', '', $code ); |
17 | | -$code = strtolower( str_replace( '_', '-', $code ) ); |
18 | | - |
19 | | -$localisation = Language::getLocalisationArray( $code, true ); |
20 | | -if ( wfIsWindows() ) { |
21 | | - $localisation = unixLineEndings( $localisation ); |
22 | | -} |
23 | | - |
24 | | -if ( isset( $options['o'] ) ) { |
25 | | - $out = fopen( $options['o'], 'wb' ); |
26 | | - if ( !$out ) { |
27 | | - fwrite( $stderr, "Unable to open file \"{$options['o']}\" for output\n" ); |
28 | | - exit( 1 ); |
29 | | - } |
30 | | -} else { |
31 | | - $out = fopen( 'php://stdout', 'wb' ); |
32 | | -} |
33 | | - |
34 | | -fwrite( $out, serialize( $localisation ) ); |
35 | | - |
36 | | - |
Index: trunk/phase3/serialized/README |
— | — | @@ -1,37 +0,0 @@ |
2 | | -This directory contains data files in the format of PHP's serialize() function. |
3 | | -The source data are typically array literals in PHP source files. We have |
4 | | -observed that unserialize(file_get_contents(...)) is faster than executing such |
5 | | -a file from an oparray cache like APC, and very much faster than loading it by |
6 | | -parsing the source file without such a cache. It should also be faster than |
7 | | -loading the data across the network with memcached, as long as you are careful |
8 | | -to put your MediaWiki root directory on a local hard drive rather than on NFS. |
9 | | -This is a good idea for performance in any case. |
10 | | - |
11 | | -To generate all data files: |
12 | | - |
13 | | - cd /path/to/wiki/serialized |
14 | | - make |
15 | | - |
16 | | -This requires GNU Make. At present, the only serialized data file which is |
17 | | -strictly required is Utf8Case.ser. This contains UTF-8 case conversion tables, |
18 | | -which have essentially never changed since MediaWiki was invented. |
19 | | - |
20 | | -The Messages*.ser files are localisation files, containing user interface text |
21 | | -and various other data related to language-specific behaviour. Because they |
22 | | -are merged with the fallback language (usually English) before caching, they |
23 | | -are all quite large, about 140 KB each at the time of writing. If you generate |
24 | | -all of them, they take up about 20 MB. Hence, I don't expect we will include |
25 | | -all of them in the release tarballs. However, to obtain optimum performance, |
26 | | -YOU SHOULD GENERATE ALL THE LOCALISATION FILES THAT YOU WILL BE USING ON YOUR |
27 | | -WIKIS. |
28 | | - |
29 | | -You can generate individual files by typing a command such as: |
30 | | - cd /path/to/wiki/serialized |
31 | | - make MessagesAr.ser |
32 | | - |
33 | | -If you change a Messages*.php source file, you must recompile any serialized |
34 | | -data files which are present. If you change MessagesEn.php, this will |
35 | | -invalidate *all* Messages*.ser files. |
36 | | - |
37 | | -I think we should distribute a few Messages*.ser files in the release tarballs, |
38 | | -specifically the ones created by "make dist". |
Index: trunk/phase3/serialized/Makefile |
— | — | @@ -1,20 +1,12 @@ |
2 | 2 | |
3 | | -MESSAGE_SOURCES=$(wildcard ../languages/messages/Messages*.php) |
4 | | -MESSAGE_TARGETS=$(patsubst ../languages/messages/Messages%.php, Messages%.ser, $(MESSAGE_SOURCES)) |
5 | 3 | SPECIAL_TARGETS=Utf8Case.ser |
6 | | -ALL_TARGETS=$(MESSAGE_TARGETS) $(SPECIAL_TARGETS) |
7 | | -DIST_TARGETS=$(SPECIAL_TARGETS) \ |
8 | | - MessagesDe.ser \ |
9 | | - MessagesEn.ser \ |
10 | | - MessagesFr.ser \ |
11 | | - MessagesJa.ser \ |
12 | | - MessagesNl.ser \ |
13 | | - MessagesPl.ser \ |
14 | | - MessagesSv.ser |
| 4 | +ALL_TARGETS=$(SPECIAL_TARGETS) |
| 5 | +DIST_TARGETS=$(SPECIAL_TARGETS) |
15 | 6 | |
16 | 7 | .PHONY: all dist clean |
17 | 8 | |
18 | 9 | all: $(ALL_TARGETS) |
| 10 | + @echo 'Warning: messages are no longer serialized by this makefile.' |
19 | 11 | |
20 | 12 | dist: $(DIST_TARGETS) |
21 | 13 | |
— | — | @@ -24,5 +16,3 @@ |
25 | 17 | Utf8Case.ser : ../includes/normal/Utf8Case.php |
26 | 18 | php serialize.php -o $@ $< |
27 | 19 | |
28 | | -Messages%.ser : ../languages/messages/Messages%.php ../languages/messages/MessagesEn.php |
29 | | - php serialize-localisation.php -o $@ $< |
Index: trunk/phase3/config/index.php |
— | — | @@ -1924,6 +1924,11 @@ |
1925 | 1925 | ## you can enable inline LaTeX equations: |
1926 | 1926 | \$wgUseTeX = false; |
1927 | 1927 | |
| 1928 | +## Set \$wgCacheDirectory to a writable directory on the web server |
| 1929 | +## to make your wiki go slightly faster. The directory should not |
| 1930 | +## be publically accessible from the web. |
| 1931 | +#\$wgCacheDirectory = \"\$IP/cache\"; |
| 1932 | + |
1928 | 1933 | \$wgLocalInterwiki = strtolower( \$wgSitename ); |
1929 | 1934 | |
1930 | 1935 | \$wgLanguageCode = \"{$slconf['LanguageCode']}\"; |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -474,6 +474,109 @@ |
475 | 475 | 'button-hr' => 'button_hr.png', |
476 | 476 | ); |
477 | 477 | |
| 478 | +/** |
| 479 | + * A list of messages to preload for each request. |
| 480 | + * We add messages here which are needed for a typical anonymous parser cache hit. |
| 481 | + */ |
| 482 | +$preloadedMessages = array( |
| 483 | + 'aboutpage', |
| 484 | + 'aboutsite', |
| 485 | + 'accesskey-ca-edit', |
| 486 | + 'accesskey-ca-history', |
| 487 | + 'accesskey-ca-nstab-main', |
| 488 | + 'accesskey-ca-talk', |
| 489 | + 'accesskey-n-currentevents', |
| 490 | + 'accesskey-n-help', |
| 491 | + 'accesskey-n-mainpage-description', |
| 492 | + 'accesskey-n-portal', |
| 493 | + 'accesskey-n-randompage', |
| 494 | + 'accesskey-n-recentchanges', |
| 495 | + 'accesskey-n-sitesupport', |
| 496 | + 'accesskey-p-logo', |
| 497 | + 'accesskey-pt-login', |
| 498 | + 'accesskey-search', |
| 499 | + 'accesskey-search-fulltext', |
| 500 | + 'accesskey-search-go', |
| 501 | + 'accesskey-t-permalink', |
| 502 | + 'accesskey-t-print', |
| 503 | + 'accesskey-t-recentchangeslinked', |
| 504 | + 'accesskey-t-specialpages', |
| 505 | + 'accesskey-t-whatlinkshere', |
| 506 | + 'anonnotice', |
| 507 | + 'catseparator', |
| 508 | + 'colon-separator', |
| 509 | + 'currentevents', |
| 510 | + 'currentevents-url', |
| 511 | + 'disclaimerpage', |
| 512 | + 'disclaimers', |
| 513 | + 'edit', |
| 514 | + 'help', |
| 515 | + 'helppage', |
| 516 | + 'history_short', |
| 517 | + 'jumpto', |
| 518 | + 'jumptonavigation', |
| 519 | + 'jumptosearch', |
| 520 | + 'lastmodifiedat', |
| 521 | + 'mainpage', |
| 522 | + 'mainpage-description', |
| 523 | + 'nav-login-createaccount', |
| 524 | + 'navigation', |
| 525 | + 'nstab-main', |
| 526 | + 'opensearch-desc', |
| 527 | + 'pagecategories', |
| 528 | + 'pagecategorieslink', |
| 529 | + 'pagetitle', |
| 530 | + 'pagetitle-view-mainpage', |
| 531 | + 'permalink', |
| 532 | + 'personaltools', |
| 533 | + 'portal', |
| 534 | + 'portal-url', |
| 535 | + 'printableversion', |
| 536 | + 'privacy', |
| 537 | + 'privacypage', |
| 538 | + 'randompage', |
| 539 | + 'randompage-url', |
| 540 | + 'recentchanges', |
| 541 | + 'recentchanges-url', |
| 542 | + 'recentchangeslinked-toolbox', |
| 543 | + 'retrievedfrom', |
| 544 | + 'search', |
| 545 | + 'searcharticle', |
| 546 | + 'searchbutton', |
| 547 | + 'sidebar', |
| 548 | + 'site-atom-feed', |
| 549 | + 'site-rss-feed', |
| 550 | + 'sitenotice', |
| 551 | + 'specialpages', |
| 552 | + 'tagline', |
| 553 | + 'talk', |
| 554 | + 'toolbox', |
| 555 | + 'tooltip-ca-edit', |
| 556 | + 'tooltip-ca-history', |
| 557 | + 'tooltip-ca-nstab-main', |
| 558 | + 'tooltip-ca-talk', |
| 559 | + 'tooltip-n-currentevents', |
| 560 | + 'tooltip-n-help', |
| 561 | + 'tooltip-n-mainpage-description', |
| 562 | + 'tooltip-n-portal', |
| 563 | + 'tooltip-n-randompage', |
| 564 | + 'tooltip-n-recentchanges', |
| 565 | + 'tooltip-n-sitesupport', |
| 566 | + 'tooltip-p-logo', |
| 567 | + 'tooltip-p-navigation', |
| 568 | + 'tooltip-pt-login', |
| 569 | + 'tooltip-search', |
| 570 | + 'tooltip-search-fulltext', |
| 571 | + 'tooltip-search-go', |
| 572 | + 'tooltip-t-permalink', |
| 573 | + 'tooltip-t-print', |
| 574 | + 'tooltip-t-recentchangeslinked', |
| 575 | + 'tooltip-t-specialpages', |
| 576 | + 'tooltip-t-whatlinkshere', |
| 577 | + 'views', |
| 578 | + 'whatlinkshere', |
| 579 | +); |
| 580 | + |
478 | 581 | #------------------------------------------------------------------- |
479 | 582 | # Default messages |
480 | 583 | #------------------------------------------------------------------- |
Index: trunk/phase3/languages/Language.php |
— | — | @@ -57,24 +57,12 @@ |
58 | 58 | var $mConverter, $mVariants, $mCode, $mLoaded = false; |
59 | 59 | var $mMagicExtensions = array(), $mMagicHookDone = false; |
60 | 60 | |
61 | | - static public $mLocalisationKeys = array( |
62 | | - 'fallback', 'namespaceNames', 'mathNames', 'bookstoreList', |
63 | | - 'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable', |
64 | | - 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension', |
65 | | - 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases', |
66 | | - 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap', |
67 | | - 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases', |
68 | | - 'imageFiles' |
69 | | - ); |
| 61 | + var $mNamespaceIds, $namespaceNames, $namespaceAliases; |
| 62 | + var $dateFormatStrings = array(); |
| 63 | + var $minSearchLength; |
| 64 | + var $mExtendedSpecialPageAliases; |
70 | 65 | |
71 | | - static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames', |
72 | | - 'dateFormats', 'defaultUserOptionOverrides', 'magicWords', 'imageFiles' ); |
73 | | - |
74 | | - static public $mMergeableListKeys = array( 'extraUserToggles' ); |
75 | | - |
76 | | - static public $mMergeableAliasListKeys = array( 'specialPageAliases' ); |
77 | | - |
78 | | - static public $mLocalisationCache = array(); |
| 66 | + static public $dataCache; |
79 | 67 | static public $mLangObjCache = array(); |
80 | 68 | |
81 | 69 | static public $mWeekdayMsgs = array( |
— | — | @@ -180,6 +168,15 @@ |
181 | 169 | return $lang; |
182 | 170 | } |
183 | 171 | |
| 172 | + public static function getLocalisationCache() { |
| 173 | + if ( is_null( self::$dataCache ) ) { |
| 174 | + global $wgLocalisationCacheConf; |
| 175 | + $class = $wgLocalisationCacheConf['class']; |
| 176 | + self::$dataCache = new $class( $wgLocalisationCacheConf ); |
| 177 | + } |
| 178 | + return self::$dataCache; |
| 179 | + } |
| 180 | + |
184 | 181 | function __construct() { |
185 | 182 | $this->mConverter = new FakeConverter($this); |
186 | 183 | // Set the code to the name of the descendant |
— | — | @@ -188,6 +185,7 @@ |
189 | 186 | } else { |
190 | 187 | $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) ); |
191 | 188 | } |
| 189 | + self::getLocalisationCache(); |
192 | 190 | } |
193 | 191 | |
194 | 192 | /** |
— | — | @@ -215,7 +213,11 @@ |
216 | 214 | } |
217 | 215 | |
218 | 216 | function getFallbackLanguageCode() { |
219 | | - return self::getFallbackFor( $this->mCode ); |
| 217 | + if ( $this->mCode === 'en' ) { |
| 218 | + return false; |
| 219 | + } else { |
| 220 | + return self::$dataCache->getItem( $this->mCode, 'fallback' ); |
| 221 | + } |
220 | 222 | } |
221 | 223 | |
222 | 224 | /** |
— | — | @@ -223,15 +225,34 @@ |
224 | 226 | * @return array |
225 | 227 | */ |
226 | 228 | function getBookstoreList() { |
227 | | - $this->load(); |
228 | | - return $this->bookstoreList; |
| 229 | + return self::$dataCache->getItem( $this->mCode, 'bookstoreList' ); |
229 | 230 | } |
230 | 231 | |
231 | 232 | /** |
232 | 233 | * @return array |
233 | 234 | */ |
234 | 235 | function getNamespaces() { |
235 | | - $this->load(); |
| 236 | + if ( is_null( $this->namespaceNames ) ) { |
| 237 | + global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk; |
| 238 | + |
| 239 | + $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' ); |
| 240 | + if ( $wgExtraNamespaces ) { |
| 241 | + $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames; |
| 242 | + } |
| 243 | + |
| 244 | + $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace; |
| 245 | + if ( $wgMetaNamespaceTalk ) { |
| 246 | + $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk; |
| 247 | + } else { |
| 248 | + $talk = $this->namespaceNames[NS_PROJECT_TALK]; |
| 249 | + $this->namespaceNames[NS_PROJECT_TALK] = |
| 250 | + $this->fixVariableInNamespace( $talk ); |
| 251 | + } |
| 252 | + |
| 253 | + # The above mixing may leave namespaces out of canonical order. |
| 254 | + # Re-order by namespace ID number... |
| 255 | + ksort( $this->namespaceNames ); |
| 256 | + } |
236 | 257 | return $this->namespaceNames; |
237 | 258 | } |
238 | 259 | |
— | — | @@ -287,11 +308,54 @@ |
288 | 309 | * @return mixed An integer if $text is a valid value otherwise false |
289 | 310 | */ |
290 | 311 | function getLocalNsIndex( $text ) { |
291 | | - $this->load(); |
292 | 312 | $lctext = $this->lc($text); |
293 | | - return isset( $this->mNamespaceIds[$lctext] ) ? $this->mNamespaceIds[$lctext] : false; |
| 313 | + $ids = $this->getNamespaceIds(); |
| 314 | + return isset( $ids[$lctext] ) ? $ids[$lctext] : false; |
294 | 315 | } |
295 | 316 | |
| 317 | + function getNamespaceAliases() { |
| 318 | + if ( is_null( $this->namespaceAliases ) ) { |
| 319 | + $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' ); |
| 320 | + if ( !$aliases ) { |
| 321 | + $aliases = array(); |
| 322 | + } else { |
| 323 | + foreach ( $aliases as $name => $index ) { |
| 324 | + if ( $index === NS_PROJECT_TALK ) { |
| 325 | + unset( $aliases[$name] ); |
| 326 | + $name = $this->fixVariableInNamespace( $name ); |
| 327 | + $aliases[$name] = $index; |
| 328 | + } |
| 329 | + } |
| 330 | + } |
| 331 | + $this->namespaceAliases = $aliases; |
| 332 | + } |
| 333 | + return $this->namespaceAliases; |
| 334 | + } |
| 335 | + |
| 336 | + function getNamespaceIds() { |
| 337 | + if ( is_null( $this->mNamespaceIds ) ) { |
| 338 | + global $wgNamespaceAliases; |
| 339 | + # Put namespace names and aliases into a hashtable. |
| 340 | + # If this is too slow, then we should arrange it so that it is done |
| 341 | + # before caching. The catch is that at pre-cache time, the above |
| 342 | + # class-specific fixup hasn't been done. |
| 343 | + $this->mNamespaceIds = array(); |
| 344 | + foreach ( $this->getNamespaces() as $index => $name ) { |
| 345 | + $this->mNamespaceIds[$this->lc($name)] = $index; |
| 346 | + } |
| 347 | + foreach ( $this->getNamespaceAliases() as $name => $index ) { |
| 348 | + $this->mNamespaceIds[$this->lc($name)] = $index; |
| 349 | + } |
| 350 | + if ( $wgNamespaceAliases ) { |
| 351 | + foreach ( $wgNamespaceAliases as $name => $index ) { |
| 352 | + $this->mNamespaceIds[$this->lc($name)] = $index; |
| 353 | + } |
| 354 | + } |
| 355 | + } |
| 356 | + return $this->mNamespaceIds; |
| 357 | + } |
| 358 | + |
| 359 | + |
296 | 360 | /** |
297 | 361 | * Get a namespace key by value, case insensitive. Canonical namespace |
298 | 362 | * names override custom ones defined for the current language. |
— | — | @@ -300,10 +364,12 @@ |
301 | 365 | * @return mixed An integer if $text is a valid value otherwise false |
302 | 366 | */ |
303 | 367 | function getNsIndex( $text ) { |
304 | | - $this->load(); |
305 | 368 | $lctext = $this->lc($text); |
306 | | - if( ( $ns = MWNamespace::getCanonicalIndex( $lctext ) ) !== null ) return $ns; |
307 | | - return isset( $this->mNamespaceIds[$lctext] ) ? $this->mNamespaceIds[$lctext] : false; |
| 369 | + if ( ( $ns = MWNamespace::getCanonicalIndex( $lctext ) ) !== null ) { |
| 370 | + return $ns; |
| 371 | + } |
| 372 | + $ids = $this->getNamespaceIds(); |
| 373 | + return isset( $ids[$lctext] ) ? $ids[$lctext] : false; |
308 | 374 | } |
309 | 375 | |
310 | 376 | /** |
— | — | @@ -335,48 +401,41 @@ |
336 | 402 | } |
337 | 403 | |
338 | 404 | function getMathNames() { |
339 | | - $this->load(); |
340 | | - return $this->mathNames; |
| 405 | + return self::$dataCache->getItem( $this->mCode, 'mathNames' ); |
341 | 406 | } |
342 | 407 | |
343 | 408 | function getDatePreferences() { |
344 | | - $this->load(); |
345 | | - return $this->datePreferences; |
| 409 | + return self::$dataCache->getItem( $this->mCode, 'datePreferences' ); |
346 | 410 | } |
347 | 411 | |
348 | 412 | function getDateFormats() { |
349 | | - $this->load(); |
350 | | - return $this->dateFormats; |
| 413 | + return self::$dataCache->getItem( $this->mCode, 'dateFormats' ); |
351 | 414 | } |
352 | 415 | |
353 | 416 | function getDefaultDateFormat() { |
354 | | - $this->load(); |
355 | | - return $this->defaultDateFormat; |
| 417 | + $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' ); |
| 418 | + if ( $df === 'dmy or mdy' ) { |
| 419 | + global $wgAmericanDates; |
| 420 | + return $wgAmericanDates ? 'mdy' : 'dmy'; |
| 421 | + } else { |
| 422 | + return $df; |
| 423 | + } |
356 | 424 | } |
357 | 425 | |
358 | 426 | function getDatePreferenceMigrationMap() { |
359 | | - $this->load(); |
360 | | - return $this->datePreferenceMigrationMap; |
| 427 | + return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' ); |
361 | 428 | } |
362 | 429 | |
363 | 430 | function getImageFile( $image ) { |
364 | | - $this->load(); |
365 | | - return $this->imageFiles[$image]; |
| 431 | + return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image ); |
366 | 432 | } |
367 | 433 | |
368 | 434 | function getDefaultUserOptionOverrides() { |
369 | | - $this->load(); |
370 | | - # XXX - apparently some languageas get empty arrays, didn't get to it yet -- midom |
371 | | - if (is_array($this->defaultUserOptionOverrides)) { |
372 | | - return $this->defaultUserOptionOverrides; |
373 | | - } else { |
374 | | - return array(); |
375 | | - } |
| 435 | + return self::$dataCache->getItem( $this->mCode, 'defaultUserOptionOverrides' ); |
376 | 436 | } |
377 | 437 | |
378 | 438 | function getExtraUserToggles() { |
379 | | - $this->load(); |
380 | | - return $this->extraUserToggles; |
| 439 | + return self::$dataCache->getItem( $this->mCode, 'extraUserToggles' ); |
381 | 440 | } |
382 | 441 | |
383 | 442 | function getUserToggle( $tog ) { |
— | — | @@ -1319,6 +1378,28 @@ |
1320 | 1379 | } |
1321 | 1380 | |
1322 | 1381 | /** |
| 1382 | + * Get a format string for a given type and preference |
| 1383 | + * @param $type May be date, time or both |
| 1384 | + * @param $pref The format name as it appears in Messages*.php |
| 1385 | + */ |
| 1386 | + function getDateFormatString( $type, $pref ) { |
| 1387 | + if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) { |
| 1388 | + if ( $pref == 'default' ) { |
| 1389 | + $pref = $this->getDefaultDateFormat(); |
| 1390 | + $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); |
| 1391 | + } else { |
| 1392 | + $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); |
| 1393 | + if ( is_null( $df ) ) { |
| 1394 | + $pref = $this->getDefaultDateFormat(); |
| 1395 | + $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); |
| 1396 | + } |
| 1397 | + } |
| 1398 | + $this->dateFormatStrings[$type][$pref] = $df; |
| 1399 | + } |
| 1400 | + return $this->dateFormatStrings[$type][$pref]; |
| 1401 | + } |
| 1402 | + |
| 1403 | + /** |
1323 | 1404 | * @param $ts Mixed: the time format which needs to be turned into a |
1324 | 1405 | * date('YmdHis') format with wfTimestamp(TS_MW,$ts) |
1325 | 1406 | * @param $adj Bool: whether to adjust the time output according to the |
— | — | @@ -1329,16 +1410,11 @@ |
1330 | 1411 | * @return string |
1331 | 1412 | */ |
1332 | 1413 | function date( $ts, $adj = false, $format = true, $timecorrection = false ) { |
1333 | | - $this->load(); |
1334 | 1414 | if ( $adj ) { |
1335 | 1415 | $ts = $this->userAdjust( $ts, $timecorrection ); |
1336 | 1416 | } |
1337 | | - |
1338 | | - $pref = $this->dateFormat( $format ); |
1339 | | - if( $pref == 'default' || !isset( $this->dateFormats["$pref date"] ) ) { |
1340 | | - $pref = $this->defaultDateFormat; |
1341 | | - } |
1342 | | - return $this->sprintfDate( $this->dateFormats["$pref date"], $ts ); |
| 1417 | + $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) ); |
| 1418 | + return $this->sprintfDate( $df, $ts ); |
1343 | 1419 | } |
1344 | 1420 | |
1345 | 1421 | /** |
— | — | @@ -1352,16 +1428,11 @@ |
1353 | 1429 | * @return string |
1354 | 1430 | */ |
1355 | 1431 | function time( $ts, $adj = false, $format = true, $timecorrection = false ) { |
1356 | | - $this->load(); |
1357 | 1432 | if ( $adj ) { |
1358 | 1433 | $ts = $this->userAdjust( $ts, $timecorrection ); |
1359 | 1434 | } |
1360 | | - |
1361 | | - $pref = $this->dateFormat( $format ); |
1362 | | - if( $pref == 'default' || !isset( $this->dateFormats["$pref time"] ) ) { |
1363 | | - $pref = $this->defaultDateFormat; |
1364 | | - } |
1365 | | - return $this->sprintfDate( $this->dateFormats["$pref time"], $ts ); |
| 1435 | + $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) ); |
| 1436 | + return $this->sprintfDate( $df, $ts ); |
1366 | 1437 | } |
1367 | 1438 | |
1368 | 1439 | /** |
— | — | @@ -1376,30 +1447,20 @@ |
1377 | 1448 | * @return string |
1378 | 1449 | */ |
1379 | 1450 | function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) { |
1380 | | - $this->load(); |
1381 | | - |
1382 | 1451 | $ts = wfTimestamp( TS_MW, $ts ); |
1383 | | - |
1384 | 1452 | if ( $adj ) { |
1385 | 1453 | $ts = $this->userAdjust( $ts, $timecorrection ); |
1386 | 1454 | } |
1387 | | - |
1388 | | - $pref = $this->dateFormat( $format ); |
1389 | | - if( $pref == 'default' || !isset( $this->dateFormats["$pref both"] ) ) { |
1390 | | - $pref = $this->defaultDateFormat; |
1391 | | - } |
1392 | | - |
1393 | | - return $this->sprintfDate( $this->dateFormats["$pref both"], $ts ); |
| 1455 | + $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) ); |
| 1456 | + return $this->sprintfDate( $df, $ts ); |
1394 | 1457 | } |
1395 | 1458 | |
1396 | 1459 | function getMessage( $key ) { |
1397 | | - $this->load(); |
1398 | | - return isset( $this->messages[$key] ) ? $this->messages[$key] : null; |
| 1460 | + return self::$dataCache->getSubitem( $this->mCode, 'messages', $key ); |
1399 | 1461 | } |
1400 | 1462 | |
1401 | 1463 | function getAllMessages() { |
1402 | | - $this->load(); |
1403 | | - return $this->messages; |
| 1464 | + return self::$dataCache->getItem( $this->mCode, 'messages' ); |
1404 | 1465 | } |
1405 | 1466 | |
1406 | 1467 | function iconv( $in, $out, $string ) { |
— | — | @@ -1590,8 +1651,7 @@ |
1591 | 1652 | } |
1592 | 1653 | |
1593 | 1654 | function fallback8bitEncoding() { |
1594 | | - $this->load(); |
1595 | | - return $this->fallback8bitEncoding; |
| 1655 | + return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' ); |
1596 | 1656 | } |
1597 | 1657 | |
1598 | 1658 | /** |
— | — | @@ -1669,7 +1729,7 @@ |
1670 | 1730 | * if we need to pad short words... |
1671 | 1731 | */ |
1672 | 1732 | protected function minSearchLength() { |
1673 | | - if( !isset( $this->minSearchLength ) ) { |
| 1733 | + if( is_null( $this->minSearchLength ) ) { |
1674 | 1734 | $sql = "show global variables like 'ft\\_min\\_word\\_len'"; |
1675 | 1735 | $dbr = wfGetDB( DB_SLAVE ); |
1676 | 1736 | $result = $dbr->query( $sql ); |
— | — | @@ -1789,8 +1849,7 @@ |
1790 | 1850 | * @return bool |
1791 | 1851 | */ |
1792 | 1852 | function isRTL() { |
1793 | | - $this->load(); |
1794 | | - return $this->rtl; |
| 1853 | + return self::$dataCache->getItem( $this->mCode, 'rtl' ); |
1795 | 1854 | } |
1796 | 1855 | |
1797 | 1856 | /** |
— | — | @@ -1803,8 +1862,7 @@ |
1804 | 1863 | } |
1805 | 1864 | |
1806 | 1865 | function capitalizeAllNouns() { |
1807 | | - $this->load(); |
1808 | | - return $this->capitalizeAllNouns; |
| 1866 | + return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' ); |
1809 | 1867 | } |
1810 | 1868 | |
1811 | 1869 | /** |
— | — | @@ -1822,13 +1880,11 @@ |
1823 | 1881 | * @return bool |
1824 | 1882 | */ |
1825 | 1883 | function linkPrefixExtension() { |
1826 | | - $this->load(); |
1827 | | - return $this->linkPrefixExtension; |
| 1884 | + return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' ); |
1828 | 1885 | } |
1829 | 1886 | |
1830 | | - function &getMagicWords() { |
1831 | | - $this->load(); |
1832 | | - return $this->magicWords; |
| 1887 | + function getMagicWords() { |
| 1888 | + return self::$dataCache->getItem( $this->mCode, 'magicWords' ); |
1833 | 1889 | } |
1834 | 1890 | |
1835 | 1891 | # Fill a MagicWord object with data from here |
— | — | @@ -1840,16 +1896,11 @@ |
1841 | 1897 | if ( isset( $this->mMagicExtensions[$mw->mId] ) ) { |
1842 | 1898 | $rawEntry = $this->mMagicExtensions[$mw->mId]; |
1843 | 1899 | } else { |
1844 | | - $magicWords =& $this->getMagicWords(); |
| 1900 | + $magicWords = $this->getMagicWords(); |
1845 | 1901 | if ( isset( $magicWords[$mw->mId] ) ) { |
1846 | 1902 | $rawEntry = $magicWords[$mw->mId]; |
1847 | 1903 | } else { |
1848 | | - # Fall back to English if local list is incomplete |
1849 | | - $magicWords =& Language::getMagicWords(); |
1850 | | - if ( !isset($magicWords[$mw->mId]) ) { |
1851 | | - throw new MWException("Magic word '{$mw->mId}' not found" ); |
1852 | | - } |
1853 | | - $rawEntry = $magicWords[$mw->mId]; |
| 1904 | + $rawEntry = false; |
1854 | 1905 | } |
1855 | 1906 | } |
1856 | 1907 | |
— | — | @@ -1887,43 +1938,11 @@ |
1888 | 1939 | * case folded alias => real name |
1889 | 1940 | */ |
1890 | 1941 | function getSpecialPageAliases() { |
1891 | | - $this->load(); |
1892 | | - |
1893 | 1942 | // Cache aliases because it may be slow to load them |
1894 | | - if ( !isset( $this->mExtendedSpecialPageAliases ) ) { |
1895 | | - |
| 1943 | + if ( is_null( $this->mExtendedSpecialPageAliases ) ) { |
1896 | 1944 | // Initialise array |
1897 | | - $this->mExtendedSpecialPageAliases = $this->specialPageAliases; |
1898 | | - |
1899 | | - global $wgExtensionAliasesFiles; |
1900 | | - foreach ( $wgExtensionAliasesFiles as $file ) { |
1901 | | - |
1902 | | - // Fail fast |
1903 | | - if ( !file_exists($file) ) |
1904 | | - throw new MWException( "Aliases file does not exist: $file" ); |
1905 | | - |
1906 | | - $aliases = array(); |
1907 | | - require($file); |
1908 | | - |
1909 | | - // Check the availability of aliases |
1910 | | - if ( !isset($aliases['en']) ) |
1911 | | - throw new MWException( "Malformed aliases file: $file" ); |
1912 | | - |
1913 | | - // Merge all aliases in fallback chain |
1914 | | - $code = $this->getCode(); |
1915 | | - do { |
1916 | | - if ( !isset($aliases[$code]) ) continue; |
1917 | | - |
1918 | | - $aliases[$code] = $this->fixSpecialPageAliases( $aliases[$code] ); |
1919 | | - /* Merge the aliases, THIS will break if there is special page name |
1920 | | - * which looks like a numerical key, thanks to PHP... |
1921 | | - * See the array_merge_recursive manual entry */ |
1922 | | - $this->mExtendedSpecialPageAliases = array_merge_recursive( |
1923 | | - $this->mExtendedSpecialPageAliases, $aliases[$code] ); |
1924 | | - |
1925 | | - } while ( $code = self::getFallbackFor( $code ) ); |
1926 | | - } |
1927 | | - |
| 1945 | + $this->mExtendedSpecialPageAliases = |
| 1946 | + self::$dataCache->getItem( $this->mCode, 'specialPageAliases' ); |
1928 | 1947 | wfRunHooks( 'LanguageGetSpecialPageAliases', |
1929 | 1948 | array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) ); |
1930 | 1949 | } |
— | — | @@ -1932,20 +1951,6 @@ |
1933 | 1952 | } |
1934 | 1953 | |
1935 | 1954 | /** |
1936 | | - * Function to fix special page aliases. Will convert the first letter to |
1937 | | - * upper case and spaces to underscores. Can be given a full aliases array, |
1938 | | - * in which case it will recursively fix all aliases. |
1939 | | - */ |
1940 | | - public function fixSpecialPageAliases( $mixed ) { |
1941 | | - // Work recursively until in string level |
1942 | | - if ( is_array($mixed) ) { |
1943 | | - $callback = array( $this, 'fixSpecialPageAliases' ); |
1944 | | - return array_map( $callback, $mixed ); |
1945 | | - } |
1946 | | - return str_replace( ' ', '_', $this->ucfirst( $mixed ) ); |
1947 | | - } |
1948 | | - |
1949 | | - /** |
1950 | 1955 | * Italic is unsuitable for some languages |
1951 | 1956 | * |
1952 | 1957 | * @param $text String: the text to be emphasized. |
— | — | @@ -2017,13 +2022,11 @@ |
2018 | 2023 | } |
2019 | 2024 | |
2020 | 2025 | function digitTransformTable() { |
2021 | | - $this->load(); |
2022 | | - return $this->digitTransformTable; |
| 2026 | + return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' ); |
2023 | 2027 | } |
2024 | 2028 | |
2025 | 2029 | function separatorTransformTable() { |
2026 | | - $this->load(); |
2027 | | - return $this->separatorTransformTable; |
| 2030 | + return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' ); |
2028 | 2031 | } |
2029 | 2032 | |
2030 | 2033 | |
— | — | @@ -2380,8 +2383,7 @@ |
2381 | 2384 | * @return string |
2382 | 2385 | */ |
2383 | 2386 | function linkTrail() { |
2384 | | - $this->load(); |
2385 | | - return $this->linkTrail; |
| 2387 | + return self::$dataCache->getItem( $this->mCode, 'linkTrail' ); |
2386 | 2388 | } |
2387 | 2389 | |
2388 | 2390 | function getLangObj() { |
— | — | @@ -2413,308 +2415,33 @@ |
2414 | 2416 | return self::getFileName( "$IP/languages/classes/Language", $code, '.php' ); |
2415 | 2417 | } |
2416 | 2418 | |
2417 | | - static function getLocalisationArray( $code, $disableCache = false ) { |
2418 | | - self::loadLocalisation( $code, $disableCache ); |
2419 | | - return self::$mLocalisationCache[$code]; |
2420 | | - } |
2421 | | - |
2422 | 2419 | /** |
2423 | | - * Load localisation data for a given code into the static cache |
2424 | | - * |
2425 | | - * @return array Dependencies, map of filenames to mtimes |
2426 | | - */ |
2427 | | - static function loadLocalisation( $code, $disableCache = false ) { |
2428 | | - static $recursionGuard = array(); |
2429 | | - global $wgMemc, $wgEnableSerializedMessages, $wgCheckSerialized; |
2430 | | - |
2431 | | - if ( !$code ) { |
2432 | | - throw new MWException( "Invalid language code requested" ); |
2433 | | - } |
2434 | | - |
2435 | | - if ( !$disableCache ) { |
2436 | | - # Try the per-process cache |
2437 | | - if ( isset( self::$mLocalisationCache[$code] ) ) { |
2438 | | - return self::$mLocalisationCache[$code]['deps']; |
2439 | | - } |
2440 | | - |
2441 | | - wfProfileIn( __METHOD__ ); |
2442 | | - |
2443 | | - # Try the serialized directory |
2444 | | - if( $wgEnableSerializedMessages ) { |
2445 | | - $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) ); |
2446 | | - if ( $cache ) { |
2447 | | - if ( $wgCheckSerialized && self::isLocalisationOutOfDate( $cache ) ) { |
2448 | | - $cache = false; |
2449 | | - wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" ); |
2450 | | - } else { |
2451 | | - self::$mLocalisationCache[$code] = $cache; |
2452 | | - wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" ); |
2453 | | - wfProfileOut( __METHOD__ ); |
2454 | | - return self::$mLocalisationCache[$code]['deps']; |
2455 | | - } |
2456 | | - } |
2457 | | - } else { |
2458 | | - $cache = false; |
2459 | | - } |
2460 | | - |
2461 | | - # Try the global cache |
2462 | | - $memcKey = wfMemcKey('localisation', $code ); |
2463 | | - $fbMemcKey = wfMemcKey('fallback', $cache['fallback'] ); |
2464 | | - $cache = $wgMemc->get( $memcKey ); |
2465 | | - if ( $cache ) { |
2466 | | - if ( self::isLocalisationOutOfDate( $cache ) ) { |
2467 | | - $wgMemc->delete( $memcKey ); |
2468 | | - $wgMemc->delete( $fbMemcKey ); |
2469 | | - $cache = false; |
2470 | | - wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired\n" ); |
2471 | | - } else { |
2472 | | - self::$mLocalisationCache[$code] = $cache; |
2473 | | - wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" ); |
2474 | | - wfProfileOut( __METHOD__ ); |
2475 | | - return $cache['deps']; |
2476 | | - } |
2477 | | - } |
2478 | | - } else { |
2479 | | - wfProfileIn( __METHOD__ ); |
2480 | | - } |
2481 | | - |
2482 | | - # Default fallback, may be overridden when the messages file is included |
2483 | | - if ( $code != 'en' ) { |
2484 | | - $fallback = 'en'; |
2485 | | - } else { |
2486 | | - $fallback = false; |
2487 | | - } |
2488 | | - |
2489 | | - # Load the primary localisation from the source file |
2490 | | - $filename = self::getMessagesFileName( $code ); |
2491 | | - if ( !file_exists( $filename ) ) { |
2492 | | - wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" ); |
2493 | | - $cache = compact( self::$mLocalisationKeys ); // Set correct fallback |
2494 | | - $deps = array(); |
2495 | | - } else { |
2496 | | - $deps = array( $filename => filemtime( $filename ) ); |
2497 | | - require( $filename ); |
2498 | | - $cache = compact( self::$mLocalisationKeys ); |
2499 | | - wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" ); |
2500 | | - } |
2501 | | - |
2502 | | - # Load magic word source file |
2503 | | - global $IP; |
2504 | | - $filename = "$IP/includes/MagicWord.php"; |
2505 | | - $newDeps = array( $filename => filemtime( $filename ) ); |
2506 | | - $deps = array_merge( $deps, $newDeps ); |
2507 | | - |
2508 | | - if ( !empty( $fallback ) ) { |
2509 | | - # Load the fallback localisation, with a circular reference guard |
2510 | | - if ( isset( $recursionGuard[$code] ) ) { |
2511 | | - throw new MWException( "Error: Circular fallback reference in language code $code" ); |
2512 | | - } |
2513 | | - $recursionGuard[$code] = true; |
2514 | | - $newDeps = self::loadLocalisation( $fallback, $disableCache ); |
2515 | | - unset( $recursionGuard[$code] ); |
2516 | | - |
2517 | | - $secondary = self::$mLocalisationCache[$fallback]; |
2518 | | - $deps = array_merge( $deps, $newDeps ); |
2519 | | - |
2520 | | - # Merge the fallback localisation with the current localisation |
2521 | | - foreach ( self::$mLocalisationKeys as $key ) { |
2522 | | - if ( isset( $cache[$key] ) ) { |
2523 | | - if ( isset( $secondary[$key] ) ) { |
2524 | | - if ( in_array( $key, self::$mMergeableMapKeys ) ) { |
2525 | | - $cache[$key] = $cache[$key] + $secondary[$key]; |
2526 | | - } elseif ( in_array( $key, self::$mMergeableListKeys ) ) { |
2527 | | - $cache[$key] = array_merge( $secondary[$key], $cache[$key] ); |
2528 | | - } elseif ( in_array( $key, self::$mMergeableAliasListKeys ) ) { |
2529 | | - $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] ); |
2530 | | - } |
2531 | | - } |
2532 | | - } else { |
2533 | | - $cache[$key] = $secondary[$key]; |
2534 | | - } |
2535 | | - } |
2536 | | - |
2537 | | - # Merge bookstore lists if requested |
2538 | | - if ( !empty( $cache['bookstoreList']['inherit'] ) ) { |
2539 | | - $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] ); |
2540 | | - } |
2541 | | - if ( isset( $cache['bookstoreList']['inherit'] ) ) { |
2542 | | - unset( $cache['bookstoreList']['inherit'] ); |
2543 | | - } |
2544 | | - } |
2545 | | - |
2546 | | - # Add dependencies to the cache entry |
2547 | | - $cache['deps'] = $deps; |
2548 | | - |
2549 | | - # Replace spaces with underscores in namespace names |
2550 | | - $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] ); |
2551 | | - |
2552 | | - # And do the same for specialpage aliases. $page is an array. |
2553 | | - foreach ( $cache['specialPageAliases'] as &$page ) { |
2554 | | - $page = str_replace( ' ', '_', $page ); |
2555 | | - } |
2556 | | - # Decouple the reference to prevent accidental damage |
2557 | | - unset($page); |
2558 | | - |
2559 | | - # Save to both caches |
2560 | | - self::$mLocalisationCache[$code] = $cache; |
2561 | | - if ( !$disableCache ) { |
2562 | | - $wgMemc->set( $memcKey, $cache ); |
2563 | | - $wgMemc->set( $fbMemcKey, (string) $cache['fallback'] ); |
2564 | | - } |
2565 | | - |
2566 | | - wfProfileOut( __METHOD__ ); |
2567 | | - return $deps; |
2568 | | - } |
2569 | | - |
2570 | | - /** |
2571 | | - * Test if a given localisation cache is out of date with respect to the |
2572 | | - * source Messages files. This is done automatically for the global cache |
2573 | | - * in $wgMemc, but is only done on certain occasions for the serialized |
2574 | | - * data file. |
2575 | | - * |
2576 | | - * @param $cache mixed Either a language code or a cache array |
2577 | | - */ |
2578 | | - static function isLocalisationOutOfDate( $cache ) { |
2579 | | - if ( !is_array( $cache ) ) { |
2580 | | - self::loadLocalisation( $cache ); |
2581 | | - $cache = self::$mLocalisationCache[$cache]; |
2582 | | - } |
2583 | | - // At least one language file and the MagicWord file needed |
2584 | | - if( count($cache['deps']) < 2 ) { |
2585 | | - return true; |
2586 | | - } |
2587 | | - $expired = false; |
2588 | | - foreach ( $cache['deps'] as $file => $mtime ) { |
2589 | | - if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) { |
2590 | | - $expired = true; |
2591 | | - break; |
2592 | | - } |
2593 | | - } |
2594 | | - return $expired; |
2595 | | - } |
2596 | | - |
2597 | | - /** |
2598 | 2420 | * Get the fallback for a given language |
2599 | 2421 | */ |
2600 | 2422 | static function getFallbackFor( $code ) { |
2601 | | - // Shortcut |
2602 | | - if ( $code === 'en' ) return false; |
2603 | | - |
2604 | | - // Local cache |
2605 | | - static $cache = array(); |
2606 | | - // Quick return |
2607 | | - if ( isset($cache[$code]) ) return $cache[$code]; |
2608 | | - |
2609 | | - // Try memcache |
2610 | | - global $wgMemc; |
2611 | | - $memcKey = wfMemcKey( 'fallback', $code ); |
2612 | | - $fbcode = $wgMemc->get( $memcKey ); |
2613 | | - |
2614 | | - if ( is_string($fbcode) ) { |
2615 | | - // False is stored as a string to detect failures in memcache properly |
2616 | | - if ( $fbcode === '' ) $fbcode = false; |
2617 | | - |
2618 | | - // Update local cache and return |
2619 | | - $cache[$code] = $fbcode; |
2620 | | - return $fbcode; |
| 2423 | + if ( $code === 'en' ) { |
| 2424 | + // Shortcut |
| 2425 | + return false; |
| 2426 | + } else { |
| 2427 | + return self::getLocalisationCache()->getItem( $code, 'fallback' ); |
2621 | 2428 | } |
2622 | | - |
2623 | | - // Nothing in caches, load and and update both caches |
2624 | | - self::loadLocalisation( $code ); |
2625 | | - $fbcode = self::$mLocalisationCache[$code]['fallback']; |
2626 | | - |
2627 | | - $cache[$code] = $fbcode; |
2628 | | - $wgMemc->set( $memcKey, (string) $fbcode ); |
2629 | | - |
2630 | | - return $fbcode; |
2631 | 2429 | } |
2632 | 2430 | |
2633 | 2431 | /** |
2634 | 2432 | * Get all messages for a given language |
| 2433 | + * WARNING: this may take a long time |
2635 | 2434 | */ |
2636 | 2435 | static function getMessagesFor( $code ) { |
2637 | | - self::loadLocalisation( $code ); |
2638 | | - return self::$mLocalisationCache[$code]['messages']; |
| 2436 | + return self::getLocalisationCache()->getItem( $code, 'messages' ); |
2639 | 2437 | } |
2640 | 2438 | |
2641 | 2439 | /** |
2642 | 2440 | * Get a message for a given language |
2643 | 2441 | */ |
2644 | 2442 | static function getMessageFor( $key, $code ) { |
2645 | | - self::loadLocalisation( $code ); |
2646 | | - return isset( self::$mLocalisationCache[$code]['messages'][$key] ) ? self::$mLocalisationCache[$code]['messages'][$key] : null; |
| 2443 | + return self::getLocalisationCache()->getSubitem( $code, 'messages', $key ); |
2647 | 2444 | } |
2648 | 2445 | |
2649 | | - /** |
2650 | | - * Load localisation data for this object |
2651 | | - */ |
2652 | | - function load() { |
2653 | | - if ( !$this->mLoaded ) { |
2654 | | - self::loadLocalisation( $this->getCode() ); |
2655 | | - $cache =& self::$mLocalisationCache[$this->getCode()]; |
2656 | | - foreach ( self::$mLocalisationKeys as $key ) { |
2657 | | - $this->$key = $cache[$key]; |
2658 | | - } |
2659 | | - $this->mLoaded = true; |
2660 | | - |
2661 | | - $this->fixUpSettings(); |
2662 | | - } |
2663 | | - } |
2664 | | - |
2665 | | - /** |
2666 | | - * Do any necessary post-cache-load settings adjustment |
2667 | | - */ |
2668 | | - function fixUpSettings() { |
2669 | | - global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk, |
2670 | | - $wgNamespaceAliases, $wgAmericanDates; |
2671 | | - wfProfileIn( __METHOD__ ); |
2672 | | - if ( $wgExtraNamespaces ) { |
2673 | | - $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames; |
2674 | | - } |
2675 | | - |
2676 | | - $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace; |
2677 | | - if ( $wgMetaNamespaceTalk ) { |
2678 | | - $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk; |
2679 | | - } else { |
2680 | | - $talk = $this->namespaceNames[NS_PROJECT_TALK]; |
2681 | | - $this->namespaceNames[NS_PROJECT_TALK] = |
2682 | | - $this->fixVariableInNamespace( $talk ); |
2683 | | - } |
2684 | | - |
2685 | | - # The above mixing may leave namespaces out of canonical order. |
2686 | | - # Re-order by namespace ID number... |
2687 | | - ksort( $this->namespaceNames ); |
2688 | | - |
2689 | | - # Put namespace names and aliases into a hashtable. |
2690 | | - # If this is too slow, then we should arrange it so that it is done |
2691 | | - # before caching. The catch is that at pre-cache time, the above |
2692 | | - # class-specific fixup hasn't been done. |
2693 | | - $this->mNamespaceIds = array(); |
2694 | | - foreach ( $this->namespaceNames as $index => $name ) { |
2695 | | - $this->mNamespaceIds[$this->lc($name)] = $index; |
2696 | | - } |
2697 | | - if ( $this->namespaceAliases ) { |
2698 | | - foreach ( $this->namespaceAliases as $name => $index ) { |
2699 | | - if ( $index === NS_PROJECT_TALK ) { |
2700 | | - unset( $this->namespaceAliases[$name] ); |
2701 | | - $name = $this->fixVariableInNamespace( $name ); |
2702 | | - $this->namespaceAliases[$name] = $index; |
2703 | | - } |
2704 | | - $this->mNamespaceIds[$this->lc($name)] = $index; |
2705 | | - } |
2706 | | - } |
2707 | | - if ( $wgNamespaceAliases ) { |
2708 | | - foreach ( $wgNamespaceAliases as $name => $index ) { |
2709 | | - $this->mNamespaceIds[$this->lc($name)] = $index; |
2710 | | - } |
2711 | | - } |
2712 | | - |
2713 | | - if ( $this->defaultDateFormat == 'dmy or mdy' ) { |
2714 | | - $this->defaultDateFormat = $wgAmericanDates ? 'mdy' : 'dmy'; |
2715 | | - } |
2716 | | - wfProfileOut( __METHOD__ ); |
2717 | | - } |
2718 | | - |
2719 | 2446 | function fixVariableInNamespace( $talk ) { |
2720 | 2447 | if ( strpos( $talk, '$1' ) === false ) return $talk; |
2721 | 2448 | |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -41,6 +41,15 @@ |
42 | 42 | appropriate privileges. Creating this user with web-install page requires |
43 | 43 | oci8.privileged_connect set to On in php.ini. |
44 | 44 | * Removed UserrightsChangeableGroups hook introduced in 1.14 |
| 45 | +* Added $wgCacheDirectory, to replace $wgFileCacheDirectory, |
| 46 | + $wgLocalMessageCache, and any other local caches which need a place to put |
| 47 | + files. |
| 48 | +* $wgFileCacheDirectory is no longer set to anything by default, and so either |
| 49 | + needs to be set explicitly, or $wgCacheDirectory needs to be set instead. |
| 50 | +* $wgLocalMessageCache has been removed. Instead, set $wgUseLocalMessageCache |
| 51 | + to true |
| 52 | +* Removed $wgEnableSerializedMessages and $wgCheckSerialized. Similar |
| 53 | + functionality is now available via $wgLocalisationCacheConf. |
45 | 54 | |
46 | 55 | === New features in 1.16 === |
47 | 56 | |
— | — | @@ -93,6 +102,12 @@ |
94 | 103 | the DBA extension is not available. |
95 | 104 | * (bug 14611) Added support showing the version of the image thumbnailing |
96 | 105 | engine and diff/diff3 engine. |
| 106 | +* Introduced a new system for localisation caching. The system is based around |
| 107 | + fast fetches of individual messages, minimising memory overhead and startup |
| 108 | + time in the typical case. The database backend will be used by default, but |
| 109 | + set $wgCacheDirectory to get a faster CDB-based implementation. |
| 110 | +* Expanded the number of variables which can be set in the extension messages |
| 111 | + files. |
97 | 112 | |
98 | 113 | === Bug fixes in 1.16 === |
99 | 114 | |