+ /**
+ * Load localisation data for a given code into the static cache
+ *
+ * @return array Dependencies, map of filenames to mtimes
+ */
+ static function loadLocalisation( $code, $disableCache = false ) {
+ static $recursionGuard = array();
+ global $wgMemc;
+
+ if ( !$code ) {
+ throw new MWException( "Invalid language code requested" );
+ }
+
+ if ( !$disableCache ) {
+ # Try the per-process cache
+ if ( isset( self::$mLocalisationCache[$code] ) ) {
+ return self::$mLocalisationCache[$code]['deps'];
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ # Try the serialized directory
+ $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) );
+ if ( $cache ) {
+ self::$mLocalisationCache[$code] = $cache;
+ wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
+ wfProfileOut( __METHOD__ );
+ return self::$mLocalisationCache[$code]['deps'];
+ }
+
+ # Try the global cache
+ $memcKey = wfMemcKey('localisation', $code );
+ $cache = $wgMemc->get( $memcKey );
+ if ( $cache ) {
+ # Check file modification times
+ foreach ( $cache['deps'] as $file => $mtime ) {
+ if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
+ break;
+ }
+ }
+ if ( self::isLocalisationOutOfDate( $cache ) ) {
+ $wgMemc->delete( $memcKey );
+ $cache = false;
+ wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired due to update of $file\n" );
+ } else {
+ self::$mLocalisationCache[$code] = $cache;
+ wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
+ wfProfileOut( __METHOD__ );
+ return $cache['deps'];
+ }
+ }
+ } else {
+ wfProfileIn( __METHOD__ );
+ }
+
+ # Default fallback, may be overridden when the messages file is included
+ if ( $code != 'en' ) {
+ $fallback = 'en';
+ } else {
+ $fallback = false;
+ }
+
+ # Load the primary localisation from the source file
+ $filename = self::getMessagesFileName( $code );
+ if ( !file_exists( $filename ) ) {
+ wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
+ $cache = array();
+ $deps = array();
+ } else {
+ $deps = array( $filename => filemtime( $filename ) );
+ require( $filename );
+ $cache = compact( self::$mLocalisationKeys );
+ wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
+ }
+
+ if ( !empty( $fallback ) ) {
+ # Load the fallback localisation, with a circular reference guard
+ if ( isset( $recursionGuard[$code] ) ) {
+ throw new MWException( "Error: Circular fallback reference in language code $code" );
+ }
+ $recursionGuard[$code] = true;
+ $newDeps = self::loadLocalisation( $fallback, $disableCache );
+ unset( $recursionGuard[$code] );
+
+ $secondary = self::$mLocalisationCache[$fallback];
+ $deps = array_merge( $deps, $newDeps );
+
+ # Merge the fallback localisation with the current localisation
+ foreach ( self::$mLocalisationKeys as $key ) {
+ if ( isset( $cache[$key] ) ) {
+ if ( isset( $secondary[$key] ) ) {
+ if ( in_array( $key, self::$mMergeableMapKeys ) ) {
+ $cache[$key] = $cache[$key] + $secondary[$key];
+ } elseif ( in_array( $key, self::$mMergeableListKeys ) ) {
+ $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
+ } elseif ( in_array( $key, self::$mMergeableAliasListKeys ) ) {
+ $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
+ }
+ }
+ } else {
+ $cache[$key] = $secondary[$key];
+ }
+ }
+
+ # Merge bookstore lists if requested
+ if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
+ $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
+ }
+ if ( isset( $cache['bookstoreList']['inherit'] ) ) {
+ unset( $cache['bookstoreList']['inherit'] );
+ }
+ }
+
+ # Add dependencies to the cache entry
+ $cache['deps'] = $deps;
+
+ # Replace spaces with underscores in namespace names
+ $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
+
+ # Save to both caches
+ self::$mLocalisationCache[$code] = $cache;
+ if ( !$disableCache ) {
+ $wgMemc->set( $memcKey, $cache );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $deps;
+ }
+
+ /**
+ * Test if a given localisation cache is out of date with respect to the
+ * source Messages files. This is done automatically for the global cache
+ * in $wgMemc, but is only done on certain occasions for the serialized
+ * data file.
+ *
+ * @param $cache mixed Either a language code or a cache array
+ */
+ static function isLocalisationOutOfDate( $cache ) {
+ if ( !is_array( $cache ) ) {
+ self::loadLocalisation( $cache );
+ $cache = self::$mLocalisationCache[$cache];
+ }
+ $expired = false;
+ foreach ( $cache['deps'] as $file => $mtime ) {
+ if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
+ $expired = true;
+ break;
+ }
+ }
+ return $expired;
+ }
+
+ /**
+ * Get the fallback for a given language
+ */
+ static function getFallbackFor( $code ) {
+ self::loadLocalisation( $code );
+ return self::$mLocalisationCache[$code]['fallback'];
+ }
+
+ /**
+ * Get all messages for a given language
+ */
+ static function getMessagesFor( $code ) {
+ self::loadLocalisation( $code );
+ return self::$mLocalisationCache[$code]['messages'];
+ }
+
+ /**
+ * Get a message for a given language
+ */
+ static function getMessageFor( $key, $code ) {
+ self::loadLocalisation( $code );
+ return isset( self::$mLocalisationCache[$code]['messages'][$key] ) ? self::$mLocalisationCache[$code]['messages'][$key] : null;
+ }
+
+ /**
+ * Load localisation data for this object
+ */
+ function load() {
+ if ( !$this->mLoaded ) {
+ self::loadLocalisation( $this->getCode() );
+ $cache =& self::$mLocalisationCache[$this->getCode()];
+ foreach ( self::$mLocalisationKeys as $key ) {
+ $this->$key = $cache[$key];
+ }
+ $this->mLoaded = true;
+
+ $this->fixUpSettings();
+ }
+ }
+
+ /**
+ * Do any necessary post-cache-load settings adjustment
+ */
+ function fixUpSettings() {
+ global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
+ $wgNamespaceAliases, $wgAmericanDates;
+ wfProfileIn( __METHOD__ );
+ if ( $wgExtraNamespaces ) {
+ $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames;
+ }
+
+ $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
+ if ( $wgMetaNamespaceTalk ) {
+ $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
+ } else {
+ $talk = $this->namespaceNames[NS_PROJECT_TALK];
+ $talk = str_replace( '$1', $wgMetaNamespace, $talk );
+
+ # Allow grammar transformations
+ # Allowing full message-style parsing would make simple requests
+ # such as action=raw much more expensive than they need to be.
+ # This will hopefully cover most cases.
+ $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
+ array( &$this, 'replaceGrammarInNamespace' ), $talk );
+ $talk = str_replace( ' ', '_', $talk );
+ $this->namespaceNames[NS_PROJECT_TALK] = $talk;
+ }
+
+ # The above mixing may leave namespaces out of canonical order.
+ # Re-order by namespace ID number...
+ ksort( $this->namespaceNames );
+
+ # Put namespace names and aliases into a hashtable.
+ # If this is too slow, then we should arrange it so that it is done
+ # before caching. The catch is that at pre-cache time, the above
+ # class-specific fixup hasn't been done.
+ $this->mNamespaceIds = array();
+ foreach ( $this->namespaceNames as $index => $name ) {
+ $this->mNamespaceIds[$this->lc($name)] = $index;
+ }
+ if ( $this->namespaceAliases ) {
+ foreach ( $this->namespaceAliases as $name => $index ) {
+ $this->mNamespaceIds[$this->lc($name)] = $index;
+ }
+ }
+ if ( $wgNamespaceAliases ) {
+ foreach ( $wgNamespaceAliases as $name => $index ) {
+ $this->mNamespaceIds[$this->lc($name)] = $index;
+ }
+ }
+
+ if ( $this->defaultDateFormat == 'dmy or mdy' ) {
+ $this->defaultDateFormat = $wgAmericanDates ? 'mdy' : 'dmy';
+ }
+ wfProfileOut( __METHOD__ );
+ }
+
+ function replaceGrammarInNamespace( $m ) {
+ return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
+ }
+
+ static function getCaseMaps() {
+ static $wikiUpperChars, $wikiLowerChars;
+ if ( isset( $wikiUpperChars ) ) {
+ return array( $wikiUpperChars, $wikiLowerChars );
+ }
+
+ wfProfileIn( __METHOD__ );
+ $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
+ if ( $arr === false ) {
+ throw new MWException(
+ "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
+ }
+ extract( $arr );
+ wfProfileOut( __METHOD__ );
+ return array( $wikiUpperChars, $wikiLowerChars );
+ }