diff options
Diffstat (limited to 'languages/Language.php')
-rw-r--r-- | languages/Language.php | 926 |
1 files changed, 667 insertions, 259 deletions
diff --git a/languages/Language.php b/languages/Language.php index ac921403..bb9abf16 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -33,6 +33,7 @@ class FakeConverter { function __construct( $langobj ) { $this->mLang = $langobj; } function autoConvertToAllVariants( $text ) { return array( $this->mLang->getCode() => $text ); } function convert( $t ) { return $t; } + function convertTo( $text, $variant ) { return $text; } function convertTitle( $t ) { return $t->getPrefixedText(); } function getVariants() { return array( $this->mLang->getCode() ); } function getPreferredVariant() { return $this->mLang->getCode(); } @@ -53,13 +54,21 @@ class FakeConverter { * @ingroup Language */ class Language { - var $mConverter, $mVariants, $mCode, $mLoaded = false; + + /** + * @var LanguageConverter + */ + var $mConverter; + + var $mVariants, $mCode, $mLoaded = false; var $mMagicExtensions = array(), $mMagicHookDone = false; + private $mHtmlCode = null; - var $mNamespaceIds, $namespaceNames, $namespaceAliases; var $dateFormatStrings = array(); var $mExtendedSpecialPageAliases; + protected $namespaceNames, $mNamespaceIds, $namespaceAliases; + /** * ReplacementArray object caches */ @@ -148,9 +157,6 @@ class Language { * @return Language */ protected static function newFromCode( $code ) { - global $IP; - static $recursionLevel = 0; - // Protect against path traversal below if ( !Language::isValidCode( $code ) || strcspn( $code, ":/\\\000" ) !== strlen( $code ) ) @@ -166,35 +172,31 @@ class Language { return $lang; } - if ( $code == 'en' ) { - $class = 'Language'; - } else { - $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) ); - 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" ); - } - } + // Check if there is a language class for the code + $class = self::classFromCode( $code ); + self::preloadLanguageClass( $class ); + if ( MWInit::classExists( $class ) ) { + $lang = new $class; + return $lang; } - if ( $recursionLevel > 5 ) { - throw new MWException( "Language fallback loop detected when creating class $class\n" ); - } + // 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'" ); + } - if ( !MWInit::classExists( $class ) ) { - $fallback = Language::getFallbackFor( $code ); - ++$recursionLevel; - $lang = Language::newFromCode( $fallback ); - --$recursionLevel; - $lang->setCode( $code ); - } else { - $lang = new $class; + $class = self::classFromCode( $fallbackCode ); + self::preloadLanguageClass( $class ); + if ( MWInit::classExists( $class ) ) { + $lang = Language::newFromCode( $fallbackCode ); + $lang->setCode( $code ); + return $lang; + } } - return $lang; + + throw new MWException( "Invalid fallback sequence for language '$code'" ); } /** @@ -222,7 +224,42 @@ class Language { * @return bool */ public static function isValidBuiltInCode( $code ) { - return preg_match( '/^[a-z0-9-]*$/i', $code ); + return preg_match( '/^[a-z0-9-]+$/i', $code ); + } + + /** + * @param $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 $class string Name of the language class + */ + public static function preloadLanguageClass( $class ) { + global $IP; + + if ( $class === 'Language' ) { + return; + } + + 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" ); + } + } } /** @@ -266,14 +303,21 @@ class Language { function initContLang() { } /** + * Same as getFallbacksFor for current language. * @return array|bool + * @deprecated in 1.19 */ function getFallbackLanguageCode() { - if ( $this->mCode === 'en' ) { - return false; - } else { - return self::$dataCache->getItem( $this->mCode, 'fallback' ); - } + wfDeprecated( __METHOD__ ); + return self::getFallbackFor( $this->mCode ); + } + + /** + * @return array + * @since 1.19 + */ + function getFallbackLanguages() { + return self::getFallbacksFor( $this->mCode ); } /** @@ -287,7 +331,7 @@ class Language { /** * @return array */ - function getNamespaces() { + public function getNamespaces() { if ( is_null( $this->namespaceNames ) ) { global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces; @@ -306,7 +350,7 @@ class Language { } # Sometimes a language will be localised but not actually exist on this wiki. - foreach( $this->namespaceNames as $key => $text ) { + foreach ( $this->namespaceNames as $key => $text ) { if ( !isset( $validNamespaces[$key] ) ) { unset( $this->namespaceNames[$key] ); } @@ -315,11 +359,21 @@ class Language { # The above mixing may leave namespaces out of canonical order. # Re-order by namespace ID number... ksort( $this->namespaceNames ); + + wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) ); } return $this->namespaceNames; } /** + * Arbitrarily set all of the namespace names at once. Mainly used for testing + * @param $namespaces Array of namespaces (id => name) + */ + public function setNamespaces( array $namespaces ) { + $this->namespaceNames = $namespaces; + } + + /** * 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 @@ -373,7 +427,7 @@ class Language { */ function getGenderNsText( $index, $gender ) { global $wgExtraGenderNamespaces; - + $ns = $wgExtraGenderNamespaces + self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index ); } @@ -389,7 +443,7 @@ class Language { if ( count( $wgExtraGenderNamespaces ) > 0 ) { // $wgExtraGenderNamespaces overrides everything return true; - } elseif( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) { + } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) { /// @todo There may be other gender namespace than NS_USER & NS_USER_TALK in the future // $wgExtraNamespaces overrides any gender aliases specified in i18n files return false; @@ -471,7 +525,6 @@ class Language { return $this->mNamespaceIds; } - /** * Get a namespace key by value, case insensitive. Canonical namespace * names override custom ones defined for the current language. @@ -481,7 +534,8 @@ class Language { */ function getNsIndex( $text ) { $lctext = $this->lc( $text ); - if ( ( $ns = MWNamespace::getCanonicalIndex( $lctext ) ) !== null ) { + $ns = MWNamespace::getCanonicalIndex( $lctext ); + if ( $ns !== null ) { return $ns; } $ids = $this->getNamespaceIds(); @@ -492,10 +546,22 @@ class Language { * short names for language variants used for language conversion links. * * @param $code String + * @param $usemsg bool Use the "variantname-xyz" message if it exists * @return string */ - function getVariantname( $code ) { - return $this->getMessageFromDB( "variantname-$code" ); + 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 ); + if ( $name ) { + return $name; # if it's defined as a language name, show that + } else { + # otherwise, output the language code + return $code; + } } /** @@ -570,7 +636,7 @@ class Language { * @return array */ function getExtraUserToggles() { - return self::$dataCache->getItem( $this->mCode, 'extraUserToggles' ); + return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' ); } /** @@ -582,7 +648,8 @@ class Language { } /** - * Get language names, indexed by code. + * Get native language names, indexed by code. + * Only those defined in MediaWiki, no other data like CLDR. * If $customisedOnly is true, only returns codes with a messages file * * @param $customisedOnly bool @@ -602,16 +669,14 @@ class Language { return $allNames; } - global $IP; $names = array(); - $dir = opendir( "$IP/languages/messages" ); - while ( false !== ( $file = readdir( $dir ) ) ) { - $code = self::getCodeFromFileName( $file, 'Messages' ); - if ( $code && isset( $allNames[$code] ) ) { + // 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]; } } - closedir( $dir ); return $names; } @@ -645,6 +710,8 @@ class Language { } /** + * Get the native language name of $code. + * Only if defined in MediaWiki, no other data like CLDR. * @param $code string * @return string */ @@ -669,7 +736,7 @@ class Language { */ function getMonthNamesArray() { $monthNames = array( '' ); - for ( $i=1; $i < 13; $i++ ) { + for ( $i = 1; $i < 13; $i++ ) { $monthNames[] = $this->getMonthName( $i ); } return $monthNames; @@ -696,7 +763,7 @@ class Language { */ function getMonthAbbreviationsArray() { $monthNames = array( '' ); - for ( $i=1; $i < 13; $i++ ) { + for ( $i = 1; $i < 13; $i++ ) { $monthNames[] = $this->getMonthAbbreviation( $i ); } return $monthNames; @@ -751,82 +818,6 @@ class Language { } /** - * Used by date() and time() to adjust the time output. - * - * @param $ts Int the time in date('YmdHis') format - * @param $tz Mixed: adjust the time by this amount (default false, mean we - * get user timecorrection setting) - * @return int - */ - function userAdjust( $ts, $tz = false ) { - global $wgUser, $wgLocalTZoffset; - - if ( $tz === false ) { - $tz = $wgUser->getOption( 'timecorrection' ); - } - - $data = explode( '|', $tz, 3 ); - - if ( $data[0] == 'ZoneInfo' ) { - wfSuppressWarnings(); - $userTZ = timezone_open( $data[2] ); - wfRestoreWarnings(); - if ( $userTZ !== false ) { - $date = date_create( $ts, timezone_open( 'UTC' ) ); - date_timezone_set( $date, $userTZ ); - $date = date_format( $date, 'YmdHis' ); - return $date; - } - # Unrecognized timezone, default to 'Offset' with the stored offset. - $data[0] = 'Offset'; - } - - $minDiff = 0; - if ( $data[0] == 'System' || $tz == '' ) { - # Global offset in minutes. - if ( isset( $wgLocalTZoffset ) ) { - $minDiff = $wgLocalTZoffset; - } - } elseif ( $data[0] == 'Offset' ) { - $minDiff = intval( $data[1] ); - } else { - $data = explode( ':', $tz ); - if ( count( $data ) == 2 ) { - $data[0] = intval( $data[0] ); - $data[1] = intval( $data[1] ); - $minDiff = abs( $data[0] ) * 60 + $data[1]; - if ( $data[0] < 0 ) { - $minDiff = -$minDiff; - } - } else { - $minDiff = intval( $data[0] ) * 60; - } - } - - # No difference ? Return time unchanged - if ( 0 == $minDiff ) { - return $ts; - } - - wfSuppressWarnings(); // E_STRICT system time bitching - # Generate an adjusted date; take advantage of the fact that mktime - # will normalize out-of-range values so we don't have to split $minDiff - # into hours and minutes. - $t = mktime( ( - (int)substr( $ts, 8, 2 ) ), # Hours - (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes - (int)substr( $ts, 12, 2 ), # Seconds - (int)substr( $ts, 4, 2 ), # Month - (int)substr( $ts, 6, 2 ), # Day - (int)substr( $ts, 0, 4 ) ); # Year - - $date = date( 'YmdHis', $t ); - wfRestoreWarnings(); - - return $date; - } - - /** * This is a workalike of PHP's date() function, but with better * internationalisation, a reduced set of format characters, and a better * escaping format. @@ -845,6 +836,7 @@ class Language { * xij j (day number) in Iranian calendar * xiF F (month name) in Iranian calendar * xin n (month number) in Iranian calendar + * xiy y (two digit year) in Iranian calendar * xiY Y (full year) in Iranian calendar * * xjj j (day number) in Hebrew calendar @@ -1063,7 +1055,7 @@ class Language { if ( !$unix ) { $unix = wfTimestamp( TS_UNIX, $ts ); } - $num = date( 'o', $unix ); + $num = gmdate( 'o', $unix ); break; case 'Y': $num = substr( $ts, 0, 4 ); @@ -1107,6 +1099,12 @@ class Language { case 'y': $num = substr( $ts, 2, 2 ); break; + case 'xiy': + if ( !$iranian ) { + $iranian = self::tsToIranian( $ts ); + } + $num = substr( $iranian[0], -2 ); + break; case 'a': $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm'; break; @@ -1222,7 +1220,6 @@ class Language { - floor( ( $gy + 99 ) / 100 ) + floor( ( $gy + 399 ) / 400 ); - // Add days of the past months of this year for ( $i = 0; $i < $gm; $i++ ) { $gDayNo += self::$GREG_DAYS[$i]; @@ -1234,7 +1231,7 @@ class Language { } // Days passed in current month - $gDayNo += $gd; + $gDayNo += (int)$gd; $jDayNo = $gDayNo - 79; @@ -1264,7 +1261,7 @@ class Language { * * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license * - * @link http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0 + * @see http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0 * * @param $ts string * @@ -1595,7 +1592,7 @@ class Language { $s = ''; for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) { if ( $num >= $pow10 ) { - $s .= $table[$i][floor( $num / $pow10 )]; + $s .= $table[$i][(int)floor( $num / $pow10 )]; } $num = $num % $pow10; } @@ -1666,6 +1663,82 @@ class Language { } /** + * Used by date() and time() to adjust the time output. + * + * @param $ts Int the time in date('YmdHis') format + * @param $tz Mixed: adjust the time by this amount (default false, mean we + * get user timecorrection setting) + * @return int + */ + function userAdjust( $ts, $tz = false ) { + global $wgUser, $wgLocalTZoffset; + + if ( $tz === false ) { + $tz = $wgUser->getOption( 'timecorrection' ); + } + + $data = explode( '|', $tz, 3 ); + + if ( $data[0] == 'ZoneInfo' ) { + wfSuppressWarnings(); + $userTZ = timezone_open( $data[2] ); + wfRestoreWarnings(); + if ( $userTZ !== false ) { + $date = date_create( $ts, timezone_open( 'UTC' ) ); + date_timezone_set( $date, $userTZ ); + $date = date_format( $date, 'YmdHis' ); + return $date; + } + # Unrecognized timezone, default to 'Offset' with the stored offset. + $data[0] = 'Offset'; + } + + $minDiff = 0; + if ( $data[0] == 'System' || $tz == '' ) { + # Global offset in minutes. + if ( isset( $wgLocalTZoffset ) ) { + $minDiff = $wgLocalTZoffset; + } + } elseif ( $data[0] == 'Offset' ) { + $minDiff = intval( $data[1] ); + } else { + $data = explode( ':', $tz ); + if ( count( $data ) == 2 ) { + $data[0] = intval( $data[0] ); + $data[1] = intval( $data[1] ); + $minDiff = abs( $data[0] ) * 60 + $data[1]; + if ( $data[0] < 0 ) { + $minDiff = -$minDiff; + } + } else { + $minDiff = intval( $data[0] ) * 60; + } + } + + # No difference ? Return time unchanged + if ( 0 == $minDiff ) { + return $ts; + } + + wfSuppressWarnings(); // E_STRICT system time bitching + # Generate an adjusted date; take advantage of the fact that mktime + # will normalize out-of-range values so we don't have to split $minDiff + # into hours and minutes. + $t = mktime( ( + (int)substr( $ts, 8, 2 ) ), # Hours + (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes + (int)substr( $ts, 12, 2 ), # Seconds + (int)substr( $ts, 4, 2 ), # Month + (int)substr( $ts, 6, 2 ), # Day + (int)substr( $ts, 0, 4 ) ); # Year + + $date = date( 'YmdHis', $t ); + wfRestoreWarnings(); + + return $date; + } + + /** * This is meant to be used by time(), date(), and timeanddate() to get * the date preference they're supposed to use, it should be used in * all children. @@ -1786,6 +1859,114 @@ class Language { } /** + * Internal helper function for userDate(), userTime() and userTimeAndDate() + * + * @param $type String: can be 'date', 'time' or 'both' + * @param $ts Mixed: the time format which needs to be turned into a + * date('YmdHis') format with wfTimestamp(TS_MW,$ts) + * @param $user User object used to get preferences for timezone and format + * @param $options Array, can contain the following keys: + * - 'timecorrection': time correction, can have the following values: + * - true: use user's preference + * - false: don't use time correction + * - integer: value of time correction in minutes + * - 'format': format to use, can have the following values: + * - true: use user's preference + * - false: use default preference + * - string: format to use + * @since 1.19 + * @return String + */ + private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) { + $ts = wfTimestamp( TS_MW, $ts ); + $options += array( 'timecorrection' => true, 'format' => true ); + if ( $options['timecorrection'] !== false ) { + if ( $options['timecorrection'] === true ) { + $offset = $user->getOption( 'timecorrection' ); + } else { + $offset = $options['timecorrection']; + } + $ts = $this->userAdjust( $ts, $offset ); + } + if ( $options['format'] === true ) { + $format = $user->getDatePreference(); + } else { + $format = $options['format']; + } + $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) ); + return $this->sprintfDate( $df, $ts ); + } + + /** + * Get the formatted date for the given timestamp and formatted for + * the given user. + * + * @param $ts Mixed: the time format which needs to be turned into a + * date('YmdHis') format with wfTimestamp(TS_MW,$ts) + * @param $user User object used to get preferences for timezone and format + * @param $options Array, can contain the following keys: + * - 'timecorrection': time correction, can have the following values: + * - true: use user's preference + * - false: don't use time correction + * - integer: value of time correction in minutes + * - 'format': format to use, can have the following values: + * - true: use user's preference + * - false: use default preference + * - string: format to use + * @since 1.19 + * @return String + */ + public function userDate( $ts, User $user, array $options = array() ) { + return $this->internalUserTimeAndDate( 'date', $ts, $user, $options ); + } + + /** + * Get the formatted time for the given timestamp and formatted for + * the given user. + * + * @param $ts Mixed: the time format which needs to be turned into a + * date('YmdHis') format with wfTimestamp(TS_MW,$ts) + * @param $user User object used to get preferences for timezone and format + * @param $options Array, can contain the following keys: + * - 'timecorrection': time correction, can have the following values: + * - true: use user's preference + * - false: don't use time correction + * - integer: value of time correction in minutes + * - 'format': format to use, can have the following values: + * - true: use user's preference + * - false: use default preference + * - string: format to use + * @since 1.19 + * @return String + */ + public function userTime( $ts, User $user, array $options = array() ) { + return $this->internalUserTimeAndDate( 'time', $ts, $user, $options ); + } + + /** + * Get the formatted date and time for the given timestamp and formatted for + * the given user. + * + * @param $ts Mixed: the time format which needs to be turned into a + * date('YmdHis') format with wfTimestamp(TS_MW,$ts) + * @param $user User object used to get preferences for timezone and format + * @param $options Array, can contain the following keys: + * - 'timecorrection': time correction, can have the following values: + * - true: use user's preference + * - false: don't use time correction + * - integer: value of time correction in minutes + * - 'format': format to use, can have the following values: + * - true: use user's preference + * - false: use default preference + * - string: format to use + * @since 1.19 + * @return String + */ + public function userTimeAndDate( $ts, User $user, array $options = array() ) { + return $this->internalUserTimeAndDate( 'both', $ts, $user, $options ); + } + + /** * @param $key string * @return array|null */ @@ -2372,7 +2553,7 @@ class Language { function getDirMark( $opposite = false ) { $rtl = "\xE2\x80\x8F"; $ltr = "\xE2\x80\x8E"; - if( $opposite ) { return $this->isRTL() ? $ltr : $rtl; } + if ( $opposite ) { return $this->isRTL() ? $ltr : $rtl; } return $this->isRTL() ? $rtl : $ltr; } @@ -2451,15 +2632,7 @@ class Language { * @param $newWords array */ function addMagicWordsByLang( $newWords ) { - $code = $this->getCode(); - $fallbackChain = array(); - while ( $code && !in_array( $code, $fallbackChain ) ) { - $fallbackChain[] = $code; - $code = self::getFallbackFor( $code ); - } - if ( !in_array( 'en', $fallbackChain ) ) { - $fallbackChain[] = 'en'; - } + $fallbackChain = $this->getFallbackLanguages(); $fallbackChain = array_reverse( $fallbackChain ); foreach ( $fallbackChain as $code ) { if ( isset( $newWords[$code] ) ) { @@ -2498,7 +2671,7 @@ class Language { /** * Normally we output all numbers in plain en_US style, that is * 293,291.235 for twohundredninetythreethousand-twohundredninetyone - * point twohundredthirtyfive. However this is not sutable for all + * point twohundredthirtyfive. However this is not suitable for all * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as * Icelandic just want to use commas instead of dots, and dots instead * of commas like "293.291,235". @@ -2519,7 +2692,7 @@ class Language { * @param $nocommafy Bool: set to true for special numbers like dates * @return string */ - function formatNum( $number, $nocommafy = false ) { + public function formatNum( $number, $nocommafy = false ) { global $wgTranslateNumerals; if ( !$nocommafy ) { $number = $this->commafy( $number ); @@ -2560,12 +2733,63 @@ class Language { /** * Adds commas to a given number - * + * @since 1.19 * @param $_ mixed * @return string */ function commafy( $_ ) { - return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $_ ) ) ); + $digitGroupingPattern = $this->digitGroupingPattern(); + if ( $_ === null ) { + return ''; + } + + if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) { + // default grouping is at thousands, use the same for ###,###,### pattern too. + return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $_ ) ) ); + } else { + // Ref: http://cldr.unicode.org/translation/number-patterns + $sign = ""; + if ( intval( $_ ) < 0 ) { + // For negative numbers apply the algorithm like positive number and add sign. + $sign = "-"; + $_ = substr( $_, 1 ); + } + $numberpart = array(); + $decimalpart = array(); + $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches ); + preg_match( "/\d+/", $_, $numberpart ); + preg_match( "/\.\d*/", $_, $decimalpart ); + $groupedNumber = ( count( $decimalpart ) > 0 ) ? $decimalpart[0]:""; + if ( $groupedNumber === $_ ) { + // the string does not have any number part. Eg: .12345 + return $sign . $groupedNumber; + } + $start = $end = strlen( $numberpart[0] ); + while ( $start > 0 ) { + $match = $matches[0][$numMatches -1] ; + $matchLen = strlen( $match ); + $start = $end - $matchLen; + if ( $start < 0 ) { + $start = 0; + } + $groupedNumber = substr( $_ , $start, $end -$start ) . $groupedNumber ; + $end = $start; + if ( $numMatches > 1 ) { + // use the last pattern for the rest of the number + $numMatches--; + } + if ( $start > 0 ) { + $groupedNumber = "," . $groupedNumber; + } + } + return $sign . $groupedNumber; + } + } + /** + * @return String + */ + function digitGroupingPattern() { + return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' ); } /** @@ -2590,7 +2814,7 @@ class Language { * @param $l Array * @return string */ - function listToText( $l ) { + function listToText( array $l ) { $s = ''; $m = count( $l ) - 1; if ( $m == 1 ) { @@ -2615,13 +2839,13 @@ class Language { * @param $list array of strings to put in a comma list * @return string */ - function commaList( $list ) { + function commaList( array $list ) { return implode( - $list, wfMsgExt( 'comma-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) - ) + ), + $list ); } @@ -2631,13 +2855,13 @@ class Language { * @param $list array of strings to put in a semicolon list * @return string */ - function semicolonList( $list ) { + function semicolonList( array $list ) { return implode( - $list, wfMsgExt( 'semicolon-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) - ) + ), + $list ); } @@ -2646,13 +2870,13 @@ class Language { * @param $list array of strings to put in a pipe list * @return string */ - function pipeList( $list ) { + function pipeList( array $list ) { return implode( - $list, wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) - ) + ), + $list ); } @@ -2905,10 +3129,10 @@ class Language { * truncateHtml() helper function * (a) push or pop $tag from $openTags as needed * (b) clear $tag value - * @param String &$tag Current HTML tag name we are looking at - * @param int $tagType (0-open tag, 1-close tag) - * @param char $lastCh Character before the '>' that ended this tag - * @param array &$openTags Open tag stack (not accounting for $tag) + * @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 &$openTags array Open tag stack (not accounting for $tag) */ private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) { $tag = ltrim( $tag ); @@ -2945,8 +3169,14 @@ class Language { * Usage {{gender:username|masculine|feminine|neutral}}. * username is optional, in which case the gender of current user is used, * but only in (some) interface messages; otherwise default gender is used. - * If second or third parameter are not specified, masculine is used. - * These details may be overriden per language. + * + * If no forms are given, an empty string is returned. If only one form is + * given, it will be returned unconditionally. These details are implied by + * the caller and cannot be overridden in subclasses. + * + * If more than one form is given, the default is to use the neutral one + * if it is specified, and to use the masculine one otherwise. These + * details can be overridden in subclasses. * * @param $gender string * @param $forms array @@ -3019,7 +3249,7 @@ class Language { */ function translateBlockExpiry( $str ) { $duration = SpecialBlock::getSuggestedDurations( $this ); - foreach( $duration as $show => $value ){ + foreach ( $duration as $show => $value ) { if ( strcmp( $str, $value ) == 0 ) { return htmlspecialchars( trim( $show ) ); } @@ -3029,7 +3259,7 @@ class Language { // equivalents if still here. $indefs = array( 'infinite', 'infinity', 'indefinite' ); if ( in_array( $str, $indefs ) ) { - foreach( $indefs as $val ) { + foreach ( $indefs as $val ) { $show = array_search( $val, $duration, true ); if ( $show !== false ) { return htmlspecialchars( trim( $show ) ); @@ -3047,7 +3277,7 @@ class Language { * @param $text String * @return String */ - function segmentForDiff( $text ) { + public function segmentForDiff( $text ) { return $text; } @@ -3057,17 +3287,27 @@ class Language { * @param $text String * @return String */ - function unsegmentForDiff( $text ) { + public function unsegmentForDiff( $text ) { return $text; } /** + * Return the LanguageConverter used in the Language + * + * @since 1.19 + * @return LanguageConverter + */ + public function getConverter() { + return $this->mConverter; + } + + /** * convert text to all supported variants * * @param $text string * @return array */ - function autoConvertToAllVariants( $text ) { + public function autoConvertToAllVariants( $text ) { return $this->mConverter->autoConvertToAllVariants( $text ); } @@ -3077,18 +3317,17 @@ class Language { * @param $text string * @return string */ - function convert( $text ) { + public function convert( $text ) { return $this->mConverter->convert( $text ); } - /** * Convert a Title object to a string in the preferred variant * * @param $title Title * @return string */ - function convertTitle( $title ) { + public function convertTitle( $title ) { return $this->mConverter->convertTitle( $title ); } @@ -3097,17 +3336,28 @@ class Language { * * @return bool */ - function hasVariants() { + public function hasVariants() { return sizeof( $this->getVariants() ) > 1; } /** + * Check if the language has the specific variant + * + * @since 1.19 + * @param $variant string + * @return bool + */ + public function hasVariant( $variant ) { + return (bool)$this->mConverter->validateVariant( $variant ); + } + + /** * Put custom tags (e.g. -{ }-) around math to prevent conversion * * @param $text string * @return string */ - function armourMath( $text ) { + public function armourMath( $text ) { return $this->mConverter->armourMath( $text ); } @@ -3118,7 +3368,7 @@ class Language { * @return string * @todo this should get integrated somewhere sane */ - function convertHtml( $text, $isTitle = false ) { + public function convertHtml( $text, $isTitle = false ) { return htmlspecialchars( $this->convert( $text, $isTitle ) ); } @@ -3126,7 +3376,7 @@ class Language { * @param $key string * @return string */ - function convertCategoryKey( $key ) { + public function convertCategoryKey( $key ) { return $this->mConverter->convertCategoryKey( $key ); } @@ -3136,28 +3386,28 @@ class Language { * * @return array an array of language codes */ - function getVariants() { + public function getVariants() { return $this->mConverter->getVariants(); } /** * @return string */ - function getPreferredVariant() { + public function getPreferredVariant() { return $this->mConverter->getPreferredVariant(); } /** * @return string */ - function getDefaultVariant() { + public function getDefaultVariant() { return $this->mConverter->getDefaultVariant(); } /** * @return string */ - function getURLVariant() { + public function getURLVariant() { return $this->mConverter->getURLVariant(); } @@ -3173,7 +3423,7 @@ class Language { * we need to transclude a template or update a category's link * @return null the input parameters may be modified upon return */ - function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) { + public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) { $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond ); } @@ -3188,7 +3438,7 @@ class Language { * * @return string */ - function convertLinkToAllVariants( $text ) { + public function convertLinkToAllVariants( $text ) { return $this->mConverter->convertLinkToAllVariants( $text ); } @@ -3209,7 +3459,7 @@ class Language { * * @return string */ - function getParsedTitle() { + public function getParsedTitle() { return $this->mConverter->getParsedTitle(); } @@ -3221,7 +3471,7 @@ class Language { * @param $noParse bool * @return string the tagged text */ - function markNoConversion( $text, $noParse = false ) { + public function markNoConversion( $text, $noParse = false ) { return $this->mConverter->markNoConversion( $text, $noParse ); } @@ -3231,7 +3481,7 @@ class Language { * * @return string */ - function linkTrail() { + public function linkTrail() { return self::$dataCache->getItem( $this->mCode, 'linkTrail' ); } @@ -3247,15 +3497,30 @@ class Language { * * @return string */ - function getCode() { + public function getCode() { return $this->mCode; } /** + * Get the code in Bcp47 format which we can use + * inside of html lang="" tags. + * @since 1.19 + * @return string + */ + public function getHtmlCode() { + if ( is_null( $this->mHtmlCode ) ) { + $this->mHtmlCode = wfBCP47( $this->getCode() ); + } + return $this->mHtmlCode; + } + + /** * @param $code string */ - function setCode( $code ) { + public function setCode( $code ) { $this->mCode = $code; + // Ensure we don't leave an incorrect html code lying around + $this->mHtmlCode = null; } /** @@ -3265,7 +3530,7 @@ class Language { * @param $suffix string Append this to the filename * @return string $prefix . $mangledCode . $suffix */ - static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) { + public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) { // Protect against path traversal if ( !Language::isValidCode( $code ) || strcspn( $code, ":/\\\000" ) !== strlen( $code ) ) @@ -3283,7 +3548,7 @@ class Language { * @param $suffix string Suffix after the language code * @return string Language code, or false if $prefix or $suffix isn't found */ - static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) { + public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) { $m = null; preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' . preg_quote( $suffix, '/' ) . '/', $filename, $m ); @@ -3297,45 +3562,69 @@ class Language { * @param $code string * @return string */ - static function getMessagesFileName( $code ) { + public static function getMessagesFileName( $code ) { global $IP; - return self::getFileName( "$IP/languages/messages/Messages", $code, '.php' ); + $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' ); + wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) ); + return $file; } /** * @param $code string * @return string */ - static function getClassFileName( $code ) { + public static function getClassFileName( $code ) { global $IP; return self::getFileName( "$IP/languages/classes/Language", $code, '.php' ); } /** - * Get the fallback for a given language + * Get the first fallback for a given language. * * @param $code string * * @return false|string */ - static function getFallbackFor( $code ) { - if ( $code === 'en' ) { - // Shortcut + public static function getFallbackFor( $code ) { + if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { return false; } else { - return self::getLocalisationCache()->getItem( $code, 'fallback' ); + $fallbacks = self::getFallbacksFor( $code ); + $first = array_shift( $fallbacks ); + return $first; + } + } + + /** + * Get the ordered list of fallback languages. + * + * @since 1.19 + * @param $code string Language code + * @return array + */ + public static function getFallbacksFor( $code ) { + if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { + return array(); + } else { + $v = self::getLocalisationCache()->getItem( $code, 'fallback' ); + $v = array_map( 'trim', explode( ',', $v ) ); + if ( $v[count( $v ) - 1] !== 'en' ) { + $v[] = 'en'; + } + return $v; } } /** * Get all messages for a given language - * WARNING: this may take a long time + * WARNING: this may take a long time. If you just need all message *keys* + * but need the *contents* of only a few messages, consider using getMessageKeysFor(). * * @param $code string * * @return array */ - static function getMessagesFor( $code ) { + public static function getMessagesFor( $code ) { return self::getLocalisationCache()->getItem( $code, 'messages' ); } @@ -3347,11 +3636,23 @@ class Language { * * @return string */ - static function getMessageFor( $key, $code ) { + public static function getMessageFor( $key, $code ) { return self::getLocalisationCache()->getSubitem( $code, 'messages', $key ); } /** + * Get all message keys for a given language. This is a faster alternative to + * array_keys( Language::getMessagesFor( $code ) ) + * + * @since 1.19 + * @param $code string Language code + * @return array of message keys (strings) + */ + public static function getMessageKeysFor( $code ) { + return self::getLocalisationCache()->getSubItemList( $code, 'messages' ); + } + + /** * @param $talk * @return mixed */ @@ -3412,7 +3713,7 @@ class Language { */ public function formatExpiry( $expiry, $format = true ) { static $infinity, $infinityMsg; - if( $infinity === null ){ + if ( $infinity === null ) { $infinityMsg = wfMessage( 'infiniteblock' ); $infinity = wfGetDB( DB_SLAVE )->getInfinity(); } @@ -3431,18 +3732,38 @@ class Language { /** * @todo Document * @param $seconds int|float - * @param $format String Optional, one of ("avoidseconds","avoidminutes"): - * "avoidseconds" - don't mention seconds if $seconds >= 1 hour - * "avoidminutes" - don't mention seconds/minutes if $seconds > 48 hours + * @param $format Array Optional + * If $format['avoid'] == 'avoidseconds' - don't mention seconds if $seconds >= 1 hour + * If $format['avoid'] == 'avoidminutes' - don't mention seconds/minutes if $seconds > 48 hours + * If $format['noabbrevs'] is true - use 'seconds' and friends instead of 'seconds-abbrev' and friends + * For backwards compatibility, $format may also be one of the strings 'avoidseconds' or 'avoidminutes' * @return string */ - function formatTimePeriod( $seconds, $format = false ) { + function formatTimePeriod( $seconds, $format = array() ) { + if ( !is_array( $format ) ) { + $format = array( 'avoid' => $format ); // For backwards compatibility + } + if ( !isset( $format['avoid'] ) ) { + $format['avoid'] = false; + } + if ( !isset( $format['noabbrevs' ] ) ) { + $format['noabbrevs'] = false; + } + $secondsMsg = wfMessage( + $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this ); + $minutesMsg = wfMessage( + $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this ); + $hoursMsg = wfMessage( + $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this ); + $daysMsg = wfMessage( + $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this ); + if ( round( $seconds * 10 ) < 100 ) { $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) ); - $s .= $this->getMessageFromDB( 'seconds-abbrev' ); + $s = $secondsMsg->params( $s )->text(); } elseif ( round( $seconds ) < 60 ) { $s = $this->formatNum( round( $seconds ) ); - $s .= $this->getMessageFromDB( 'seconds-abbrev' ); + $s = $secondsMsg->params( $s )->text(); } elseif ( round( $seconds ) < 3600 ) { $minutes = floor( $seconds / 60 ); $secondsPart = round( fmod( $seconds, 60 ) ); @@ -3450,10 +3771,10 @@ class Language { $secondsPart = 0; $minutes++; } - $s = $this->formatNum( $minutes ) . $this->getMessageFromDB( 'minutes-abbrev' ); + $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text(); $s .= ' '; - $s .= $this->formatNum( $secondsPart ) . $this->getMessageFromDB( 'seconds-abbrev' ); - } elseif ( round( $seconds ) <= 2*86400 ) { + $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text(); + } elseif ( round( $seconds ) <= 2 * 86400 ) { $hours = floor( $seconds / 3600 ); $minutes = floor( ( $seconds - $hours * 3600 ) / 60 ); $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 ); @@ -3465,25 +3786,24 @@ class Language { $minutes = 0; $hours++; } - $s = $this->formatNum( $hours ) . $this->getMessageFromDB( 'hours-abbrev' ); + $s = $hoursMsg->params( $this->formatNum( $hours ) )->text(); $s .= ' '; - $s .= $this->formatNum( $minutes ) . $this->getMessageFromDB( 'minutes-abbrev' ); - if ( !in_array( $format, array( 'avoidseconds', 'avoidminutes' ) ) ) { - $s .= ' ' . $this->formatNum( $secondsPart ) . - $this->getMessageFromDB( 'seconds-abbrev' ); + $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text(); + if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) { + $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text(); } } else { $days = floor( $seconds / 86400 ); - if ( $format === 'avoidminutes' ) { + if ( $format['avoid'] === 'avoidminutes' ) { $hours = round( ( $seconds - $days * 86400 ) / 3600 ); if ( $hours == 24 ) { $hours = 0; $days++; } - $s = $this->formatNum( $days ) . $this->getMessageFromDB( 'days-abbrev' ); + $s = $daysMsg->params( $this->formatNum( $days ) )->text(); $s .= ' '; - $s .= $this->formatNum( $hours ) . $this->getMessageFromDB( 'hours-abbrev' ); - } elseif ( $format === 'avoidseconds' ) { + $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text(); + } elseif ( $format['avoid'] === 'avoidseconds' ) { $hours = floor( ( $seconds - $days * 86400 ) / 3600 ); $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 ); if ( $minutes == 60 ) { @@ -3494,13 +3814,13 @@ class Language { $hours = 0; $days++; } - $s = $this->formatNum( $days ) . $this->getMessageFromDB( 'days-abbrev' ); + $s = $daysMsg->params( $this->formatNum( $days ) )->text(); $s .= ' '; - $s .= $this->formatNum( $hours ) . $this->getMessageFromDB( 'hours-abbrev' ); + $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text(); $s .= ' '; - $s .= $this->formatNum( $minutes ) . $this->getMessageFromDB( 'minutes-abbrev' ); + $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text(); } else { - $s = $this->formatNum( $days ) . $this->getMessageFromDB( 'days-abbrev' ); + $s = $daysMsg->params( $this->formatNum( $days ) )->text(); $s .= ' '; $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format ); } @@ -3509,55 +3829,143 @@ class Language { } /** + * Format a bitrate for output, using an appropriate + * unit (bps, kbps, Mbps, Gbps, Tbps, Pbps, Ebps, Zbps or Ybps) according to the magnitude in question + * + * This use base 1000. For base 1024 use formatSize(), for another base + * see formatComputingNumbers() + * * @param $bps int * @return string */ function formatBitrate( $bps ) { - $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' ); - if ( $bps <= 0 ) { - return $this->formatNum( $bps ) . $units[0]; - } - $unitIndex = floor( log10( $bps ) / 3 ); - $mantissa = $bps / pow( 1000, $unitIndex ); - if ( $mantissa < 10 ) { - $mantissa = round( $mantissa, 1 ); - } else { - $mantissa = round( $mantissa ); + return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" ); + } + + /** + * @param $size int Size of the unit + * @param $boundary int Size boundary (1000, or 1024 in most cases) + * @param $messageKey string Message key to be uesd + * @return string + */ + function formatComputingNumbers( $size, $boundary, $messageKey ) { + if ( $size <= 0 ) { + return str_replace( '$1', $this->formatNum( $size ), + $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) ) + ); + } + $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ); + $index = 0; + + $maxIndex = count( $sizes ) - 1; + while ( $size >= $boundary && $index < $maxIndex ) { + $index++; + $size /= $boundary; + } + + // For small sizes no decimal places necessary + $round = 0; + if ( $index > 1 ) { + // For MB and bigger two decimal places are smarter + $round = 2; } - return $this->formatNum( $mantissa ) . $units[$unitIndex]; + $msg = str_replace( '$1', $sizes[$index], $messageKey ); + + $size = round( $size, $round ); + $text = $this->getMessageFromDB( $msg ); + return str_replace( '$1', $this->formatNum( $size ), $text ); } /** * Format a size in bytes for output, using an appropriate - * unit (B, KB, MB or GB) according to the magnitude in question + * unit (B, KB, MB, GB, TB, PB, EB, ZB or YB) according to the magnitude in question * - * @param $size Size to format + * This method use base 1024. For base 1000 use formatBitrate(), for + * another base see formatComputingNumbers() + * + * @param $size int Size to format * @return string Plain text (not HTML) */ function formatSize( $size ) { - // For small sizes no decimal places necessary - $round = 0; - if ( $size > 1024 ) { - $size = $size / 1024; - if ( $size > 1024 ) { - $size = $size / 1024; - // For MB and bigger two decimal places are smarter - $round = 2; - if ( $size > 1024 ) { - $size = $size / 1024; - $msg = 'size-gigabytes'; - } else { - $msg = 'size-megabytes'; - } - } else { - $msg = 'size-kilobytes'; - } + return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" ); + } + + /** + * Make a list item, used by various special pages + * + * @param $page String Page link + * @param $details String Text between brackets + * @param $oppositedm Boolean Add the direction mark opposite to your + * language, to display text properly + * @return String + */ + function specialList( $page, $details, $oppositedm = true ) { + $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . + $this->getDirMark(); + $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) . + wfMsgExt( 'parentheses', array( 'escape', 'replaceafter', 'language' => $this ), $details ) : ''; + return $page . $details; + } + + /** + * Generate (prev x| next x) (20|50|100...) type links for paging + * + * @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 $atend Bool optional param for specified if this is the last page + * @return String + */ + public function viewPrevNext( Title $title, $offset, $limit, array $query = array(), $atend = false ) { + // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip? + + # Make 'previous' link + $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); + if ( $offset > 0 ) { + $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit, + $query, $prev, 'prevn-title', 'mw-prevlink' ); } else { - $msg = 'size-bytes'; + $plink = htmlspecialchars( $prev ); } - $size = round( $size, $round ); - $text = $this->getMessageFromDB( $msg ); - return str_replace( '$1', $this->formatNum( $size ), $text ); + + # Make 'next' link + $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); + if ( $atend ) { + $nlink = htmlspecialchars( $next ); + } else { + $nlink = $this->numLink( $title, $offset + $limit, $limit, + $query, $next, 'prevn-title', 'mw-nextlink' ); + } + + # Make links to set number of items per page + $numLinks = array(); + foreach ( array( 20, 50, 100, 250, 500 ) as $num ) { + $numLinks[] = $this->numLink( $title, $offset, $num, + $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' ); + } + + return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title + )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped(); + } + + /** + * Helper function for viewPrevNext() that generates links + * + * @param $title Title object to link + * @param $offset Integer offset parameter + * @param $limit Integer limit parameter + * @param $query Array extra query parameters + * @param $link String text to use for the link; will be escaped + * @param $tooltipMsg String name of the message to use as tooltip + * @param $class String value of the "class" attribute of the link + * @return String HTML fragment + */ + private function numLink( Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class ) { + $query = array( 'limit' => $limit, 'offset' => $offset ) + $query; + $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); + return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ), + 'title' => $tooltip, 'class' => $class ), $link ); } /** @@ -3565,7 +3973,7 @@ class Language { * * @return string */ - function getConvRuleTitle() { + public function getConvRuleTitle() { return $this->mConverter->getConvRuleTitle(); } } |