From 63601400e476c6cf43d985f3e7b9864681695ed4 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 18 Jan 2013 16:46:04 +0100 Subject: Update to MediaWiki 1.20.2 this update includes: * adjusted Arch Linux skin * updated FluxBBAuthPlugin * patch for https://bugzilla.wikimedia.org/show_bug.cgi?id=44024 --- languages/Language.php | 399 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 321 insertions(+), 78 deletions(-) (limited to 'languages/Language.php') diff --git a/languages/Language.php b/languages/Language.php index ad544328..40d1f36c 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -1,6 +1,21 @@ mLang = $langobj; } function autoConvertToAllVariants( $text ) { return array( $this->mLang->getCode() => $text ); } @@ -135,6 +154,22 @@ class Language { 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12' ); + /** + * @since 1.20 + * @var array + */ + static public $durationIntervals = array( + 'millennia' => 31557600000, + 'centuries' => 3155760000, + 'decades' => 315576000, + 'years' => 31557600, // 86400 * 365.25 + 'weeks' => 604800, + 'days' => 86400, + 'hours' => 3600, + 'minutes' => 60, + 'seconds' => 1, + ); + /** * Get a cached language object for a given language code * @param $code String @@ -154,6 +189,7 @@ class Language { /** * Create a language object for a given language code * @param $code String + * @throws MWException * @return Language */ protected static function newFromCode( $code ) { @@ -224,10 +260,22 @@ class Language { * * @param $code string * + * @throws MWException * @since 1.18 * @return bool */ public static function isValidBuiltInCode( $code ) { + + if ( !is_string( $code ) ) { + $type = gettype( $code ); + if ( $type === 'object' ) { + $addmsg = " of class " . get_class( $code ); + } else { + $addmsg = ''; + } + throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" ); + } + return preg_match( '/^[a-z0-9-]+$/i', $code ); } @@ -256,10 +304,6 @@ class Language { } if ( !defined( 'MW_COMPILED' ) ) { - // Preload base classes to work around APC/PHP5 bug - if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) { - include_once( "$IP/languages/classes/$class.deps.php" ); - } if ( file_exists( "$IP/languages/classes/$class.php" ) ) { include_once( "$IP/languages/classes/$class.php" ); } @@ -555,11 +599,10 @@ class Language { */ function getVariantname( $code, $usemsg = true ) { $msg = "variantname-$code"; - list( $rootCode ) = explode( '-', $code ); if ( $usemsg && wfMessage( $msg )->exists() ) { return $this->getMessageFromDB( $msg ); } - $name = self::getLanguageName( $code ); + $name = self::fetchLanguageName( $code ); if ( $name ) { return $name; # if it's defined as a language name, show that } else { @@ -659,29 +702,10 @@ class Language { * @param $customisedOnly bool * * @return array + * @deprecated in 1.20, use fetchLanguageNames() */ public static function getLanguageNames( $customisedOnly = false ) { - global $wgExtraLanguageNames; - static $coreLanguageNames; - - if ( $coreLanguageNames === null ) { - include( MWInit::compiledPath( 'languages/Names.php' ) ); - } - - $allNames = $wgExtraLanguageNames + $coreLanguageNames; - if ( !$customisedOnly ) { - return $allNames; - } - - $names = array(); - // We do this using a foreach over the codes instead of a directory - // loop so that messages files in extensions will work correctly. - foreach ( $allNames as $code => $value ) { - if ( is_readable( self::getMessagesFileName( $code ) ) ) { - $names[$code] = $allNames[$code]; - } - } - return $names; + return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' ); } /** @@ -691,16 +715,82 @@ class Language { * @param $code String Language code. * @return Array language code => language name * @since 1.18.0 + * @deprecated in 1.20, use fetchLanguageNames() */ public static function getTranslatedLanguageNames( $code ) { + return self::fetchLanguageNames( $code, 'all' ); + } + + /** + * Get an array of language names, indexed by code. + * @param $inLanguage null|string: Code of language in which to return the names + * Use null for autonyms (native names) + * @param $include string: + * 'all' all available languages + * 'mw' only if the language is defined in MediaWiki or wgExtraLanguageNames (default) + * 'mwfile' only if the language is in 'mw' *and* has a message file + * @return array: language code => language name + * @since 1.20 + */ + public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) { + global $wgExtraLanguageNames; + static $coreLanguageNames; + + if ( $coreLanguageNames === null ) { + include( MWInit::compiledPath( 'languages/Names.php' ) ); + } + $names = array(); - wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $code ) ); - foreach ( self::getLanguageNames() as $code => $name ) { - if ( !isset( $names[$code] ) ) $names[$code] = $name; + if ( $inLanguage ) { + # TODO: also include when $inLanguage is null, when this code is more efficient + wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) ); + } + + $mwNames = $wgExtraLanguageNames + $coreLanguageNames; + foreach ( $mwNames as $mwCode => $mwName ) { + # - Prefer own MediaWiki native name when not using the hook + # - For other names just add if not added through the hook + if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) { + $names[$mwCode] = $mwName; + } + } + + if ( $include === 'all' ) { + return $names; + } + + $returnMw = array(); + $coreCodes = array_keys( $mwNames ); + foreach ( $coreCodes as $coreCode ) { + $returnMw[$coreCode] = $names[$coreCode]; } - return $names; + if ( $include === 'mwfile' ) { + $namesMwFile = array(); + # We do this using a foreach over the codes instead of a directory + # loop so that messages files in extensions will work correctly. + foreach ( $returnMw as $code => $value ) { + if ( is_readable( self::getMessagesFileName( $code ) ) ) { + $namesMwFile[$code] = $names[$code]; + } + } + return $namesMwFile; + } + # 'mw' option; default if it's not one of the other two options (all/mwfile) + return $returnMw; + } + + /** + * @param $code string: The code of the language for which to get the name + * @param $inLanguage null|string: Code of language in which to return the name (null for autonyms) + * @param $include string: 'all', 'mw' or 'mwfile'; see fetchLanguageNames() + * @return string: Language name or empty + * @since 1.20 + */ + public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) { + $array = self::fetchLanguageNames( $inLanguage, $include ); + return !array_key_exists( $code, $array ) ? '' : $array[$code]; } /** @@ -710,7 +800,7 @@ class Language { * @return string */ function getMessageFromDB( $msg ) { - return wfMsgExt( $msg, array( 'parsemag', 'language' => $this ) ); + return wfMessage( $msg )->inLanguage( $this )->text(); } /** @@ -718,13 +808,10 @@ class Language { * Only if defined in MediaWiki, no other data like CLDR. * @param $code string * @return string + * @deprecated in 1.20, use fetchLanguageName() */ function getLanguageName( $code ) { - $names = self::getLanguageNames(); - if ( !array_key_exists( $code, $names ) ) { - return ''; - } - return $names[$code]; + return self::fetchLanguageName( $code ); } /** @@ -1185,7 +1272,7 @@ class Language { $s .= $num; $raw = false; } elseif ( $roman ) { - $s .= self::romanNumeral( $num ); + $s .= Language::romanNumeral( $num ); $roman = false; } elseif ( $hebrewNum ) { $s .= self::hebrewNumeral( $num ); @@ -1574,7 +1661,7 @@ class Language { } /** - * Roman number formatting up to 3000 + * Roman number formatting up to 10000 * * @param $num int * @@ -1585,11 +1672,11 @@ class Language { array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ), array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ), array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ), - array( '', 'M', 'MM', 'MMM' ) + array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM', 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ) ); $num = intval( $num ); - if ( $num > 3000 || $num <= 0 ) { + if ( $num > 10000 || $num <= 0 ) { return $num; } @@ -1862,6 +1949,63 @@ class Language { return $this->sprintfDate( $df, $ts ); } + /** + * Takes a number of seconds and turns it into a text using values such as hours and minutes. + * + * @since 1.20 + * + * @param integer $seconds The amount of seconds. + * @param array $chosenIntervals The intervals to enable. + * + * @return string + */ + public function formatDuration( $seconds, array $chosenIntervals = array() ) { + $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals ); + + $segments = array(); + + foreach ( $intervals as $intervalName => $intervalValue ) { + $message = new Message( 'duration-' . $intervalName, array( $intervalValue ) ); + $segments[] = $message->inLanguage( $this )->escaped(); + } + + return $this->listToText( $segments ); + } + + /** + * Takes a number of seconds and returns an array with a set of corresponding intervals. + * For example 65 will be turned into array( minutes => 1, seconds => 5 ). + * + * @since 1.20 + * + * @param integer $seconds The amount of seconds. + * @param array $chosenIntervals The intervals to enable. + * + * @return array + */ + public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) { + if ( empty( $chosenIntervals ) ) { + $chosenIntervals = array( 'millennia', 'centuries', 'decades', 'years', 'days', 'hours', 'minutes', 'seconds' ); + } + + $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) ); + $sortedNames = array_keys( $intervals ); + $smallestInterval = array_pop( $sortedNames ); + + $segments = array(); + + foreach ( $intervals as $name => $length ) { + $value = floor( $seconds / $length ); + + if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) { + $seconds -= $value * $length; + $segments[$name] = $value; + } + } + + return $segments; + } + /** * Internal helper function for userDate(), userTime() and userTimeAndDate() * @@ -2267,8 +2411,12 @@ class Language { return $s; } - $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' . - '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ); + if ( function_exists( 'mb_check_encoding' ) ) { + $isutf8 = mb_check_encoding( $s, 'UTF-8' ); + } else { + $isutf8 = preg_match( '/^(?>[\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' . + '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s ); + } if ( $isutf8 ) { return $s; } @@ -2494,6 +2642,7 @@ class Language { * @param $file string * @param $string string * + * @throws MWException * @return string */ function transformUsingPairFile( $file, $string ) { @@ -2549,16 +2698,36 @@ class Language { } /** - * A hidden direction mark (LRM or RLM), depending on the language direction + * A hidden direction mark (LRM or RLM), depending on the language direction. + * Unlike getDirMark(), this function returns the character as an HTML entity. + * This function should be used when the output is guaranteed to be HTML, + * because it makes the output HTML source code more readable. When + * the output is plain text or can be escaped, getDirMark() should be used. + * + * @param $opposite Boolean Get the direction mark opposite to your language + * @return string + * @since 1.20 + */ + function getDirMarkEntity( $opposite = false ) { + if ( $opposite ) { return $this->isRTL() ? '‎' : '‏'; } + return $this->isRTL() ? '‏' : '‎'; + } + + /** + * A hidden direction mark (LRM or RLM), depending on the language direction. + * This function produces them as invisible Unicode characters and + * the output may be hard to read and debug, so it should only be used + * when the output is plain text or can be escaped. When the output is + * HTML, use getDirMarkEntity() instead. * * @param $opposite Boolean Get the direction mark opposite to your language * @return string */ function getDirMark( $opposite = false ) { - $rtl = "\xE2\x80\x8F"; - $ltr = "\xE2\x80\x8E"; - if ( $opposite ) { return $this->isRTL() ? $ltr : $rtl; } - return $this->isRTL() ? $rtl : $ltr; + $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM + $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM + if ( $opposite ) { return $this->isRTL() ? $lrm : $rlm; } + return $this->isRTL() ? $rlm : $lrm; } /** @@ -2569,12 +2738,26 @@ class Language { } /** - * An arrow, depending on the language direction + * An arrow, depending on the language direction. * + * @param $direction String: the direction of the arrow: forwards (default), backwards, left, right, up, down. * @return string */ - function getArrow() { - return $this->isRTL() ? '←' : '→'; + function getArrow( $direction = 'forwards' ) { + switch ( $direction ) { + case 'forwards': + return $this->isRTL() ? '←' : '→'; + case 'backwards': + return $this->isRTL() ? '→' : '←'; + case 'left': + return '←'; + case 'right': + return '→'; + case 'up': + return '↑'; + case 'down': + return '↓'; + } } /** @@ -2682,7 +2865,7 @@ class Language { * * An example of this function being called: * - * wfMsg( 'message', $wgLang->formatNum( $num ) ) + * wfMessage( 'message' )->numParams( $num )->text() * * * See LanguageGu.php for the Gujarati implementation and @@ -2814,6 +2997,7 @@ class Language { * Take a list of strings and build a locale-friendly comma-separated * list, using the local comma-separator message. * The last two strings are chained with an "and". + * NOTE: This function will only work with standard numeric array keys (0, 1, 2…) * * @param $l Array * @return string @@ -2821,7 +3005,10 @@ class Language { function listToText( array $l ) { $s = ''; $m = count( $l ) - 1; - if ( $m == 1 ) { + + if ( $m === 0 ) { + return $l[0]; + } elseif ( $m === 1 ) { return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1]; } else { for ( $i = $m; $i >= 0; $i-- ) { @@ -2845,10 +3032,7 @@ class Language { */ function commaList( array $list ) { return implode( - wfMsgExt( - 'comma-separator', - array( 'parsemag', 'escapenoentities', 'language' => $this ) - ), + wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(), $list ); } @@ -2861,10 +3045,7 @@ class Language { */ function semicolonList( array $list ) { return implode( - wfMsgExt( - 'semicolon-separator', - array( 'parsemag', 'escapenoentities', 'language' => $this ) - ), + wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(), $list ); } @@ -2876,10 +3057,7 @@ class Language { */ function pipeList( array $list ) { return implode( - wfMsgExt( - 'pipe-separator', - array( 'escapenoentities', 'language' => $this ) - ), + wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(), $list ); } @@ -2904,7 +3082,7 @@ class Language { function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) { # Use the localized ellipsis character if ( $ellipsis == '...' ) { - $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) ); + $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); } # Check if there is no need to truncate if ( $length == 0 ) { @@ -3002,7 +3180,7 @@ class Language { function truncateHtml( $text, $length, $ellipsis = '...' ) { # Use the localized ellipsis character if ( $ellipsis == '...' ) { - $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) ); + $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); } # Check if there is clearly no need to truncate if ( $length <= 0 ) { @@ -3135,7 +3313,7 @@ class Language { * (b) clear $tag value * @param &$tag string Current HTML tag name we are looking at * @param $tagType int (0-open tag, 1-close tag) - * @param $lastCh char|string Character before the '>' that ended this tag + * @param $lastCh string Character before the '>' that ended this tag * @param &$openTags array Open tag stack (not accounting for $tag) */ private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) { @@ -3167,7 +3345,18 @@ class Language { } return $word; } - + /** + * Get the grammar forms for the content language + * @return array of grammar forms + * @since 1.20 + */ + function getGrammarForms() { + global $wgGrammarForms; + if ( isset( $wgGrammarForms[$this->getCode()] ) && is_array( $wgGrammarForms[$this->getCode()] ) ) { + return $wgGrammarForms[$this->getCode()]; + } + return array(); + } /** * Provides an alternative text depending on specified gender. * Usage {{gender:username|masculine|feminine|neutral}}. @@ -3220,9 +3409,9 @@ class Language { if ( !count( $forms ) ) { return ''; } - $forms = $this->preConvertPlural( $forms, 2 ); - - return ( $count == 1 ) ? $forms[0] : $forms[1]; + $pluralForm = $this->getPluralForm( $count ); + $pluralForm = min( $pluralForm, count( $forms ) - 1 ); + return $forms[$pluralForm]; } /** @@ -3248,7 +3437,7 @@ class Language { * match up with it. * * @param $str String: the validated block duration in English - * @return Somehow translated block duration + * @return string Somehow translated block duration * @see LanguageFi.php for example implementation */ function translateBlockExpiry( $str ) { @@ -3539,6 +3728,7 @@ class Language { * @param $prefix string Prepend this to the filename * @param $code string Language code * @param $suffix string Append this to the filename + * @throws MWException * @return string $prefix . $mangledCode . $suffix */ public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) { @@ -3594,7 +3784,7 @@ class Language { * * @param $code string * - * @return false|string + * @return bool|string */ public static function getFallbackFor( $code ) { if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { @@ -3717,10 +3907,13 @@ class Language { /** * Decode an expiry (block, protection, etc) which has come from the DB * + * @todo FIXME: why are we returnings DBMS-dependent strings??? + * * @param $expiry String: Database expiry String * @param $format Bool|Int true to process using language functions, or TS_ constant * to return the expiry in a given timestamp * @return String + * @since 1.18 */ public function formatExpiry( $expiry, $format = true ) { static $infinity, $infinityMsg; @@ -3914,7 +4107,7 @@ class Language { $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark(); $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) . - wfMsgExt( 'parentheses', array( 'escape', 'replaceafter', 'language' => $this ), $details ) : ''; + wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : ''; return $page . $details; } @@ -3924,7 +4117,7 @@ class Language { * @param $title Title object to link * @param $offset Integer offset parameter * @param $limit Integer limit parameter - * @param $query String optional URL query parameter string + * @param $query array|String optional URL query parameter string * @param $atend Bool optional param for specified if this is the last page * @return String */ @@ -3987,4 +4180,54 @@ class Language { public function getConvRuleTitle() { return $this->mConverter->getConvRuleTitle(); } + + /** + * Get the compiled plural rules for the language + * @since 1.20 + * @return array Associative array with plural form, and plural rule as key-value pairs + */ + public function getCompiledPluralRules() { + $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' ); + $fallbacks = Language::getFallbacksFor( $this->mCode ); + if ( !$pluralRules ) { + foreach ( $fallbacks as $fallbackCode ) { + $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' ); + if ( $pluralRules ) { + break; + } + } + } + return $pluralRules; + } + + /** + * Get the plural rules for the language + * @since 1.20 + * @return array Associative array with plural form, and plural rule as key-value pairs + */ + public function getPluralRules() { + $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' ); + $fallbacks = Language::getFallbacksFor( $this->mCode ); + if ( !$pluralRules ) { + foreach ( $fallbacks as $fallbackCode ) { + $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' ); + if ( $pluralRules ) { + break; + } + } + } + return $pluralRules; + } + + /** + * Find the plural form matching to the given number + * It return the form index. + * @return int The index of the plural form + */ + private function getPluralForm( $number ) { + $pluralRules = $this->getCompiledPluralRules(); + $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules ); + return $form; + } + } -- cgit v1.2.3-54-g00ecf