X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=languages%2FLanguage.php;h=7ef2effb985cce462196fa0c280e3871dee61a95;hb=22806b0a4509e97b56fb52b387e17e3c80fb7eb2;hp=3e28759234228e4ae105aac1297aef8ae39d54b9;hpb=59e7337ea612d03d08b67e6ada707e3d7ced738d;p=lhc%2Fweb%2Fwiklou.git diff --git a/languages/Language.php b/languages/Language.php index 3e28759234..067231557b 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -45,7 +45,9 @@ class Language { public $dateFormatStrings = []; public $mExtendedSpecialPageAliases; - protected $namespaceNames, $mNamespaceIds, $namespaceAliases; + /** @var array|null */ + protected $namespaceNames; + protected $mNamespaceIds, $namespaceAliases; /** * ReplacementArray object caches @@ -137,6 +139,12 @@ class Language { */ static private $fallbackLanguageCache = []; + /** + * Cache for grammar rules data + * @var MapCacheLRU|null + */ + static private $grammarTransformations; + /** * Cache for language names * @var HashBagOStuff|null @@ -291,7 +299,7 @@ class Language { # 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 + # https://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)" @@ -332,7 +340,7 @@ class Language { // 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 + // see bugs T39564, T39587, T38938 $cache[$code] = // Protect against path traversal strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) @@ -407,10 +415,10 @@ class Language { function __construct() { $this->mConverter = new FakeConverter( $this ); // Set the code to the name of the descendant - if ( get_class( $this ) == 'Language' ) { + if ( static::class === 'Language' ) { $this->mCode = 'en'; } else { - $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) ); + $this->mCode = str_replace( '_', '-', strtolower( substr( static::class, 8 ) ) ); } self::getLocalisationCache(); } @@ -457,10 +465,11 @@ class Language { 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 = $wgExtraNamespaces + + self::$dataCache->getItem( $this->mCode, 'namespaceNames' ); + $this->namespaceNames += $validNamespaces; $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace; if ( $wgMetaNamespaceTalk ) { @@ -1623,7 +1632,7 @@ class Language { * * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license * - * @see http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0 + * @see https://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0 * * @param string $ts * @@ -1852,9 +1861,9 @@ class Language { * Algorithm to convert Gregorian dates to Thai solar dates, * Minguo dates or Minguo dates. * - * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar - * http://en.wikipedia.org/wiki/Minguo_calendar - * http://en.wikipedia.org/wiki/Japanese_era_name + * Link: https://en.wikipedia.org/wiki/Thai_solar_calendar + * https://en.wikipedia.org/wiki/Minguo_calendar + * https://en.wikipedia.org/wiki/Japanese_era_name * * @param string $ts 14-character timestamp * @param string $cName Calender name @@ -2091,17 +2100,15 @@ class Language { $data = explode( '|', $tz, 3 ); if ( $data[0] == 'ZoneInfo' ) { - MediaWiki\suppressWarnings(); - $userTZ = timezone_open( $data[2] ); - MediaWiki\restoreWarnings(); - if ( $userTZ !== false ) { - $date = date_create( $ts, timezone_open( 'UTC' ) ); - date_timezone_set( $date, $userTZ ); - $date = date_format( $date, 'YmdHis' ); - return $date; + try { + $userTZ = new DateTimeZone( $data[2] ); + $date = new DateTime( $ts, new DateTimeZone( 'UTC' ) ); + $date->setTimezone( $userTZ ); + return $date->format( 'YmdHis' ); + } catch ( Exception $e ) { + // Unrecognized timezone, default to 'Offset' with the stored offset. + $data[0] = 'Offset'; } - # Unrecognized timezone, default to 'Offset' with the stored offset. - $data[0] = 'Offset'; } if ( $data[0] == 'System' || $tz == '' ) { @@ -2151,12 +2158,10 @@ class Language { * the date preference they're supposed to use, it should be used in * all children. * - * - * function timeanddate([...], $format = true) { - * $datePreference = $this->dateFormat($format); - * [...] - * } - * + * function timeanddate([...], $format = true) { + * $datePreference = $this->dateFormat($format); + * [...] + * } * * @param int|string|bool $usePrefs If true, the user's preference is used * if false, the site/language default is used @@ -2571,7 +2576,7 @@ class Language { /** * @param string $key - * @return array|null + * @return string|null */ public function getMessage( $key ) { return self::$dataCache->getSubitem( $this->mCode, 'messages', $key ); @@ -2593,7 +2598,7 @@ class Language { public function iconv( $in, $out, $string ) { # Even with //IGNORE iconv can whine about illegal characters in # *input* string. We just ignore those too. - # REF: http://bugs.php.net/bug.php?id=37166 + # REF: https://bugs.php.net/bug.php?id=37166 # REF: https://phabricator.wikimedia.org/T18885 MediaWiki\suppressWarnings(); $text = iconv( $in, $out . '//IGNORE', $string ); @@ -3279,14 +3284,14 @@ class Language { public function parseFormattedNumber( $number ) { $s = $this->digitTransformTable(); if ( $s ) { - // eliminate empty array values such as ''. (bug 64347) + // eliminate empty array values such as ''. (T66347) $s = array_filter( $s ); $number = strtr( $number, array_flip( $s ) ); } $s = $this->separatorTransformTable(); if ( $s ) { - // eliminate empty array values such as ''. (bug 64347) + // eliminate empty array values such as ''. (T66347) $s = array_filter( $s ); $number = strtr( $number, array_flip( $s ) ); } @@ -3490,7 +3495,7 @@ class Language { $string = $ellipsis . $string; } } - # Do not truncate if the ellipsis makes the string longer/equal (bug 22181). + # Do not truncate if the ellipsis makes the string longer/equal (T24181). # This check is *not* redundant if $adjustLength, due to the single case where # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string. if ( strlen( $string ) < strlen( $stringOriginal ) ) { @@ -3728,8 +3733,46 @@ class Language { return $wgGrammarForms[$this->getCode()][$case][$word]; } + $grammarTransformations = $this->getGrammarTransformations(); + + if ( isset( $grammarTransformations[$case] ) ) { + $forms = $grammarTransformations[$case]; + + // Some names of grammar rules are aliases for other rules. + // In such cases the value is a string rather than object, + // so load the actual rules. + if ( is_string( $forms ) ) { + $forms = $grammarTransformations[$forms]; + } + + foreach ( array_values( $forms ) as $rule ) { + $form = $rule[0]; + + if ( $form === '@metadata' ) { + continue; + } + + $replacement = $rule[1]; + + $regex = '/' . addcslashes( $form, '/' ) . '/u'; + $patternMatches = preg_match( $regex, $word ); + + if ( $patternMatches === false ) { + wfLogWarning( + 'An error occurred while processing grammar. ' . + "Word: '$word'. Regex: /$form/." + ); + } elseif ( $patternMatches === 1 ) { + $word = preg_replace( $regex, $replacement, $word ); + + break; + } + } + } + return $word; } + /** * Get the grammar forms for the content language * @return array Array of grammar forms @@ -3745,6 +3788,46 @@ class Language { return []; } + + /** + * Get the grammar transformations data for the language. + * Used like grammar forms, with {{GRAMMAR}} and cases, + * but uses pairs of regexes and replacements instead of code. + * + * @return array[] Array of grammar transformations. + * @throws MWException + * @since 1.28 + */ + public function getGrammarTransformations() { + $languageCode = $this->getCode(); + + if ( self::$grammarTransformations === null ) { + self::$grammarTransformations = new MapCacheLRU( 10 ); + } + + if ( self::$grammarTransformations->has( $languageCode ) ) { + return self::$grammarTransformations->get( $languageCode ); + } + + $data = []; + + $grammarDataFile = __DIR__ . "/data/grammarTransformations/$languageCode.json"; + if ( is_readable( $grammarDataFile ) ) { + $data = FormatJson::decode( + file_get_contents( $grammarDataFile ), + true + ); + + if ( $data === null ) { + throw new MWException( "Invalid grammar data for \"$languageCode\"." ); + } + + self::$grammarTransformations->set( $languageCode, $data ); + } + + return $data; + } + /** * Provides an alternative text depending on specified gender. * Usage {{gender:username|masculine|feminine|unknown}}. @@ -3890,10 +3973,11 @@ class Language { * * @param string $str The validated block duration in English * @param User $user User object to use timezone from or null for $wgUser + * @param int $now Current timestamp, for formatting relative block durations * @return string Somehow translated block duration * @see LanguageFi.php for example implementation */ - function translateBlockExpiry( $str, User $user = null ) { + function translateBlockExpiry( $str, User $user = null, $now = 0 ) { $duration = SpecialBlock::getSuggestedDurations( $this ); foreach ( $duration as $show => $value ) { if ( strcmp( $str, $value ) == 0 ) { @@ -3910,12 +3994,13 @@ class Language { } // If all else fails, return a standard duration or timestamp description. - $time = strtotime( $str, 0 ); + $time = strtotime( $str, $now ); if ( $time === false ) { // Unknown format. Return it as-is in case. return $str; - } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp. - // $time is relative to 0 so it's a duration length. - return $this->formatDuration( $time ); + } elseif ( $time !== strtotime( $str, $now + 1 ) ) { // It's a relative timestamp. + // The result differs based on current time, so the difference + // is a fixed duration length. + return $this->formatDuration( $time - $now ); } else { // It's an absolute timestamp. if ( $time === 0 ) { // wfTimestamp() handles 0 as current time instead of epoch. @@ -4421,7 +4506,7 @@ class Language { # such as action=raw much more expensive than they need to be. # This will hopefully cover most cases. $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i', - [ &$this, 'replaceGrammarInNamespace' ], $talk ); + [ $this, 'replaceGrammarInNamespace' ], $talk ); return str_replace( ' ', '_', $talk ); } @@ -4461,14 +4546,15 @@ class Language { } /** - * @todo Document + * Formats a time given in seconds into a string representation of that time. + * * @param int|float $seconds - * @param array $format 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. + * @param array $format An optional argument that formats the returned string in different ways: + * If $format['avoid'] === 'avoidseconds': don't show seconds if $seconds >= 1 hour, + * If $format['avoid'] === 'avoidminutes': don't show 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' + * @note For backwards compatibility, $format may also be one of the strings 'avoidseconds' * or 'avoidminutes'. * @return string */