31556952000, 'centuries' => 3155695200, 'decades' => 315569520, 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 ) 'weeks' => 604800, 'days' => 86400, 'hours' => 3600, 'minutes' => 60, 'seconds' => 1, ); /** * Cache for language fallbacks. * @see Language::getFallbacksIncludingSiteLanguage * @since 1.21 * @var array */ static private $fallbackLanguageCache = array(); /** * Cache for language names * @var MapCacheLRU|null */ static private $languageNameCache; /** * Get a cached or new language object for a given language code * @param string $code * @return Language */ static function factory( $code ) { global $wgDummyLanguageCodes, $wgLangObjCacheSize; if ( isset( $wgDummyLanguageCodes[$code] ) ) { $code = $wgDummyLanguageCodes[$code]; } // get the language object to process $langObj = isset( self::$mLangObjCache[$code] ) ? self::$mLangObjCache[$code] : self::newFromCode( $code ); // merge the language object in to get it up front in the cache self::$mLangObjCache = array_merge( array( $code => $langObj ), self::$mLangObjCache ); // get rid of the oldest ones in case we have an overflow self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true ); return $langObj; } /** * Create a language object for a given language code * @param string $code * @throws MWException * @return Language */ protected static function newFromCode( $code ) { // Protect against path traversal below if ( !Language::isValidCode( $code ) || strcspn( $code, ":/\\\000" ) !== strlen( $code ) ) { throw new MWException( "Invalid language code \"$code\"" ); } if ( !Language::isValidBuiltInCode( $code ) ) { // It's not possible to customise this code with class files, so // just return a Language object. This is to support uselang= hacks. $lang = new Language; $lang->setCode( $code ); return $lang; } // Check if there is a language class for the code $class = self::classFromCode( $code ); self::preloadLanguageClass( $class ); if ( class_exists( $class ) ) { $lang = new $class; return $lang; } // Keep trying the fallback list until we find an existing class $fallbacks = Language::getFallbacksFor( $code ); foreach ( $fallbacks as $fallbackCode ) { if ( !Language::isValidBuiltInCode( $fallbackCode ) ) { throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" ); } $class = self::classFromCode( $fallbackCode ); self::preloadLanguageClass( $class ); if ( class_exists( $class ) ) { $lang = Language::newFromCode( $fallbackCode ); $lang->setCode( $code ); return $lang; } } throw new MWException( "Invalid fallback sequence for language '$code'" ); } /** * Checks whether any localisation is available for that language tag * in MediaWiki (MessagesXx.php exists). * * @param string $code Language tag (in lower case) * @return bool Whether language is supported * @since 1.21 */ public static function isSupportedLanguage( $code ) { return self::isValidBuiltInCode( $code ) && ( is_readable( self::getMessagesFileName( $code ) ) || is_readable( self::getJsonMessagesFileName( $code ) ) ); } /** * Returns true if a language code string is a well-formed language tag * according to RFC 5646. * This function only checks well-formedness; it doesn't check that * language, script or variant codes actually exist in the repositories. * * Based on regexes by Mark Davis of the Unicode Consortium: * http://unicode.org/repos/cldr/trunk/tools/java/org/unicode/cldr/util/data/langtagRegex.txt * * @param string $code * @param bool $lenient Whether to allow '_' as separator. The default is only '-'. * * @return bool * @since 1.21 */ public static function isWellFormedLanguageTag( $code, $lenient = false ) { $alpha = '[a-z]'; $digit = '[0-9]'; $alphanum = '[a-z0-9]'; $x = 'x'; # private use singleton $singleton = '[a-wy-z]'; # other singleton $s = $lenient ? '[-_]' : '-'; $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}"; $script = "$alpha{4}"; # ISO 15924 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})"; $extension = "$singleton(?:$s$alphanum{2,8})+"; $privateUse = "$x(?:$s$alphanum{1,8})+"; # Define certain grandfathered codes, since otherwise the regex is pretty useless. # Since these are limited, this is safe even later changes to the registry -- # the only oddity is that it might change the type of the tag, and thus # the results from the capturing groups. # http://www.iana.org/assignments/language-subtag-registry $grandfathered = "en{$s}GB{$s}oed" . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)" . "|no{$s}(?:bok|nyn)" . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)" . "|zh{$s}min{$s}nan"; $variantList = "$variant(?:$s$variant)*"; $extensionList = "$extension(?:$s$extension)*"; $langtag = "(?:($language)" . "(?:$s$script)?" . "(?:$s$region)?" . "(?:$s$variantList)?" . "(?:$s$extensionList)?" . "(?:$s$privateUse)?)"; # The final breakdown, with capturing groups for each of these components # The variants, extensions, grandfathered, and private-use may have interior '-' $root = "^(?:$langtag|$privateUse|$grandfathered)$"; return (bool)preg_match( "/$root/", strtolower( $code ) ); } /** * Returns true if a language code string is of a valid form, whether or * not it exists. This includes codes which are used solely for * customisation via the MediaWiki namespace. * * @param string $code * * @return bool */ public static function isValidCode( $code ) { static $cache = array(); if ( isset( $cache[$code] ) ) { return $cache[$code]; } // People think language codes are html safe, so enforce it. // Ideally we should only allow a-zA-Z0-9- // but, .+ and other chars are often used for {{int:}} hacks // see bugs 37564, 37587, 36938 $cache[$code] = strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) && !preg_match( MediaWikiTitleCodec::getTitleInvalidRegex(), $code ); return $cache[$code]; } /** * Returns true if a language code is of a valid form for the purposes of * internal customisation of MediaWiki, via Messages*.php or *.json. * * @param string $code * * @throws MWException * @since 1.18 * @return bool */ public static function isValidBuiltInCode( $code ) { if ( !is_string( $code ) ) { if ( is_object( $code ) ) { $addmsg = " of class " . get_class( $code ); } else { $addmsg = ''; } $type = gettype( $code ); throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" ); } return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code ); } /** * Returns true if a language code is an IETF tag known to MediaWiki. * * @param string $tag * * @since 1.21 * @return bool */ public static function isKnownLanguageTag( $tag ) { static $coreLanguageNames; // Quick escape for invalid input to avoid exceptions down the line // when code tries to process tags which are not valid at all. if ( !self::isValidBuiltInCode( $tag ) ) { return false; } if ( $coreLanguageNames === null ) { global $IP; include "$IP/languages/Names.php"; } if ( isset( $coreLanguageNames[$tag] ) || self::fetchLanguageName( $tag, $tag ) !== '' ) { return true; } return false; } /** * @param string $code * @return string Name of the language class */ public static function classFromCode( $code ) { if ( $code == 'en' ) { return 'Language'; } else { return 'Language' . str_replace( '-', '_', ucfirst( $code ) ); } } /** * Includes language class files * * @param string $class Name of the language class */ public static function preloadLanguageClass( $class ) { global $IP; if ( $class === 'Language' ) { return; } if ( file_exists( "$IP/languages/classes/$class.php" ) ) { include_once "$IP/languages/classes/$class.php"; } } /** * Get the LocalisationCache instance * * @return LocalisationCache */ public static function getLocalisationCache() { if ( is_null( self::$dataCache ) ) { global $wgLocalisationCacheConf; $class = $wgLocalisationCacheConf['class']; self::$dataCache = new $class( $wgLocalisationCacheConf ); } return self::$dataCache; } function __construct() { $this->mConverter = new FakeConverter( $this ); // Set the code to the name of the descendant if ( get_class( $this ) == 'Language' ) { $this->mCode = 'en'; } else { $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) ); } self::getLocalisationCache(); } /** * Reduce memory usage */ function __destruct() { foreach ( $this as $name => $value ) { unset( $this->$name ); } } /** * Hook which will be called if this is the content language. * Descendants can use this to register hook functions or modify globals */ function initContLang() { } /** * @return array * @since 1.19 */ function getFallbackLanguages() { return self::getFallbacksFor( $this->mCode ); } /** * Exports $wgBookstoreListEn * @return array */ function getBookstoreList() { return self::$dataCache->getItem( $this->mCode, 'bookstoreList' ); } /** * Returns an array of localised namespaces indexed by their numbers. If the namespace is not * available in localised form, it will be included in English. * * @return array */ public function getNamespaces() { if ( is_null( $this->namespaceNames ) ) { global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces; $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' ); $validNamespaces = MWNamespace::getCanonicalNamespaces(); $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces; $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace; if ( $wgMetaNamespaceTalk ) { $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk; } else { $talk = $this->namespaceNames[NS_PROJECT_TALK]; $this->namespaceNames[NS_PROJECT_TALK] = $this->fixVariableInNamespace( $talk ); } # Sometimes a language will be localised but not actually exist on this wiki. foreach ( $this->namespaceNames as $key => $text ) { if ( !isset( $validNamespaces[$key] ) ) { unset( $this->namespaceNames[$key] ); } } # The above mixing may leave namespaces out of canonical order. # Re-order by namespace ID number... ksort( $this->namespaceNames ); Hooks::run( 'LanguageGetNamespaces', array( &$this->namespaceNames ) ); } return $this->namespaceNames; } /** * Arbitrarily set all of the namespace names at once. Mainly used for testing * @param array $namespaces Array of namespaces (id => name) */ public function setNamespaces( array $namespaces ) { $this->namespaceNames = $namespaces; $this->mNamespaceIds = null; } /** * Resets all of the namespace caches. Mainly used for testing */ public function resetNamespaces() { $this->namespaceNames = null; $this->mNamespaceIds = null; $this->namespaceAliases = null; } /** * A convenience function that returns the same thing as * getNamespaces() except with the array values changed to ' ' * where it found '_', useful for producing output to be displayed * e.g. in