diff options
Diffstat (limited to 'includes/LocalisationCache.php')
-rw-r--r-- | includes/LocalisationCache.php | 192 |
1 files changed, 153 insertions, 39 deletions
diff --git a/includes/LocalisationCache.php b/includes/LocalisationCache.php index 3b1f45cc..d8e5d3a3 100644 --- a/includes/LocalisationCache.php +++ b/includes/LocalisationCache.php @@ -1,4 +1,24 @@ <?php +/** + * Cache of the contents of localisation files. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ define( 'MW_LC_VERSION', 2 ); @@ -90,7 +110,7 @@ class LocalisationCache { 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap', 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases', 'imageFiles', 'preloadedMessages', 'namespaceGenderAliases', - 'digitGroupingPattern' + 'digitGroupingPattern', 'pluralRules', 'compiledPluralRules', ); /** @@ -98,7 +118,7 @@ class LocalisationCache { * by a fallback sequence. */ static public $mergeableMapKeys = array( 'messages', 'namespaceNames', - 'dateFormats', 'imageFiles', 'preloadedMessages', + 'dateFormats', 'imageFiles', 'preloadedMessages' ); /** @@ -134,6 +154,12 @@ class LocalisationCache { */ static public $preloadedKeys = array( 'dateFormats', 'namespaceNames' ); + /** + * Associative array of cached plural rules. The key is the language code, + * the value is an array of plural rules for that language. + */ + var $pluralRules = null; + var $mergeableKeys = null; /** @@ -214,9 +240,9 @@ class LocalisationCache { */ public function getItem( $code, $key ) { if ( !isset( $this->loadedItems[$code][$key] ) ) { - wfProfileIn( __METHOD__.'-load' ); + wfProfileIn( __METHOD__ . '-load' ); $this->loadItem( $code, $key ); - wfProfileOut( __METHOD__.'-load' ); + wfProfileOut( __METHOD__ . '-load' ); } if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) { @@ -236,9 +262,9 @@ class LocalisationCache { public function getSubitem( $code, $key, $subkey ) { if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) && !isset( $this->loadedItems[$code][$key] ) ) { - wfProfileIn( __METHOD__.'-load' ); + wfProfileIn( __METHOD__ . '-load' ); $this->loadSubitem( $code, $key, $subkey ); - wfProfileOut( __METHOD__.'-load' ); + wfProfileOut( __METHOD__ . '-load' ); } if ( isset( $this->data[$code][$key][$subkey] ) ) { @@ -343,10 +369,11 @@ class LocalisationCache { /** * Returns true if the cache identified by $code is missing or expired. + * @return bool */ public function isExpired( $code ) { if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) { - wfDebug( __METHOD__."($code): forced reload\n" ); + wfDebug( __METHOD__ . "($code): forced reload\n" ); return true; } @@ -355,7 +382,7 @@ class LocalisationCache { $preload = $this->store->get( $code, 'preload' ); // Different keys may expire separately, at least in LCStore_Accel if ( $deps === null || $keys === null || $preload === null ) { - wfDebug( __METHOD__."($code): cache missing, need to make one\n" ); + wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" ); return true; } @@ -365,7 +392,7 @@ class LocalisationCache { // anymore (e.g. uninstalled extensions) // When this happens, always expire the cache if ( !$dep instanceof CacheDependency || $dep->isExpired() ) { - wfDebug( __METHOD__."($code): cache for $code expired due to " . + wfDebug( __METHOD__ . "($code): cache for $code expired due to " . get_class( $dep ) . "\n" ); return true; } @@ -460,9 +487,95 @@ class LocalisationCache { } elseif ( $_fileType == 'aliases' ) { $data = compact( 'aliases' ); } else { - throw new MWException( __METHOD__.": Invalid file type: $_fileType" ); + throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" ); } + return $data; + } + + /** + * Get the compiled plural rules for a given language from the XML files. + * @since 1.20 + */ + public function getCompiledPluralRules( $code ) { + $rules = $this->getPluralRules( $code ); + if ( $rules === null ) { + return null; + } + try { + $compiledRules = CLDRPluralRuleEvaluator::compile( $rules ); + } catch( CLDRPluralRuleError $e ) { + wfDebugLog( 'l10n', $e->getMessage() . "\n" ); + return array(); + } + return $compiledRules; + } + + /** + * Get the plural rules for a given language from the XML files. + * Cached. + * @since 1.20 + */ + public function getPluralRules( $code ) { + if ( $this->pluralRules === null ) { + $cldrPlural = __DIR__ . "/../languages/data/plurals.xml"; + $mwPlural = __DIR__ . "/../languages/data/plurals-mediawiki.xml"; + // Load CLDR plural rules + $this->loadPluralFile( $cldrPlural ); + if ( file_exists( $mwPlural ) ) { + // Override or extend + $this->loadPluralFile( $mwPlural ); + } + } + if ( !isset( $this->pluralRules[$code] ) ) { + return null; + } else { + return $this->pluralRules[$code]; + } + } + + /** + * Load a plural XML file with the given filename, compile the relevant + * rules, and save the compiled rules in a process-local cache. + */ + protected function loadPluralFile( $fileName ) { + $doc = new DOMDocument; + $doc->load( $fileName ); + $rulesets = $doc->getElementsByTagName( "pluralRules" ); + foreach ( $rulesets as $ruleset ) { + $codes = $ruleset->getAttribute( 'locales' ); + $rules = array(); + $ruleElements = $ruleset->getElementsByTagName( "pluralRule" ); + foreach ( $ruleElements as $elt ) { + $rules[] = $elt->nodeValue; + } + foreach ( explode( ' ', $codes ) as $code ) { + $this->pluralRules[$code] = $rules; + } + } + } + + /** + * Read the data from the source files for a given language, and register + * the relevant dependencies in the $deps array. If the localisation + * exists, the data array is returned, otherwise false is returned. + */ + protected function readSourceFilesAndRegisterDeps( $code, &$deps ) { + $fileName = Language::getMessagesFileName( $code ); + if ( !file_exists( $fileName ) ) { + return false; + } + + $deps[] = new FileDependency( $fileName ); + $data = $this->readPHPFile( $fileName, 'core' ); + + # Load CLDR plural rules for JavaScript + $data['pluralRules'] = $this->getPluralRules( $code ); + # And for PHP + $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code ); + + $deps['plurals'] = new FileDependency( __DIR__ . "/../languages/data/plurals.xml" ); + $deps['plurals-mw'] = new FileDependency( __DIR__ . "/../languages/data/plurals-mediawiki.xml" ); return $data; } @@ -564,14 +677,12 @@ class LocalisationCache { $deps = array(); # Load the primary localisation from the source file - $fileName = Language::getMessagesFileName( $code ); - if ( !file_exists( $fileName ) ) { - wfDebug( __METHOD__.": no localisation file for $code, using fallback to en\n" ); + $data = $this->readSourceFilesAndRegisterDeps( $code, $deps ); + if ( $data === false ) { + wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" ); $coreData['fallback'] = 'en'; } else { - $deps[] = new FileDependency( $fileName ); - $data = $this->readPHPFile( $fileName, 'core' ); - wfDebug( __METHOD__.": got localisation for $code from source\n" ); + wfDebug( __METHOD__ . ": got localisation for $code from source\n" ); # Merge primary localisation foreach ( $data as $key => $value ) { @@ -584,7 +695,6 @@ class LocalisationCache { if ( is_null( $coreData['fallback'] ) ) { $coreData['fallback'] = $code === 'en' ? false : 'en'; } - if ( $coreData['fallback'] === false ) { $coreData['fallbackSequence'] = array(); } else { @@ -600,15 +710,11 @@ class LocalisationCache { foreach ( $coreData['fallbackSequence'] as $fbCode ) { # Load the secondary localisation from the source file to # avoid infinite cycles on cyclic fallbacks - $fbFilename = Language::getMessagesFileName( $fbCode ); - - if ( !file_exists( $fbFilename ) ) { + $fbData = $this->readSourceFilesAndRegisterDeps( $fbCode, $deps ); + if ( $fbData === false ) { continue; } - $deps[] = new FileDependency( $fbFilename ); - $fbData = $this->readPHPFile( $fbFilename, 'core' ); - foreach ( self::$allKeys as $key ) { if ( !isset( $fbData[$key] ) ) { continue; @@ -633,7 +739,7 @@ class LocalisationCache { $used = false; foreach ( $data as $key => $item ) { - if( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) { + if ( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) { $used = true; } } @@ -663,19 +769,26 @@ class LocalisationCache { $page = str_replace( ' ', '_', $page ); } # Decouple the reference to prevent accidental damage - unset($page); + unset( $page ); + + # If there were no plural rules, return an empty array + if ( $allData['pluralRules'] === null ) { + $allData['pluralRules'] = array(); + } + if ( $allData['compiledPluralRules'] === null ) { + $allData['compiledPluralRules'] = array(); + } # Set the list keys $allData['list'] = array(); foreach ( self::$splitKeys as $key ) { $allData['list'][$key] = array_keys( $allData[$key] ); } - # Run hooks wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) ); if ( is_null( $allData['namespaceNames'] ) ) { - throw new MWException( __METHOD__.': Localisation data failed sanity check! ' . + throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' . 'Check that your languages/messages/MessagesEn.php file is intact.' ); } @@ -793,8 +906,8 @@ class LocalisationCache { interface LCStore { /** * Get a value. - * @param $code Language code - * @param $key Cache key + * @param $code string Language code + * @param $key string Cache key */ function get( $code, $key ); @@ -903,17 +1016,17 @@ class LCStore_DB implements LCStore { } if ( !$code ) { - throw new MWException( __METHOD__.": Invalid language \"$code\"" ); + throw new MWException( __METHOD__ . ": Invalid language \"$code\"" ); } $this->dbw = wfGetDB( DB_MASTER ); try { - $this->dbw->begin(); + $this->dbw->begin( __METHOD__ ); $this->dbw->delete( 'l10n_cache', array( 'lc_lang' => $code ), __METHOD__ ); } catch ( DBQueryError $e ) { if ( $this->dbw->wasReadOnlyError() ) { $this->readOnly = true; - $this->dbw->rollback(); + $this->dbw->rollback( __METHOD__ ); $this->dbw->ignoreErrors( false ); return; } else { @@ -934,7 +1047,7 @@ class LCStore_DB implements LCStore { $this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ ); } - $this->dbw->commit(); + $this->dbw->commit( __METHOD__ ); $this->currentLang = null; $this->dbw = null; $this->batch = array(); @@ -947,7 +1060,7 @@ class LCStore_DB implements LCStore { } if ( is_null( $this->currentLang ) ) { - throw new MWException( __CLASS__.': must call startWrite() before calling set()' ); + throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' ); } $this->batch[] = array( @@ -1019,7 +1132,7 @@ class LCStore_CDB implements LCStore { } // Close reader to stop permission errors on write - if( !empty($this->readers[$code]) ) { + if ( !empty( $this->readers[$code] ) ) { $this->readers[$code]->close(); } @@ -1037,14 +1150,14 @@ class LCStore_CDB implements LCStore { public function set( $key, $value ) { if ( is_null( $this->writer ) ) { - throw new MWException( __CLASS__.': must call startWrite() before calling set()' ); + throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' ); } $this->writer->set( $key, serialize( $value ) ); } protected function getFileName( $code ) { if ( !$code || strpos( $code, '/' ) !== false ) { - throw new MWException( __METHOD__.": Invalid language \"$code\"" ); + throw new MWException( __METHOD__ . ": Invalid language \"$code\"" ); } return "{$this->directory}/l10n_cache-$code.cdb"; } @@ -1160,8 +1273,9 @@ class LocalisationCache_BulkLoad extends LocalisationCache { while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) { reset( $this->mruLangs ); $code = key( $this->mruLangs ); - wfDebug( __METHOD__.": unloading $code\n" ); + wfDebug( __METHOD__ . ": unloading $code\n" ); $this->unload( $code ); } } -}
\ No newline at end of file + +} |