[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/languages/ -> Language.php (source)

   1  <?php
   2  /**
   3   * Internationalisation code.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup Language
  22   */
  23  
  24  /**
  25   * @defgroup Language Language
  26   */
  27  
  28  if ( !defined( 'MEDIAWIKI' ) ) {
  29      echo "This file is part of MediaWiki, it is not a valid entry point.\n";
  30      exit( 1 );
  31  }
  32  
  33  if ( function_exists( 'mb_strtoupper' ) ) {
  34      mb_internal_encoding( 'UTF-8' );
  35  }
  36  
  37  /**
  38   * Internationalisation code
  39   * @ingroup Language
  40   */
  41  class Language {
  42      /**
  43       * @var LanguageConverter
  44       */
  45      public $mConverter;
  46  
  47      public $mVariants, $mCode, $mLoaded = false;
  48      public $mMagicExtensions = array(), $mMagicHookDone = false;
  49      private $mHtmlCode = null, $mParentLanguage = false;
  50  
  51      public $dateFormatStrings = array();
  52      public $mExtendedSpecialPageAliases;
  53  
  54      protected $namespaceNames, $mNamespaceIds, $namespaceAliases;
  55  
  56      /**
  57       * ReplacementArray object caches
  58       */
  59      public $transformData = array();
  60  
  61      /**
  62       * @var LocalisationCache
  63       */
  64      static public $dataCache;
  65  
  66      static public $mLangObjCache = array();
  67  
  68      static public $mWeekdayMsgs = array(
  69          'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
  70          'friday', 'saturday'
  71      );
  72  
  73      static public $mWeekdayAbbrevMsgs = array(
  74          'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
  75      );
  76  
  77      static public $mMonthMsgs = array(
  78          'january', 'february', 'march', 'april', 'may_long', 'june',
  79          'july', 'august', 'september', 'october', 'november',
  80          'december'
  81      );
  82      static public $mMonthGenMsgs = array(
  83          'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
  84          'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
  85          'december-gen'
  86      );
  87      static public $mMonthAbbrevMsgs = array(
  88          'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
  89          'sep', 'oct', 'nov', 'dec'
  90      );
  91  
  92      static public $mIranianCalendarMonthMsgs = array(
  93          'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
  94          'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
  95          'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
  96          'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
  97      );
  98  
  99      static public $mHebrewCalendarMonthMsgs = array(
 100          'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
 101          'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
 102          'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
 103          'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
 104          'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
 105      );
 106  
 107      static public $mHebrewCalendarMonthGenMsgs = array(
 108          'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
 109          'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
 110          'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
 111          'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
 112          'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
 113      );
 114  
 115      static public $mHijriCalendarMonthMsgs = array(
 116          'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
 117          'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
 118          'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
 119          'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
 120      );
 121  
 122      /**
 123       * @since 1.20
 124       * @var array
 125       */
 126      static public $durationIntervals = array(
 127          'millennia' => 31556952000,
 128          'centuries' => 3155695200,
 129          'decades' => 315569520,
 130          'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
 131          'weeks' => 604800,
 132          'days' => 86400,
 133          'hours' => 3600,
 134          'minutes' => 60,
 135          'seconds' => 1,
 136      );
 137  
 138      /**
 139       * Cache for language fallbacks.
 140       * @see Language::getFallbacksIncludingSiteLanguage
 141       * @since 1.21
 142       * @var array
 143       */
 144      static private $fallbackLanguageCache = array();
 145  
 146      /**
 147       * Get a cached or new language object for a given language code
 148       * @param string $code
 149       * @return Language
 150       */
 151  	static function factory( $code ) {
 152          global $wgDummyLanguageCodes, $wgLangObjCacheSize;
 153  
 154          if ( isset( $wgDummyLanguageCodes[$code] ) ) {
 155              $code = $wgDummyLanguageCodes[$code];
 156          }
 157  
 158          // get the language object to process
 159          $langObj = isset( self::$mLangObjCache[$code] )
 160              ? self::$mLangObjCache[$code]
 161              : self::newFromCode( $code );
 162  
 163          // merge the language object in to get it up front in the cache
 164          self::$mLangObjCache = array_merge( array( $code => $langObj ), self::$mLangObjCache );
 165          // get rid of the oldest ones in case we have an overflow
 166          self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
 167  
 168          return $langObj;
 169      }
 170  
 171      /**
 172       * Create a language object for a given language code
 173       * @param string $code
 174       * @throws MWException
 175       * @return Language
 176       */
 177  	protected static function newFromCode( $code ) {
 178          // Protect against path traversal below
 179          if ( !Language::isValidCode( $code )
 180              || strcspn( $code, ":/\\\000" ) !== strlen( $code )
 181          ) {
 182              throw new MWException( "Invalid language code \"$code\"" );
 183          }
 184  
 185          if ( !Language::isValidBuiltInCode( $code ) ) {
 186              // It's not possible to customise this code with class files, so
 187              // just return a Language object. This is to support uselang= hacks.
 188              $lang = new Language;
 189              $lang->setCode( $code );
 190              return $lang;
 191          }
 192  
 193          // Check if there is a language class for the code
 194          $class = self::classFromCode( $code );
 195          self::preloadLanguageClass( $class );
 196          if ( class_exists( $class ) ) {
 197              $lang = new $class;
 198              return $lang;
 199          }
 200  
 201          // Keep trying the fallback list until we find an existing class
 202          $fallbacks = Language::getFallbacksFor( $code );
 203          foreach ( $fallbacks as $fallbackCode ) {
 204              if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
 205                  throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
 206              }
 207  
 208              $class = self::classFromCode( $fallbackCode );
 209              self::preloadLanguageClass( $class );
 210              if ( class_exists( $class ) ) {
 211                  $lang = Language::newFromCode( $fallbackCode );
 212                  $lang->setCode( $code );
 213                  return $lang;
 214              }
 215          }
 216  
 217          throw new MWException( "Invalid fallback sequence for language '$code'" );
 218      }
 219  
 220      /**
 221       * Checks whether any localisation is available for that language tag
 222       * in MediaWiki (MessagesXx.php exists).
 223       *
 224       * @param string $code Language tag (in lower case)
 225       * @return bool Whether language is supported
 226       * @since 1.21
 227       */
 228  	public static function isSupportedLanguage( $code ) {
 229          return self::isValidBuiltInCode( $code )
 230              && ( is_readable( self::getMessagesFileName( $code ) )
 231                  || is_readable( self::getJsonMessagesFileName( $code ) )
 232          );
 233      }
 234  
 235      /**
 236       * Returns true if a language code string is a well-formed language tag
 237       * according to RFC 5646.
 238       * This function only checks well-formedness; it doesn't check that
 239       * language, script or variant codes actually exist in the repositories.
 240       *
 241       * Based on regexes by Mark Davis of the Unicode Consortium:
 242       * http://unicode.org/repos/cldr/trunk/tools/java/org/unicode/cldr/util/data/langtagRegex.txt
 243       *
 244       * @param string $code
 245       * @param bool $lenient Whether to allow '_' as separator. The default is only '-'.
 246       *
 247       * @return bool
 248       * @since 1.21
 249       */
 250  	public static function isWellFormedLanguageTag( $code, $lenient = false ) {
 251          $alpha = '[a-z]';
 252          $digit = '[0-9]';
 253          $alphanum = '[a-z0-9]';
 254          $x = 'x'; # private use singleton
 255          $singleton = '[a-wy-z]'; # other singleton
 256          $s = $lenient ? '[-_]' : '-';
 257  
 258          $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
 259          $script = "$alpha{4}"; # ISO 15924
 260          $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
 261          $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
 262          $extension = "$singleton(?:$s$alphanum{2,8})+";
 263          $privateUse = "$x(?:$s$alphanum{1,8})+";
 264  
 265          # Define certain grandfathered codes, since otherwise the regex is pretty useless.
 266          # Since these are limited, this is safe even later changes to the registry --
 267          # the only oddity is that it might change the type of the tag, and thus
 268          # the results from the capturing groups.
 269          # http://www.iana.org/assignments/language-subtag-registry
 270  
 271          $grandfathered = "en{$s}GB{$s}oed"
 272              . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
 273              . "|no{$s}(?:bok|nyn)"
 274              . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
 275              . "|zh{$s}min{$s}nan";
 276  
 277          $variantList = "$variant(?:$s$variant)*";
 278          $extensionList = "$extension(?:$s$extension)*";
 279  
 280          $langtag = "(?:($language)"
 281              . "(?:$s$script)?"
 282              . "(?:$s$region)?"
 283              . "(?:$s$variantList)?"
 284              . "(?:$s$extensionList)?"
 285              . "(?:$s$privateUse)?)";
 286  
 287          # The final breakdown, with capturing groups for each of these components
 288          # The variants, extensions, grandfathered, and private-use may have interior '-'
 289  
 290          $root = "^(?:$langtag|$privateUse|$grandfathered)$";
 291  
 292          return (bool)preg_match( "/$root/", strtolower( $code ) );
 293      }
 294  
 295      /**
 296       * Returns true if a language code string is of a valid form, whether or
 297       * not it exists. This includes codes which are used solely for
 298       * customisation via the MediaWiki namespace.
 299       *
 300       * @param string $code
 301       *
 302       * @return bool
 303       */
 304  	public static function isValidCode( $code ) {
 305          static $cache = array();
 306          if ( isset( $cache[$code] ) ) {
 307              return $cache[$code];
 308          }
 309          // People think language codes are html safe, so enforce it.
 310          // Ideally we should only allow a-zA-Z0-9-
 311          // but, .+ and other chars are often used for {{int:}} hacks
 312          // see bugs 37564, 37587, 36938
 313          $cache[$code] =
 314              strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
 315              && !preg_match( Title::getTitleInvalidRegex(), $code );
 316  
 317          return $cache[$code];
 318      }
 319  
 320      /**
 321       * Returns true if a language code is of a valid form for the purposes of
 322       * internal customisation of MediaWiki, via Messages*.php or *.json.
 323       *
 324       * @param string $code
 325       *
 326       * @throws MWException
 327       * @since 1.18
 328       * @return bool
 329       */
 330  	public static function isValidBuiltInCode( $code ) {
 331  
 332          if ( !is_string( $code ) ) {
 333              if ( is_object( $code ) ) {
 334                  $addmsg = " of class " . get_class( $code );
 335              } else {
 336                  $addmsg = '';
 337              }
 338              $type = gettype( $code );
 339              throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
 340          }
 341  
 342          return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
 343      }
 344  
 345      /**
 346       * Returns true if a language code is an IETF tag known to MediaWiki.
 347       *
 348       * @param string $tag
 349       *
 350       * @since 1.21
 351       * @return bool
 352       */
 353  	public static function isKnownLanguageTag( $tag ) {
 354          static $coreLanguageNames;
 355  
 356          // Quick escape for invalid input to avoid exceptions down the line
 357          // when code tries to process tags which are not valid at all.
 358          if ( !self::isValidBuiltInCode( $tag ) ) {
 359              return false;
 360          }
 361  
 362          if ( $coreLanguageNames === null ) {
 363              global $IP;
 364              include "$IP/languages/Names.php";
 365          }
 366  
 367          if ( isset( $coreLanguageNames[$tag] )
 368              || self::fetchLanguageName( $tag, $tag ) !== ''
 369          ) {
 370              return true;
 371          }
 372  
 373          return false;
 374      }
 375  
 376      /**
 377       * @param string $code
 378       * @return string Name of the language class
 379       */
 380  	public static function classFromCode( $code ) {
 381          if ( $code == 'en' ) {
 382              return 'Language';
 383          } else {
 384              return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
 385          }
 386      }
 387  
 388      /**
 389       * Includes language class files
 390       *
 391       * @param string $class Name of the language class
 392       */
 393  	public static function preloadLanguageClass( $class ) {
 394          global $IP;
 395  
 396          if ( $class === 'Language' ) {
 397              return;
 398          }
 399  
 400          if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
 401              include_once "$IP/languages/classes/$class.php";
 402          }
 403      }
 404  
 405      /**
 406       * Get the LocalisationCache instance
 407       *
 408       * @return LocalisationCache
 409       */
 410  	public static function getLocalisationCache() {
 411          if ( is_null( self::$dataCache ) ) {
 412              global $wgLocalisationCacheConf;
 413              $class = $wgLocalisationCacheConf['class'];
 414              self::$dataCache = new $class( $wgLocalisationCacheConf );
 415          }
 416          return self::$dataCache;
 417      }
 418  
 419  	function __construct() {
 420          $this->mConverter = new FakeConverter( $this );
 421          // Set the code to the name of the descendant
 422          if ( get_class( $this ) == 'Language' ) {
 423              $this->mCode = 'en';
 424          } else {
 425              $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
 426          }
 427          self::getLocalisationCache();
 428      }
 429  
 430      /**
 431       * Reduce memory usage
 432       */
 433  	function __destruct() {
 434          foreach ( $this as $name => $value ) {
 435              unset( $this->$name );
 436          }
 437      }
 438  
 439      /**
 440       * Hook which will be called if this is the content language.
 441       * Descendants can use this to register hook functions or modify globals
 442       */
 443  	function initContLang() {
 444      }
 445  
 446      /**
 447       * @return array
 448       * @since 1.19
 449       */
 450  	function getFallbackLanguages() {
 451          return self::getFallbacksFor( $this->mCode );
 452      }
 453  
 454      /**
 455       * Exports $wgBookstoreListEn
 456       * @return array
 457       */
 458  	function getBookstoreList() {
 459          return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
 460      }
 461  
 462      /**
 463       * Returns an array of localised namespaces indexed by their numbers. If the namespace is not
 464       * available in localised form, it will be included in English.
 465       *
 466       * @return array
 467       */
 468  	public function getNamespaces() {
 469          if ( is_null( $this->namespaceNames ) ) {
 470              global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces;
 471  
 472              $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
 473              $validNamespaces = MWNamespace::getCanonicalNamespaces();
 474  
 475              $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
 476  
 477              $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
 478              if ( $wgMetaNamespaceTalk ) {
 479                  $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
 480              } else {
 481                  $talk = $this->namespaceNames[NS_PROJECT_TALK];
 482                  $this->namespaceNames[NS_PROJECT_TALK] =
 483                      $this->fixVariableInNamespace( $talk );
 484              }
 485  
 486              # Sometimes a language will be localised but not actually exist on this wiki.
 487              foreach ( $this->namespaceNames as $key => $text ) {
 488                  if ( !isset( $validNamespaces[$key] ) ) {
 489                      unset( $this->namespaceNames[$key] );
 490                  }
 491              }
 492  
 493              # The above mixing may leave namespaces out of canonical order.
 494              # Re-order by namespace ID number...
 495              ksort( $this->namespaceNames );
 496  
 497              wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) );
 498          }
 499  
 500          return $this->namespaceNames;
 501      }
 502  
 503      /**
 504       * Arbitrarily set all of the namespace names at once. Mainly used for testing
 505       * @param array $namespaces Array of namespaces (id => name)
 506       */
 507  	public function setNamespaces( array $namespaces ) {
 508          $this->namespaceNames = $namespaces;
 509          $this->mNamespaceIds = null;
 510      }
 511  
 512      /**
 513       * Resets all of the namespace caches. Mainly used for testing
 514       */
 515  	public function resetNamespaces() {
 516          $this->namespaceNames = null;
 517          $this->mNamespaceIds = null;
 518          $this->namespaceAliases = null;
 519      }
 520  
 521      /**
 522       * A convenience function that returns the same thing as
 523       * getNamespaces() except with the array values changed to ' '
 524       * where it found '_', useful for producing output to be displayed
 525       * e.g. in <select> forms.
 526       *
 527       * @return array
 528       */
 529  	function getFormattedNamespaces() {
 530          $ns = $this->getNamespaces();
 531          foreach ( $ns as $k => $v ) {
 532              $ns[$k] = strtr( $v, '_', ' ' );
 533          }
 534          return $ns;
 535      }
 536  
 537      /**
 538       * Get a namespace value by key
 539       * <code>
 540       * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
 541       * echo $mw_ns; // prints 'MediaWiki'
 542       * </code>
 543       *
 544       * @param int $index The array key of the namespace to return
 545       * @return string|bool String if the namespace value exists, otherwise false
 546       */
 547  	function getNsText( $index ) {
 548          $ns = $this->getNamespaces();
 549  
 550          return isset( $ns[$index] ) ? $ns[$index] : false;
 551      }
 552  
 553      /**
 554       * A convenience function that returns the same thing as
 555       * getNsText() except with '_' changed to ' ', useful for
 556       * producing output.
 557       *
 558       * <code>
 559       * $mw_ns = $wgContLang->getFormattedNsText( NS_MEDIAWIKI_TALK );
 560       * echo $mw_ns; // prints 'MediaWiki talk'
 561       * </code>
 562       *
 563       * @param int $index The array key of the namespace to return
 564       * @return string Namespace name without underscores (empty string if namespace does not exist)
 565       */
 566  	function getFormattedNsText( $index ) {
 567          $ns = $this->getNsText( $index );
 568  
 569          return strtr( $ns, '_', ' ' );
 570      }
 571  
 572      /**
 573       * Returns gender-dependent namespace alias if available.
 574       * See https://www.mediawiki.org/wiki/Manual:$wgExtraGenderNamespaces
 575       * @param int $index Namespace index
 576       * @param string $gender Gender key (male, female... )
 577       * @return string
 578       * @since 1.18
 579       */
 580  	function getGenderNsText( $index, $gender ) {
 581          global $wgExtraGenderNamespaces;
 582  
 583          $ns = $wgExtraGenderNamespaces +
 584              self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
 585  
 586          return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
 587      }
 588  
 589      /**
 590       * Whether this language uses gender-dependent namespace aliases.
 591       * See https://www.mediawiki.org/wiki/Manual:$wgExtraGenderNamespaces
 592       * @return bool
 593       * @since 1.18
 594       */
 595  	function needsGenderDistinction() {
 596          global $wgExtraGenderNamespaces, $wgExtraNamespaces;
 597          if ( count( $wgExtraGenderNamespaces ) > 0 ) {
 598              // $wgExtraGenderNamespaces overrides everything
 599              return true;
 600          } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
 601              /// @todo There may be other gender namespace than NS_USER & NS_USER_TALK in the future
 602              // $wgExtraNamespaces overrides any gender aliases specified in i18n files
 603              return false;
 604          } else {
 605              // Check what is in i18n files
 606              $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
 607              return count( $aliases ) > 0;
 608          }
 609      }
 610  
 611      /**
 612       * Get a namespace key by value, case insensitive.
 613       * Only matches namespace names for the current language, not the
 614       * canonical ones defined in Namespace.php.
 615       *
 616       * @param string $text
 617       * @return int|bool An integer if $text is a valid value otherwise false
 618       */
 619  	function getLocalNsIndex( $text ) {
 620          $lctext = $this->lc( $text );
 621          $ids = $this->getNamespaceIds();
 622          return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
 623      }
 624  
 625      /**
 626       * @return array
 627       */
 628  	function getNamespaceAliases() {
 629          if ( is_null( $this->namespaceAliases ) ) {
 630              $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
 631              if ( !$aliases ) {
 632                  $aliases = array();
 633              } else {
 634                  foreach ( $aliases as $name => $index ) {
 635                      if ( $index === NS_PROJECT_TALK ) {
 636                          unset( $aliases[$name] );
 637                          $name = $this->fixVariableInNamespace( $name );
 638                          $aliases[$name] = $index;
 639                      }
 640                  }
 641              }
 642  
 643              global $wgExtraGenderNamespaces;
 644              $genders = $wgExtraGenderNamespaces +
 645                  (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
 646              foreach ( $genders as $index => $forms ) {
 647                  foreach ( $forms as $alias ) {
 648                      $aliases[$alias] = $index;
 649                  }
 650              }
 651  
 652              # Also add converted namespace names as aliases, to avoid confusion.
 653              $convertedNames = array();
 654              foreach ( $this->getVariants() as $variant ) {
 655                  if ( $variant === $this->mCode ) {
 656                      continue;
 657                  }
 658                  foreach ( $this->getNamespaces() as $ns => $_ ) {
 659                      $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
 660                  }
 661              }
 662  
 663              $this->namespaceAliases = $aliases + $convertedNames;
 664          }
 665  
 666          return $this->namespaceAliases;
 667      }
 668  
 669      /**
 670       * @return array
 671       */
 672  	function getNamespaceIds() {
 673          if ( is_null( $this->mNamespaceIds ) ) {
 674              global $wgNamespaceAliases;
 675              # Put namespace names and aliases into a hashtable.
 676              # If this is too slow, then we should arrange it so that it is done
 677              # before caching. The catch is that at pre-cache time, the above
 678              # class-specific fixup hasn't been done.
 679              $this->mNamespaceIds = array();
 680              foreach ( $this->getNamespaces() as $index => $name ) {
 681                  $this->mNamespaceIds[$this->lc( $name )] = $index;
 682              }
 683              foreach ( $this->getNamespaceAliases() as $name => $index ) {
 684                  $this->mNamespaceIds[$this->lc( $name )] = $index;
 685              }
 686              if ( $wgNamespaceAliases ) {
 687                  foreach ( $wgNamespaceAliases as $name => $index ) {
 688                      $this->mNamespaceIds[$this->lc( $name )] = $index;
 689                  }
 690              }
 691          }
 692          return $this->mNamespaceIds;
 693      }
 694  
 695      /**
 696       * Get a namespace key by value, case insensitive.  Canonical namespace
 697       * names override custom ones defined for the current language.
 698       *
 699       * @param string $text
 700       * @return int|bool An integer if $text is a valid value otherwise false
 701       */
 702  	function getNsIndex( $text ) {
 703          $lctext = $this->lc( $text );
 704          $ns = MWNamespace::getCanonicalIndex( $lctext );
 705          if ( $ns !== null ) {
 706              return $ns;
 707          }
 708          $ids = $this->getNamespaceIds();
 709          return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
 710      }
 711  
 712      /**
 713       * short names for language variants used for language conversion links.
 714       *
 715       * @param string $code
 716       * @param bool $usemsg Use the "variantname-xyz" message if it exists
 717       * @return string
 718       */
 719  	function getVariantname( $code, $usemsg = true ) {
 720          $msg = "variantname-$code";
 721          if ( $usemsg && wfMessage( $msg )->exists() ) {
 722              return $this->getMessageFromDB( $msg );
 723          }
 724          $name = self::fetchLanguageName( $code );
 725          if ( $name ) {
 726              return $name; # if it's defined as a language name, show that
 727          } else {
 728              # otherwise, output the language code
 729              return $code;
 730          }
 731      }
 732  
 733      /**
 734       * @deprecated since 1.24, doesn't handle conflicting aliases. Use
 735       *   SpecialPageFactory::getLocalNameFor instead.
 736       * @param string $name
 737       * @return string
 738       */
 739  	function specialPage( $name ) {
 740          $aliases = $this->getSpecialPageAliases();
 741          if ( isset( $aliases[$name][0] ) ) {
 742              $name = $aliases[$name][0];
 743          }
 744          return $this->getNsText( NS_SPECIAL ) . ':' . $name;
 745      }
 746  
 747      /**
 748       * @return array
 749       */
 750  	function getDatePreferences() {
 751          return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
 752      }
 753  
 754      /**
 755       * @return array
 756       */
 757  	function getDateFormats() {
 758          return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
 759      }
 760  
 761      /**
 762       * @return array|string
 763       */
 764  	function getDefaultDateFormat() {
 765          $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
 766          if ( $df === 'dmy or mdy' ) {
 767              global $wgAmericanDates;
 768              return $wgAmericanDates ? 'mdy' : 'dmy';
 769          } else {
 770              return $df;
 771          }
 772      }
 773  
 774      /**
 775       * @return array
 776       */
 777  	function getDatePreferenceMigrationMap() {
 778          return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
 779      }
 780  
 781      /**
 782       * @param string $image
 783       * @return array|null
 784       */
 785  	function getImageFile( $image ) {
 786          return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
 787      }
 788  
 789      /**
 790       * @return array
 791       * @since 1.24
 792       */
 793  	function getImageFiles() {
 794          return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
 795      }
 796  
 797      /**
 798       * @return array
 799       */
 800  	function getExtraUserToggles() {
 801          return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
 802      }
 803  
 804      /**
 805       * @param string $tog
 806       * @return string
 807       */
 808  	function getUserToggle( $tog ) {
 809          return $this->getMessageFromDB( "tog-$tog" );
 810      }
 811  
 812      /**
 813       * Get native language names, indexed by code.
 814       * Only those defined in MediaWiki, no other data like CLDR.
 815       * If $customisedOnly is true, only returns codes with a messages file
 816       *
 817       * @param bool $customisedOnly
 818       *
 819       * @return array
 820       * @deprecated since 1.20, use fetchLanguageNames()
 821       */
 822  	public static function getLanguageNames( $customisedOnly = false ) {
 823          return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' );
 824      }
 825  
 826      /**
 827       * Get translated language names. This is done on best effort and
 828       * by default this is exactly the same as Language::getLanguageNames.
 829       * The CLDR extension provides translated names.
 830       * @param string $code Language code.
 831       * @return array Language code => language name
 832       * @since 1.18.0
 833       * @deprecated since 1.20, use fetchLanguageNames()
 834       */
 835  	public static function getTranslatedLanguageNames( $code ) {
 836          return self::fetchLanguageNames( $code, 'all' );
 837      }
 838  
 839      /**
 840       * Get an array of language names, indexed by code.
 841       * @param null|string $inLanguage Code of language in which to return the names
 842       *        Use null for autonyms (native names)
 843       * @param string $include One of:
 844       *        'all' all available languages
 845       *        'mw' only if the language is defined in MediaWiki or wgExtraLanguageNames (default)
 846       *        'mwfile' only if the language is in 'mw' *and* has a message file
 847       * @return array Language code => language name
 848       * @since 1.20
 849       */
 850  	public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
 851          global $wgExtraLanguageNames;
 852          static $coreLanguageNames;
 853  
 854          if ( $coreLanguageNames === null ) {
 855              global $IP;
 856              include "$IP/languages/Names.php";
 857          }
 858  
 859          // If passed an invalid language code to use, fallback to en
 860          if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
 861              $inLanguage = 'en';
 862          }
 863  
 864          $names = array();
 865  
 866          if ( $inLanguage ) {
 867              # TODO: also include when $inLanguage is null, when this code is more efficient
 868              wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) );
 869          }
 870  
 871          $mwNames = $wgExtraLanguageNames + $coreLanguageNames;
 872          foreach ( $mwNames as $mwCode => $mwName ) {
 873              # - Prefer own MediaWiki native name when not using the hook
 874              # - For other names just add if not added through the hook
 875              if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
 876                  $names[$mwCode] = $mwName;
 877              }
 878          }
 879  
 880          if ( $include === 'all' ) {
 881              return $names;
 882          }
 883  
 884          $returnMw = array();
 885          $coreCodes = array_keys( $mwNames );
 886          foreach ( $coreCodes as $coreCode ) {
 887              $returnMw[$coreCode] = $names[$coreCode];
 888          }
 889  
 890          if ( $include === 'mwfile' ) {
 891              $namesMwFile = array();
 892              # We do this using a foreach over the codes instead of a directory
 893              # loop so that messages files in extensions will work correctly.
 894              foreach ( $returnMw as $code => $value ) {
 895                  if ( is_readable( self::getMessagesFileName( $code ) )
 896                      || is_readable( self::getJsonMessagesFileName( $code ) )
 897                  ) {
 898                      $namesMwFile[$code] = $names[$code];
 899                  }
 900              }
 901  
 902              return $namesMwFile;
 903          }
 904  
 905          # 'mw' option; default if it's not one of the other two options (all/mwfile)
 906          return $returnMw;
 907      }
 908  
 909      /**
 910       * @param string $code The code of the language for which to get the name
 911       * @param null|string $inLanguage Code of language in which to return the name (null for autonyms)
 912       * @param string $include 'all', 'mw' or 'mwfile'; see fetchLanguageNames()
 913       * @return string Language name or empty
 914       * @since 1.20
 915       */
 916  	public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
 917          $code = strtolower( $code );
 918          $array = self::fetchLanguageNames( $inLanguage, $include );
 919          return !array_key_exists( $code, $array ) ? '' : $array[$code];
 920      }
 921  
 922      /**
 923       * Get a message from the MediaWiki namespace.
 924       *
 925       * @param string $msg Message name
 926       * @return string
 927       */
 928  	function getMessageFromDB( $msg ) {
 929          return wfMessage( $msg )->inLanguage( $this )->text();
 930      }
 931  
 932      /**
 933       * Get the native language name of $code.
 934       * Only if defined in MediaWiki, no other data like CLDR.
 935       * @param string $code
 936       * @return string
 937       * @deprecated since 1.20, use fetchLanguageName()
 938       */
 939  	function getLanguageName( $code ) {
 940          return self::fetchLanguageName( $code );
 941      }
 942  
 943      /**
 944       * @param string $key
 945       * @return string
 946       */
 947  	function getMonthName( $key ) {
 948          return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
 949      }
 950  
 951      /**
 952       * @return array
 953       */
 954  	function getMonthNamesArray() {
 955          $monthNames = array( '' );
 956          for ( $i = 1; $i < 13; $i++ ) {
 957              $monthNames[] = $this->getMonthName( $i );
 958          }
 959          return $monthNames;
 960      }
 961  
 962      /**
 963       * @param string $key
 964       * @return string
 965       */
 966  	function getMonthNameGen( $key ) {
 967          return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
 968      }
 969  
 970      /**
 971       * @param string $key
 972       * @return string
 973       */
 974  	function getMonthAbbreviation( $key ) {
 975          return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
 976      }
 977  
 978      /**
 979       * @return array
 980       */
 981  	function getMonthAbbreviationsArray() {
 982          $monthNames = array( '' );
 983          for ( $i = 1; $i < 13; $i++ ) {
 984              $monthNames[] = $this->getMonthAbbreviation( $i );
 985          }
 986          return $monthNames;
 987      }
 988  
 989      /**
 990       * @param string $key
 991       * @return string
 992       */
 993  	function getWeekdayName( $key ) {
 994          return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
 995      }
 996  
 997      /**
 998       * @param string $key
 999       * @return string
1000       */
1001  	function getWeekdayAbbreviation( $key ) {
1002          return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
1003      }
1004  
1005      /**
1006       * @param string $key
1007       * @return string
1008       */
1009  	function getIranianCalendarMonthName( $key ) {
1010          return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
1011      }
1012  
1013      /**
1014       * @param string $key
1015       * @return string
1016       */
1017  	function getHebrewCalendarMonthName( $key ) {
1018          return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
1019      }
1020  
1021      /**
1022       * @param string $key
1023       * @return string
1024       */
1025  	function getHebrewCalendarMonthNameGen( $key ) {
1026          return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1027      }
1028  
1029      /**
1030       * @param string $key
1031       * @return string
1032       */
1033  	function getHijriCalendarMonthName( $key ) {
1034          return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1035      }
1036  
1037      /**
1038       * Pass through result from $dateTimeObj->format()
1039       * @param DateTime|bool|null &$dateTimeObj
1040       * @param string $ts
1041       * @param DateTimeZone|bool|null $zone
1042       * @param string $code
1043       * @return string
1044       */
1045  	private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
1046          if ( !$dateTimeObj ) {
1047              $dateTimeObj = DateTime::createFromFormat(
1048                  'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
1049              );
1050          }
1051          return $dateTimeObj->format( $code );
1052      }
1053  
1054      /**
1055       * This is a workalike of PHP's date() function, but with better
1056       * internationalisation, a reduced set of format characters, and a better
1057       * escaping format.
1058       *
1059       * Supported format characters are dDjlNwzWFmMntLoYyaAgGhHiscrUeIOPTZ. See
1060       * the PHP manual for definitions. There are a number of extensions, which
1061       * start with "x":
1062       *
1063       *    xn   Do not translate digits of the next numeric format character
1064       *    xN   Toggle raw digit (xn) flag, stays set until explicitly unset
1065       *    xr   Use roman numerals for the next numeric format character
1066       *    xh   Use hebrew numerals for the next numeric format character
1067       *    xx   Literal x
1068       *    xg   Genitive month name
1069       *
1070       *    xij  j (day number) in Iranian calendar
1071       *    xiF  F (month name) in Iranian calendar
1072       *    xin  n (month number) in Iranian calendar
1073       *    xiy  y (two digit year) in Iranian calendar
1074       *    xiY  Y (full year) in Iranian calendar
1075       *
1076       *    xjj  j (day number) in Hebrew calendar
1077       *    xjF  F (month name) in Hebrew calendar
1078       *    xjt  t (days in month) in Hebrew calendar
1079       *    xjx  xg (genitive month name) in Hebrew calendar
1080       *    xjn  n (month number) in Hebrew calendar
1081       *    xjY  Y (full year) in Hebrew calendar
1082       *
1083       *    xmj  j (day number) in Hijri calendar
1084       *    xmF  F (month name) in Hijri calendar
1085       *    xmn  n (month number) in Hijri calendar
1086       *    xmY  Y (full year) in Hijri calendar
1087       *
1088       *    xkY  Y (full year) in Thai solar calendar. Months and days are
1089       *                       identical to the Gregorian calendar
1090       *    xoY  Y (full year) in Minguo calendar or Juche year.
1091       *                       Months and days are identical to the
1092       *                       Gregorian calendar
1093       *    xtY  Y (full year) in Japanese nengo. Months and days are
1094       *                       identical to the Gregorian calendar
1095       *
1096       * Characters enclosed in double quotes will be considered literal (with
1097       * the quotes themselves removed). Unmatched quotes will be considered
1098       * literal quotes. Example:
1099       *
1100       * "The month is" F       => The month is January
1101       * i's"                   => 20'11"
1102       *
1103       * Backslash escaping is also supported.
1104       *
1105       * Input timestamp is assumed to be pre-normalized to the desired local
1106       * time zone, if any. Note that the format characters crUeIOPTZ will assume
1107       * $ts is UTC if $zone is not given.
1108       *
1109       * @param string $format
1110       * @param string $ts 14-character timestamp
1111       *      YYYYMMDDHHMMSS
1112       *      01234567890123
1113       * @param DateTimeZone $zone Timezone of $ts
1114       * @param[out] int $ttl The amount of time (in seconds) the output may be cached for.
1115       * Only makes sense if $ts is the current time.
1116       * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
1117       *
1118       * @throws MWException
1119       * @return string
1120       */
1121  	function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = null ) {
1122          $s = '';
1123          $raw = false;
1124          $roman = false;
1125          $hebrewNum = false;
1126          $dateTimeObj = false;
1127          $rawToggle = false;
1128          $iranian = false;
1129          $hebrew = false;
1130          $hijri = false;
1131          $thai = false;
1132          $minguo = false;
1133          $tenno = false;
1134  
1135          $usedSecond = false;
1136          $usedMinute = false;
1137          $usedHour = false;
1138          $usedAMPM = false;
1139          $usedDay = false;
1140          $usedWeek = false;
1141          $usedMonth = false;
1142          $usedYear = false;
1143          $usedISOYear = false;
1144          $usedIsLeapYear = false;
1145  
1146          $usedHebrewMonth = false;
1147          $usedIranianMonth = false;
1148          $usedHijriMonth = false;
1149          $usedHebrewYear = false;
1150          $usedIranianYear = false;
1151          $usedHijriYear = false;
1152          $usedTennoYear = false;
1153  
1154          if ( strlen( $ts ) !== 14 ) {
1155              throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
1156          }
1157  
1158          if ( !ctype_digit( $ts ) ) {
1159              throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
1160          }
1161  
1162          $formatLength = strlen( $format );
1163          for ( $p = 0; $p < $formatLength; $p++ ) {
1164              $num = false;
1165              $code = $format[$p];
1166              if ( $code == 'x' && $p < $formatLength - 1 ) {
1167                  $code .= $format[++$p];
1168              }
1169  
1170              if ( ( $code === 'xi'
1171                      || $code === 'xj'
1172                      || $code === 'xk'
1173                      || $code === 'xm'
1174                      || $code === 'xo'
1175                      || $code === 'xt' )
1176                  && $p < $formatLength - 1 ) {
1177                  $code .= $format[++$p];
1178              }
1179  
1180              switch ( $code ) {
1181                  case 'xx':
1182                      $s .= 'x';
1183                      break;
1184                  case 'xn':
1185                      $raw = true;
1186                      break;
1187                  case 'xN':
1188                      $rawToggle = !$rawToggle;
1189                      break;
1190                  case 'xr':
1191                      $roman = true;
1192                      break;
1193                  case 'xh':
1194                      $hebrewNum = true;
1195                      break;
1196                  case 'xg':
1197                      $usedMonth = true;
1198                      $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
1199                      break;
1200                  case 'xjx':
1201                      $usedHebrewMonth = true;
1202                      if ( !$hebrew ) {
1203                          $hebrew = self::tsToHebrew( $ts );
1204                      }
1205                      $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
1206                      break;
1207                  case 'd':
1208                      $usedDay = true;
1209                      $num = substr( $ts, 6, 2 );
1210                      break;
1211                  case 'D':
1212                      $usedDay = true;
1213                      $s .= $this->getWeekdayAbbreviation( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1 );
1214                      break;
1215                  case 'j':
1216                      $usedDay = true;
1217                      $num = intval( substr( $ts, 6, 2 ) );
1218                      break;
1219                  case 'xij':
1220                      $usedDay = true;
1221                      if ( !$iranian ) {
1222                          $iranian = self::tsToIranian( $ts );
1223                      }
1224                      $num = $iranian[2];
1225                      break;
1226                  case 'xmj':
1227                      $usedDay = true;
1228                      if ( !$hijri ) {
1229                          $hijri = self::tsToHijri( $ts );
1230                      }
1231                      $num = $hijri[2];
1232                      break;
1233                  case 'xjj':
1234                      $usedDay = true;
1235                      if ( !$hebrew ) {
1236                          $hebrew = self::tsToHebrew( $ts );
1237                      }
1238                      $num = $hebrew[2];
1239                      break;
1240                  case 'l':
1241                      $usedDay = true;
1242                      $s .= $this->getWeekdayName( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1 );
1243                      break;
1244                  case 'F':
1245                      $usedMonth = true;
1246                      $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
1247                      break;
1248                  case 'xiF':
1249                      $usedIranianMonth = true;
1250                      if ( !$iranian ) {
1251                          $iranian = self::tsToIranian( $ts );
1252                      }
1253                      $s .= $this->getIranianCalendarMonthName( $iranian[1] );
1254                      break;
1255                  case 'xmF':
1256                      $usedHijriMonth = true;
1257                      if ( !$hijri ) {
1258                          $hijri = self::tsToHijri( $ts );
1259                      }
1260                      $s .= $this->getHijriCalendarMonthName( $hijri[1] );
1261                      break;
1262                  case 'xjF':
1263                      $usedHebrewMonth = true;
1264                      if ( !$hebrew ) {
1265                          $hebrew = self::tsToHebrew( $ts );
1266                      }
1267                      $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
1268                      break;
1269                  case 'm':
1270                      $usedMonth = true;
1271                      $num = substr( $ts, 4, 2 );
1272                      break;
1273                  case 'M':
1274                      $usedMonth = true;
1275                      $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
1276                      break;
1277                  case 'n':
1278                      $usedMonth = true;
1279                      $num = intval( substr( $ts, 4, 2 ) );
1280                      break;
1281                  case 'xin':
1282                      $usedIranianMonth = true;
1283                      if ( !$iranian ) {
1284                          $iranian = self::tsToIranian( $ts );
1285                      }
1286                      $num = $iranian[1];
1287                      break;
1288                  case 'xmn':
1289                      $usedHijriMonth = true;
1290                      if ( !$hijri ) {
1291                          $hijri = self::tsToHijri ( $ts );
1292                      }
1293                      $num = $hijri[1];
1294                      break;
1295                  case 'xjn':
1296                      $usedHebrewMonth = true;
1297                      if ( !$hebrew ) {
1298                          $hebrew = self::tsToHebrew( $ts );
1299                      }
1300                      $num = $hebrew[1];
1301                      break;
1302                  case 'xjt':
1303                      $usedHebrewMonth = true;
1304                      if ( !$hebrew ) {
1305                          $hebrew = self::tsToHebrew( $ts );
1306                      }
1307                      $num = $hebrew[3];
1308                      break;
1309                  case 'Y':
1310                      $usedYear = true;
1311                      $num = substr( $ts, 0, 4 );
1312                      break;
1313                  case 'xiY':
1314                      $usedIranianYear = true;
1315                      if ( !$iranian ) {
1316                          $iranian = self::tsToIranian( $ts );
1317                      }
1318                      $num = $iranian[0];
1319                      break;
1320                  case 'xmY':
1321                      $usedHijriYear = true;
1322                      if ( !$hijri ) {
1323                          $hijri = self::tsToHijri( $ts );
1324                      }
1325                      $num = $hijri[0];
1326                      break;
1327                  case 'xjY':
1328                      $usedHebrewYear = true;
1329                      if ( !$hebrew ) {
1330                          $hebrew = self::tsToHebrew( $ts );
1331                      }
1332                      $num = $hebrew[0];
1333                      break;
1334                  case 'xkY':
1335                      $usedYear = true;
1336                      if ( !$thai ) {
1337                          $thai = self::tsToYear( $ts, 'thai' );
1338                      }
1339                      $num = $thai[0];
1340                      break;
1341                  case 'xoY':
1342                      $usedYear = true;
1343                      if ( !$minguo ) {
1344                          $minguo = self::tsToYear( $ts, 'minguo' );
1345                      }
1346                      $num = $minguo[0];
1347                      break;
1348                  case 'xtY':
1349                      $usedTennoYear = true;
1350                      if ( !$tenno ) {
1351                          $tenno = self::tsToYear( $ts, 'tenno' );
1352                      }
1353                      $num = $tenno[0];
1354                      break;
1355                  case 'y':
1356                      $usedYear = true;
1357                      $num = substr( $ts, 2, 2 );
1358                      break;
1359                  case 'xiy':
1360                      $usedIranianYear = true;
1361                      if ( !$iranian ) {
1362                          $iranian = self::tsToIranian( $ts );
1363                      }
1364                      $num = substr( $iranian[0], -2 );
1365                      break;
1366                  case 'a':
1367                      $usedAMPM = true;
1368                      $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
1369                      break;
1370                  case 'A':
1371                      $usedAMPM = true;
1372                      $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
1373                      break;
1374                  case 'g':
1375                      $usedHour = true;
1376                      $h = substr( $ts, 8, 2 );
1377                      $num = $h % 12 ? $h % 12 : 12;
1378                      break;
1379                  case 'G':
1380                      $usedHour = true;
1381                      $num = intval( substr( $ts, 8, 2 ) );
1382                      break;
1383                  case 'h':
1384                      $usedHour = true;
1385                      $h = substr( $ts, 8, 2 );
1386                      $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
1387                      break;
1388                  case 'H':
1389                      $usedHour = true;
1390                      $num = substr( $ts, 8, 2 );
1391                      break;
1392                  case 'i':
1393                      $usedMinute = true;
1394                      $num = substr( $ts, 10, 2 );
1395                      break;
1396                  case 's':
1397                      $usedSecond = true;
1398                      $num = substr( $ts, 12, 2 );
1399                      break;
1400                  case 'c':
1401                  case 'r':
1402                      $usedSecond = true;
1403                      // fall through
1404                  case 'e':
1405                  case 'O':
1406                  case 'P':
1407                  case 'T':
1408                      $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1409                      break;
1410                  case 'w':
1411                  case 'N':
1412                  case 'z':
1413                      $usedDay = true;
1414                      $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1415                      break;
1416                  case 'W':
1417                      $usedWeek = true;
1418                      $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1419                      break;
1420                  case 't':
1421                      $usedMonth = true;
1422                      $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1423                      break;
1424                  case 'L':
1425                      $usedIsLeapYear = true;
1426                      $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1427                      break;
1428                  case 'o':
1429                      $usedISOYear = true;
1430                      $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1431                      break;
1432                  case 'U':
1433                      $usedSecond = true;
1434                      // fall through
1435                  case 'I':
1436                  case 'Z':
1437                      $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
1438                      break;
1439                  case '\\':
1440                      # Backslash escaping
1441                      if ( $p < $formatLength - 1 ) {
1442                          $s .= $format[++$p];
1443                      } else {
1444                          $s .= '\\';
1445                      }
1446                      break;
1447                  case '"':
1448                      # Quoted literal
1449                      if ( $p < $formatLength - 1 ) {
1450                          $endQuote = strpos( $format, '"', $p + 1 );
1451                          if ( $endQuote === false ) {
1452                              # No terminating quote, assume literal "
1453                              $s .= '"';
1454                          } else {
1455                              $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1456                              $p = $endQuote;
1457                          }
1458                      } else {
1459                          # Quote at end of string, assume literal "
1460                          $s .= '"';
1461                      }
1462                      break;
1463                  default:
1464                      $s .= $format[$p];
1465              }
1466              if ( $num !== false ) {
1467                  if ( $rawToggle || $raw ) {
1468                      $s .= $num;
1469                      $raw = false;
1470                  } elseif ( $roman ) {
1471                      $s .= Language::romanNumeral( $num );
1472                      $roman = false;
1473                  } elseif ( $hebrewNum ) {
1474                      $s .= self::hebrewNumeral( $num );
1475                      $hebrewNum = false;
1476                  } else {
1477                      $s .= $this->formatNum( $num, true );
1478                  }
1479              }
1480          }
1481  
1482          if ( $usedSecond ) {
1483              $ttl = 1;
1484          } elseif ( $usedMinute ) {
1485              $ttl = 60 - substr( $ts, 12, 2 );
1486          } elseif ( $usedHour ) {
1487              $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1488          } elseif ( $usedAMPM ) {
1489              $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1490          } elseif ( $usedDay || $usedHebrewMonth || $usedIranianMonth || $usedHijriMonth || $usedHebrewYear || $usedIranianYear || $usedHijriYear || $usedTennoYear ) {
1491              // @todo Someone who understands the non-Gregorian calendars should write proper logic for them
1492              // so that they don't need purged every day.
1493              $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1494          } else {
1495              $possibleTtls = array();
1496              $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
1497              if ( $usedWeek ) {
1498                  $possibleTtls[] = ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 + $timeRemainingInDay;
1499              } elseif ( $usedISOYear ) {
1500                  // December 28th falls on the last ISO week of the year, every year.
1501                  // The last ISO week of a year can be 52 or 53.
1502                  $lastWeekOfISOYear = DateTime::createFromFormat( 'Ymd', substr( $ts, 0, 4 ) . '1228', $zone ?: new DateTimeZone( 'UTC' ) )->format( 'W' );
1503                  $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
1504                  $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
1505                  $timeRemainingInWeek = ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 + $timeRemainingInDay;
1506                  $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
1507              }
1508  
1509              if ( $usedMonth ) {
1510                  $possibleTtls[] = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) - substr( $ts, 6, 2 ) ) * 86400 + $timeRemainingInDay;
1511              } elseif ( $usedYear ) {
1512                  $possibleTtls[] = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1513                      + $timeRemainingInDay;
1514              } elseif ( $usedIsLeapYear ) {
1515                  $year = substr( $ts, 0, 4 );
1516                  $timeRemainingInYear = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
1517                      + $timeRemainingInDay;
1518                  $mod = $year % 4;
1519                  if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
1520                      // this isn't a leap year. see when the next one starts
1521                      $nextCandidate = $year - $mod + 4;
1522                      if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
1523                          $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 + $timeRemainingInYear;
1524                      } else {
1525                          $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 + $timeRemainingInYear;
1526                      }
1527                  } else {
1528                      // this is a leap year, so the next year isn't
1529                      $possibleTtls[] = $timeRemainingInYear;
1530                  }
1531              }
1532  
1533              if ( $possibleTtls ) {
1534                  $ttl = min( $possibleTtls );
1535              }
1536          }
1537  
1538          return $s;
1539      }
1540  
1541      private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
1542      private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
1543  
1544      /**
1545       * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
1546       * Gregorian dates to Iranian dates. Originally written in C, it
1547       * is released under the terms of GNU Lesser General Public
1548       * License. Conversion to PHP was performed by Niklas Laxström.
1549       *
1550       * Link: http://www.farsiweb.info/jalali/jalali.c
1551       *
1552       * @param string $ts
1553       *
1554       * @return string
1555       */
1556  	private static function tsToIranian( $ts ) {
1557          $gy = substr( $ts, 0, 4 ) -1600;
1558          $gm = substr( $ts, 4, 2 ) -1;
1559          $gd = substr( $ts, 6, 2 ) -1;
1560  
1561          # Days passed from the beginning (including leap years)
1562          $gDayNo = 365 * $gy
1563              + floor( ( $gy + 3 ) / 4 )
1564              - floor( ( $gy + 99 ) / 100 )
1565              + floor( ( $gy + 399 ) / 400 );
1566  
1567          // Add days of the past months of this year
1568          for ( $i = 0; $i < $gm; $i++ ) {
1569              $gDayNo += self::$GREG_DAYS[$i];
1570          }
1571  
1572          // Leap years
1573          if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1574              $gDayNo++;
1575          }
1576  
1577          // Days passed in current month
1578          $gDayNo += (int)$gd;
1579  
1580          $jDayNo = $gDayNo - 79;
1581  
1582          $jNp = floor( $jDayNo / 12053 );
1583          $jDayNo %= 12053;
1584  
1585          $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1586          $jDayNo %= 1461;
1587  
1588          if ( $jDayNo >= 366 ) {
1589              $jy += floor( ( $jDayNo - 1 ) / 365 );
1590              $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1591          }
1592  
1593          for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1594              $jDayNo -= self::$IRANIAN_DAYS[$i];
1595          }
1596  
1597          $jm = $i + 1;
1598          $jd = $jDayNo + 1;
1599  
1600          return array( $jy, $jm, $jd );
1601      }
1602  
1603      /**
1604       * Converting Gregorian dates to Hijri dates.
1605       *
1606       * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license
1607       *
1608       * @see http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0
1609       *
1610       * @param string $ts
1611       *
1612       * @return string
1613       */
1614  	private static function tsToHijri( $ts ) {
1615          $year = substr( $ts, 0, 4 );
1616          $month = substr( $ts, 4, 2 );
1617          $day = substr( $ts, 6, 2 );
1618  
1619          $zyr = $year;
1620          $zd = $day;
1621          $zm = $month;
1622          $zy = $zyr;
1623  
1624          if (
1625              ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1626              ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1627          ) {
1628              $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1629                      (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1630                      (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1631                      $zd - 32075;
1632          } else {
1633              $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1634                                  (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1635          }
1636  
1637          $zl = $zjd -1948440 + 10632;
1638          $zn = (int)( ( $zl - 1 ) / 10631 );
1639          $zl = $zl - 10631 * $zn + 354;
1640          $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
1641              ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
1642          $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
1643              ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
1644          $zm = (int)( ( 24 * $zl ) / 709 );
1645          $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1646          $zy = 30 * $zn + $zj - 30;
1647  
1648          return array( $zy, $zm, $zd );
1649      }
1650  
1651      /**
1652       * Converting Gregorian dates to Hebrew dates.
1653       *
1654       * Based on a JavaScript code by Abu Mami and Yisrael Hersch
1655       * ([email protected], http://www.kaluach.net), who permitted
1656       * to translate the relevant functions into PHP and release them under
1657       * GNU GPL.
1658       *
1659       * The months are counted from Tishrei = 1. In a leap year, Adar I is 13
1660       * and Adar II is 14. In a non-leap year, Adar is 6.
1661       *
1662       * @param string $ts
1663       *
1664       * @return string
1665       */
1666  	private static function tsToHebrew( $ts ) {
1667          # Parse date
1668          $year = substr( $ts, 0, 4 );
1669          $month = substr( $ts, 4, 2 );
1670          $day = substr( $ts, 6, 2 );
1671  
1672          # Calculate Hebrew year
1673          $hebrewYear = $year + 3760;
1674  
1675          # Month number when September = 1, August = 12
1676          $month += 4;
1677          if ( $month > 12 ) {
1678              # Next year
1679              $month -= 12;
1680              $year++;
1681              $hebrewYear++;
1682          }
1683  
1684          # Calculate day of year from 1 September
1685          $dayOfYear = $day;
1686          for ( $i = 1; $i < $month; $i++ ) {
1687              if ( $i == 6 ) {
1688                  # February
1689                  $dayOfYear += 28;
1690                  # Check if the year is leap
1691                  if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1692                      $dayOfYear++;
1693                  }
1694              } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1695                  $dayOfYear += 30;
1696              } else {
1697                  $dayOfYear += 31;
1698              }
1699          }
1700  
1701          # Calculate the start of the Hebrew year
1702          $start = self::hebrewYearStart( $hebrewYear );
1703  
1704          # Calculate next year's start
1705          if ( $dayOfYear <= $start ) {
1706              # Day is before the start of the year - it is the previous year
1707              # Next year's start
1708              $nextStart = $start;
1709              # Previous year
1710              $year--;
1711              $hebrewYear--;
1712              # Add days since previous year's 1 September
1713              $dayOfYear += 365;
1714              if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1715                  # Leap year
1716                  $dayOfYear++;
1717              }
1718              # Start of the new (previous) year
1719              $start = self::hebrewYearStart( $hebrewYear );
1720          } else {
1721              # Next year's start
1722              $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1723          }
1724  
1725          # Calculate Hebrew day of year
1726          $hebrewDayOfYear = $dayOfYear - $start;
1727  
1728          # Difference between year's days
1729          $diff = $nextStart - $start;
1730          # Add 12 (or 13 for leap years) days to ignore the difference between
1731          # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1732          # difference is only about the year type
1733          if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1734              $diff += 13;
1735          } else {
1736              $diff += 12;
1737          }
1738  
1739          # Check the year pattern, and is leap year
1740          # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1741          # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1742          # and non-leap years
1743          $yearPattern = $diff % 30;
1744          # Check if leap year
1745          $isLeap = $diff >= 30;
1746  
1747          # Calculate day in the month from number of day in the Hebrew year
1748          # Don't check Adar - if the day is not in Adar, we will stop before;
1749          # if it is in Adar, we will use it to check if it is Adar I or Adar II
1750          $hebrewDay = $hebrewDayOfYear;
1751          $hebrewMonth = 1;
1752          $days = 0;
1753          while ( $hebrewMonth <= 12 ) {
1754              # Calculate days in this month
1755              if ( $isLeap && $hebrewMonth == 6 ) {
1756                  # Adar in a leap year
1757                  if ( $isLeap ) {
1758                      # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1759                      $days = 30;
1760                      if ( $hebrewDay <= $days ) {
1761                          # Day in Adar I
1762                          $hebrewMonth = 13;
1763                      } else {
1764                          # Subtract the days of Adar I
1765                          $hebrewDay -= $days;
1766                          # Try Adar II
1767                          $days = 29;
1768                          if ( $hebrewDay <= $days ) {
1769                              # Day in Adar II
1770                              $hebrewMonth = 14;
1771                          }
1772                      }
1773                  }
1774              } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1775                  # Cheshvan in a complete year (otherwise as the rule below)
1776                  $days = 30;
1777              } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1778                  # Kislev in an incomplete year (otherwise as the rule below)
1779                  $days = 29;
1780              } else {
1781                  # Odd months have 30 days, even have 29
1782                  $days = 30 - ( $hebrewMonth - 1 ) % 2;
1783              }
1784              if ( $hebrewDay <= $days ) {
1785                  # In the current month
1786                  break;
1787              } else {
1788                  # Subtract the days of the current month
1789                  $hebrewDay -= $days;
1790                  # Try in the next month
1791                  $hebrewMonth++;
1792              }
1793          }
1794  
1795          return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
1796      }
1797  
1798      /**
1799       * This calculates the Hebrew year start, as days since 1 September.
1800       * Based on Carl Friedrich Gauss algorithm for finding Easter date.
1801       * Used for Hebrew date.
1802       *
1803       * @param int $year
1804       *
1805       * @return string
1806       */
1807  	private static function hebrewYearStart( $year ) {
1808          $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1809          $b = intval( ( $year - 1 ) % 4 );
1810          $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1811          if ( $m < 0 ) {
1812              $m--;
1813          }
1814          $Mar = intval( $m );
1815          if ( $m < 0 ) {
1816              $m++;
1817          }
1818          $m -= $Mar;
1819  
1820          $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1821          if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1822              $Mar++;
1823          } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1824              $Mar += 2;
1825          } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1826              $Mar++;
1827          }
1828  
1829          $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1830          return $Mar;
1831      }
1832  
1833      /**
1834       * Algorithm to convert Gregorian dates to Thai solar dates,
1835       * Minguo dates or Minguo dates.
1836       *
1837       * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
1838       *       http://en.wikipedia.org/wiki/Minguo_calendar
1839       *       http://en.wikipedia.org/wiki/Japanese_era_name
1840       *
1841       * @param string $ts 14-character timestamp
1842       * @param string $cName Calender name
1843       * @return array Converted year, month, day
1844       */
1845  	private static function tsToYear( $ts, $cName ) {
1846          $gy = substr( $ts, 0, 4 );
1847          $gm = substr( $ts, 4, 2 );
1848          $gd = substr( $ts, 6, 2 );
1849  
1850          if ( !strcmp( $cName, 'thai' ) ) {
1851              # Thai solar dates
1852              # Add 543 years to the Gregorian calendar
1853              # Months and days are identical
1854              $gy_offset = $gy + 543;
1855          } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
1856              # Minguo dates
1857              # Deduct 1911 years from the Gregorian calendar
1858              # Months and days are identical
1859              $gy_offset = $gy - 1911;
1860          } elseif ( !strcmp( $cName, 'tenno' ) ) {
1861              # Nengō dates up to Meiji period
1862              # Deduct years from the Gregorian calendar
1863              # depending on the nengo periods
1864              # Months and days are identical
1865              if ( ( $gy < 1912 )
1866                  || ( ( $gy == 1912 ) && ( $gm < 7 ) )
1867                  || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
1868              ) {
1869                  # Meiji period
1870                  $gy_gannen = $gy - 1868 + 1;
1871                  $gy_offset = $gy_gannen;
1872                  if ( $gy_gannen == 1 ) {
1873                      $gy_offset = '元';
1874                  }
1875                  $gy_offset = '明治' . $gy_offset;
1876              } elseif (
1877                  ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1878                  ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1879                  ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1880                  ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1881                  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1882              ) {
1883                  # Taishō period
1884                  $gy_gannen = $gy - 1912 + 1;
1885                  $gy_offset = $gy_gannen;
1886                  if ( $gy_gannen == 1 ) {
1887                      $gy_offset = '元';
1888                  }
1889                  $gy_offset = '大正' . $gy_offset;
1890              } elseif (
1891                  ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1892                  ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1893                  ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1894              ) {
1895                  # Shōwa period
1896                  $gy_gannen = $gy - 1926 + 1;
1897                  $gy_offset = $gy_gannen;
1898                  if ( $gy_gannen == 1 ) {
1899                      $gy_offset = '元';
1900                  }
1901                  $gy_offset = '昭和' . $gy_offset;
1902              } else {
1903                  # Heisei period
1904                  $gy_gannen = $gy - 1989 + 1;
1905                  $gy_offset = $gy_gannen;
1906                  if ( $gy_gannen == 1 ) {
1907                      $gy_offset = '元';
1908                  }
1909                  $gy_offset = '平成' . $gy_offset;
1910              }
1911          } else {
1912              $gy_offset = $gy;
1913          }
1914  
1915          return array( $gy_offset, $gm, $gd );
1916      }
1917  
1918      /**
1919       * Roman number formatting up to 10000
1920       *
1921       * @param int $num
1922       *
1923       * @return string
1924       */
1925  	static function romanNumeral( $num ) {
1926          static $table = array(
1927              array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1928              array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1929              array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1930              array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
1931                  'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' )
1932          );
1933  
1934          $num = intval( $num );
1935          if ( $num > 10000 || $num <= 0 ) {
1936              return $num;
1937          }
1938  
1939          $s = '';
1940          for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1941              if ( $num >= $pow10 ) {
1942                  $s .= $table[$i][(int)floor( $num / $pow10 )];
1943              }
1944              $num = $num % $pow10;
1945          }
1946          return $s;
1947      }
1948  
1949      /**
1950       * Hebrew Gematria number formatting up to 9999
1951       *
1952       * @param int $num
1953       *
1954       * @return string
1955       */
1956  	static function hebrewNumeral( $num ) {
1957          static $table = array(
1958              array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1959              array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1960              array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1961              array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1962          );
1963  
1964          $num = intval( $num );
1965          if ( $num > 9999 || $num <= 0 ) {
1966              return $num;
1967          }
1968  
1969          $s = '';
1970          for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1971              if ( $num >= $pow10 ) {
1972                  if ( $num == 15 || $num == 16 ) {
1973                      $s .= $table[0][9] . $table[0][$num - 9];
1974                      $num = 0;
1975                  } else {
1976                      $s .= $table[$i][intval( ( $num / $pow10 ) )];
1977                      if ( $pow10 == 1000 ) {
1978                          $s .= "'";
1979                      }
1980                  }
1981              }
1982              $num = $num % $pow10;
1983          }
1984          if ( strlen( $s ) == 2 ) {
1985              $str = $s . "'";
1986          } else {
1987              $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1988              $str .= substr( $s, strlen( $s ) - 2, 2 );
1989          }
1990          $start = substr( $str, 0, strlen( $str ) - 2 );
1991          $end = substr( $str, strlen( $str ) - 2 );
1992          switch ( $end ) {
1993              case 'כ':
1994                  $str = $start . 'ך';
1995                  break;
1996              case 'מ':
1997                  $str = $start . 'ם';
1998                  break;
1999              case 'נ':
2000                  $str = $start . 'ן';
2001                  break;
2002              case 'פ':
2003                  $str = $start . 'ף';
2004                  break;
2005              case 'צ':
2006                  $str = $start . 'ץ';
2007                  break;
2008          }
2009          return $str;
2010      }
2011  
2012      /**
2013       * Used by date() and time() to adjust the time output.
2014       *
2015       * @param string $ts The time in date('YmdHis') format
2016       * @param mixed $tz Adjust the time by this amount (default false, mean we
2017       *   get user timecorrection setting)
2018       * @return int
2019       */
2020  	function userAdjust( $ts, $tz = false ) {
2021          global $wgUser, $wgLocalTZoffset;
2022  
2023          if ( $tz === false ) {
2024              $tz = $wgUser->getOption( 'timecorrection' );
2025          }
2026  
2027          $data = explode( '|', $tz, 3 );
2028  
2029          if ( $data[0] == 'ZoneInfo' ) {
2030              wfSuppressWarnings();
2031              $userTZ = timezone_open( $data[2] );
2032              wfRestoreWarnings();
2033              if ( $userTZ !== false ) {
2034                  $date = date_create( $ts, timezone_open( 'UTC' ) );
2035                  date_timezone_set( $date, $userTZ );
2036                  $date = date_format( $date, 'YmdHis' );
2037                  return $date;
2038              }
2039              # Unrecognized timezone, default to 'Offset' with the stored offset.
2040              $data[0] = 'Offset';
2041          }
2042  
2043          if ( $data[0] == 'System' || $tz == '' ) {
2044              # Global offset in minutes.
2045              $minDiff = $wgLocalTZoffset;
2046          } elseif ( $data[0] == 'Offset' ) {
2047              $minDiff = intval( $data[1] );
2048          } else {
2049              $data = explode( ':', $tz );
2050              if ( count( $data ) == 2 ) {
2051                  $data[0] = intval( $data[0] );
2052                  $data[1] = intval( $data[1] );
2053                  $minDiff = abs( $data[0] ) * 60 + $data[1];
2054                  if ( $data[0] < 0 ) {
2055                      $minDiff = -$minDiff;
2056                  }
2057              } else {
2058                  $minDiff = intval( $data[0] ) * 60;
2059              }
2060          }
2061  
2062          # No difference ? Return time unchanged
2063          if ( 0 == $minDiff ) {
2064              return $ts;
2065          }
2066  
2067          wfSuppressWarnings(); // E_STRICT system time bitching
2068          # Generate an adjusted date; take advantage of the fact that mktime
2069          # will normalize out-of-range values so we don't have to split $minDiff
2070          # into hours and minutes.
2071          $t = mktime( (
2072              (int)substr( $ts, 8, 2 ) ), # Hours
2073              (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
2074              (int)substr( $ts, 12, 2 ), # Seconds
2075              (int)substr( $ts, 4, 2 ), # Month
2076              (int)substr( $ts, 6, 2 ), # Day
2077              (int)substr( $ts, 0, 4 ) ); # Year
2078  
2079          $date = date( 'YmdHis', $t );
2080          wfRestoreWarnings();
2081  
2082          return $date;
2083      }
2084  
2085      /**
2086       * This is meant to be used by time(), date(), and timeanddate() to get
2087       * the date preference they're supposed to use, it should be used in
2088       * all children.
2089       *
2090       *<code>
2091       * function timeanddate([...], $format = true) {
2092       *     $datePreference = $this->dateFormat($format);
2093       * [...]
2094       * }
2095       *</code>
2096       *
2097       * @param int|string|bool $usePrefs If true, the user's preference is used
2098       *   if false, the site/language default is used
2099       *   if int/string, assumed to be a format.
2100       * @return string
2101       */
2102  	function dateFormat( $usePrefs = true ) {
2103          global $wgUser;
2104  
2105          if ( is_bool( $usePrefs ) ) {
2106              if ( $usePrefs ) {
2107                  $datePreference = $wgUser->getDatePreference();
2108              } else {
2109                  $datePreference = (string)User::getDefaultOption( 'date' );
2110              }
2111          } else {
2112              $datePreference = (string)$usePrefs;
2113          }
2114  
2115          // return int
2116          if ( $datePreference == '' ) {
2117              return 'default';
2118          }
2119  
2120          return $datePreference;
2121      }
2122  
2123      /**
2124       * Get a format string for a given type and preference
2125       * @param string $type May be date, time or both
2126       * @param string $pref The format name as it appears in Messages*.php
2127       *
2128       * @since 1.22 New type 'pretty' that provides a more readable timestamp format
2129       *
2130       * @return string
2131       */
2132  	function getDateFormatString( $type, $pref ) {
2133          if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
2134              if ( $pref == 'default' ) {
2135                  $pref = $this->getDefaultDateFormat();
2136                  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2137              } else {
2138                  $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2139  
2140                  if ( $type === 'pretty' && $df === null ) {
2141                      $df = $this->getDateFormatString( 'date', $pref );
2142                  }
2143  
2144                  if ( $df === null ) {
2145                      $pref = $this->getDefaultDateFormat();
2146                      $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
2147                  }
2148              }
2149              $this->dateFormatStrings[$type][$pref] = $df;
2150          }
2151          return $this->dateFormatStrings[$type][$pref];
2152      }
2153  
2154      /**
2155       * @param string $ts The time format which needs to be turned into a
2156       *   date('YmdHis') format with wfTimestamp(TS_MW,$ts)
2157       * @param bool $adj Whether to adjust the time output according to the
2158       *   user configured offset ($timecorrection)
2159       * @param mixed $format True to use user's date format preference
2160       * @param string|bool $timecorrection The time offset as returned by
2161       *   validateTimeZone() in Special:Preferences
2162       * @return string
2163       */
2164  	function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
2165          $ts = wfTimestamp( TS_MW, $ts );
2166          if ( $adj ) {
2167              $ts = $this->userAdjust( $ts, $timecorrection );
2168          }
2169          $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
2170          return $this->sprintfDate( $df, $ts );
2171      }
2172  
2173      /**
2174       * @param string $ts The time format which needs to be turned into a
2175       *   date('YmdHis') format with wfTimestamp(TS_MW,$ts)
2176       * @param bool $adj Whether to adjust the time output according to the
2177       *   user configured offset ($timecorrection)
2178       * @param mixed $format True to use user's date format preference
2179       * @param string|bool $timecorrection The time offset as returned by
2180       *   validateTimeZone() in Special:Preferences
2181       * @return string
2182       */
2183  	function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
2184          $ts = wfTimestamp( TS_MW, $ts );
2185          if ( $adj ) {
2186              $ts = $this->userAdjust( $ts, $timecorrection );
2187          }
2188          $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
2189          return $this->sprintfDate( $df, $ts );
2190      }
2191  
2192      /**
2193       * @param string $ts The time format which needs to be turned into a
2194       *   date('YmdHis') format with wfTimestamp(TS_MW,$ts)
2195       * @param bool $adj Whether to adjust the time output according to the
2196       *   user configured offset ($timecorrection)
2197       * @param mixed $format What format to return, if it's false output the
2198       *   default one (default true)
2199       * @param string|bool $timecorrection The time offset as returned by
2200       *   validateTimeZone() in Special:Preferences
2201       * @return string
2202       */
2203  	function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
2204          $ts = wfTimestamp( TS_MW, $ts );
2205          if ( $adj ) {
2206              $ts = $this->userAdjust( $ts, $timecorrection );
2207          }
2208          $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
2209          return $this->sprintfDate( $df, $ts );
2210      }
2211  
2212      /**
2213       * Takes a number of seconds and turns it into a text using values such as hours and minutes.
2214       *
2215       * @since 1.20
2216       *
2217       * @param int $seconds The amount of seconds.
2218       * @param array $chosenIntervals The intervals to enable.
2219       *
2220       * @return string
2221       */
2222  	public function formatDuration( $seconds, array $chosenIntervals = array() ) {
2223          $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
2224  
2225          $segments = array();
2226  
2227          foreach ( $intervals as $intervalName => $intervalValue ) {
2228              // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
2229              // duration-years, duration-decades, duration-centuries, duration-millennia
2230              $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
2231              $segments[] = $message->inLanguage( $this )->escaped();
2232          }
2233  
2234          return $this->listToText( $segments );
2235      }
2236  
2237      /**
2238       * Takes a number of seconds and returns an array with a set of corresponding intervals.
2239       * For example 65 will be turned into array( minutes => 1, seconds => 5 ).
2240       *
2241       * @since 1.20
2242       *
2243       * @param int $seconds The amount of seconds.
2244       * @param array $chosenIntervals The intervals to enable.
2245       *
2246       * @return array
2247       */
2248  	public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) {
2249          if ( empty( $chosenIntervals ) ) {
2250              $chosenIntervals = array(
2251                  'millennia',
2252                  'centuries',
2253                  'decades',
2254                  'years',
2255                  'days',
2256                  'hours',
2257                  'minutes',
2258                  'seconds'
2259              );
2260          }
2261  
2262          $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2263          $sortedNames = array_keys( $intervals );
2264          $smallestInterval = array_pop( $sortedNames );
2265  
2266          $segments = array();
2267  
2268          foreach ( $intervals as $name => $length ) {
2269              $value = floor( $seconds / $length );
2270  
2271              if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
2272                  $seconds -= $value * $length;
2273                  $segments[$name] = $value;
2274              }
2275          }
2276  
2277          return $segments;
2278      }
2279  
2280      /**
2281       * Internal helper function for userDate(), userTime() and userTimeAndDate()
2282       *
2283       * @param string $type Can be 'date', 'time' or 'both'
2284       * @param string $ts The time format which needs to be turned into a
2285       *   date('YmdHis') format with wfTimestamp(TS_MW,$ts)
2286       * @param User $user User object used to get preferences for timezone and format
2287       * @param array $options Array, can contain the following keys:
2288       *   - 'timecorrection': time correction, can have the following values:
2289       *     - true: use user's preference
2290       *     - false: don't use time correction
2291       *     - int: value of time correction in minutes
2292       *   - 'format': format to use, can have the following values:
2293       *     - true: use user's preference
2294       *     - false: use default preference
2295       *     - string: format to use
2296       * @since 1.19
2297       * @return string
2298       */
2299  	private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
2300          $ts = wfTimestamp( TS_MW, $ts );
2301          $options += array( 'timecorrection' => true, 'format' => true );
2302          if ( $options['timecorrection'] !== false ) {
2303              if ( $options['timecorrection'] === true ) {
2304                  $offset = $user->getOption( 'timecorrection' );
2305              } else {
2306                  $offset = $options['timecorrection'];
2307              }
2308              $ts = $this->userAdjust( $ts, $offset );
2309          }
2310          if ( $options['format'] === true ) {
2311              $format = $user->getDatePreference();
2312          } else {
2313              $format = $options['format'];
2314          }
2315          $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
2316          return $this->sprintfDate( $df, $ts );
2317      }
2318  
2319      /**
2320       * Get the formatted date for the given timestamp and formatted for
2321       * the given user.
2322       *
2323       * @param mixed $ts Mixed: the time format which needs to be turned into a
2324       *   date('YmdHis') format with wfTimestamp(TS_MW,$ts)
2325       * @param User $user User object used to get preferences for timezone and format
2326       * @param array $options Array, can contain the following keys:
2327       *   - 'timecorrection': time correction, can have the following values:
2328       *     - true: use user's preference
2329       *     - false: don't use time correction
2330       *     - int: value of time correction in minutes
2331       *   - 'format': format to use, can have the following values:
2332       *     - true: use user's preference
2333       *     - false: use default preference
2334       *     - string: format to use
2335       * @since 1.19
2336       * @return string
2337       */
2338  	public function userDate( $ts, User $user, array $options = array() ) {
2339          return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
2340      }
2341  
2342      /**
2343       * Get the formatted time for the given timestamp and formatted for
2344       * the given user.
2345       *
2346       * @param mixed $ts The time format which needs to be turned into a
2347       *   date('YmdHis') format with wfTimestamp(TS_MW,$ts)
2348       * @param User $user User object used to get preferences for timezone and format
2349       * @param array $options Array, can contain the following keys:
2350       *   - 'timecorrection': time correction, can have the following values:
2351       *     - true: use user's preference
2352       *     - false: don't use time correction
2353       *     - int: value of time correction in minutes
2354       *   - 'format': format to use, can have the following values:
2355       *     - true: use user's preference
2356       *     - false: use default preference
2357       *     - string: format to use
2358       * @since 1.19
2359       * @return string
2360       */
2361  	public function userTime( $ts, User $user, array $options = array() ) {
2362          return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
2363      }
2364  
2365      /**
2366       * Get the formatted date and time for the given timestamp and formatted for
2367       * the given user.
2368       *
2369       * @param mixed $ts The time format which needs to be turned into a
2370       *   date('YmdHis') format with wfTimestamp(TS_MW,$ts)
2371       * @param User $user User object used to get preferences for timezone and format
2372       * @param array $options Array, can contain the following keys:
2373       *   - 'timecorrection': time correction, can have the following values:
2374       *     - true: use user's preference
2375       *     - false: don't use time correction
2376       *     - int: value of time correction in minutes
2377       *   - 'format': format to use, can have the following values:
2378       *     - true: use user's preference
2379       *     - false: use default preference
2380       *     - string: format to use
2381       * @since 1.19
2382       * @return string
2383       */
2384  	public function userTimeAndDate( $ts, User $user, array $options = array() ) {
2385          return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
2386      }
2387  
2388      /**
2389       * Convert an MWTimestamp into a pretty human-readable timestamp using
2390       * the given user preferences and relative base time.
2391       *
2392       * DO NOT USE THIS FUNCTION DIRECTLY. Instead, call MWTimestamp::getHumanTimestamp
2393       * on your timestamp object, which will then call this function. Calling
2394       * this function directly will cause hooks to be skipped over.
2395       *
2396       * @see MWTimestamp::getHumanTimestamp
2397       * @param MWTimestamp $ts Timestamp to prettify
2398       * @param MWTimestamp $relativeTo Base timestamp
2399       * @param User $user User preferences to use
2400       * @return string Human timestamp
2401       * @since 1.22
2402       */
2403  	public function getHumanTimestamp( MWTimestamp $ts, MWTimestamp $relativeTo, User $user ) {
2404          $diff = $ts->diff( $relativeTo );
2405          $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
2406              (int)$relativeTo->timestamp->format( 'w' ) );
2407          $days = $diff->days ?: (int)$diffDay;
2408          if ( $diff->invert || $days > 5
2409              && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
2410          ) {
2411              // Timestamps are in different years: use full timestamp
2412              // Also do full timestamp for future dates
2413              /**
2414               * @todo FIXME: Add better handling of future timestamps.
2415               */
2416              $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
2417              $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2418          } elseif ( $days > 5 ) {
2419              // Timestamps are in same year,  but more than 5 days ago: show day and month only.
2420              $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
2421              $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
2422          } elseif ( $days > 1 ) {
2423              // Timestamp within the past week: show the day of the week and time
2424              $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2425              $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
2426              // Messages:
2427              // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
2428              $ts = wfMessage( "$weekday-at" )
2429                  ->inLanguage( $this )
2430                  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2431                  ->text();
2432          } elseif ( $days == 1 ) {
2433              // Timestamp was yesterday: say 'yesterday' and the time.
2434              $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2435              $ts = wfMessage( 'yesterday-at' )
2436                  ->inLanguage( $this )
2437                  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2438                  ->text();
2439          } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2440              // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
2441              $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
2442              $ts = wfMessage( 'today-at' )
2443                  ->inLanguage( $this )
2444                  ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
2445                  ->text();
2446  
2447          // From here on in, the timestamp was soon enough ago so that we can simply say
2448          // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
2449          } elseif ( $diff->h == 1 ) {
2450              // Less than 90 minutes, but more than an hour ago.
2451              $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2452          } elseif ( $diff->i >= 1 ) {
2453              // A few minutes ago.
2454              $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2455          } elseif ( $diff->s >= 30 ) {
2456              // Less than a minute, but more than 30 sec ago.
2457              $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2458          } else {
2459              // Less than 30 seconds ago.
2460              $ts = wfMessage( 'just-now' )->text();
2461          }
2462  
2463          return $ts;
2464      }
2465  
2466      /**
2467       * @param string $key
2468       * @return array|null
2469       */
2470  	function getMessage( $key ) {
2471          return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
2472      }
2473  
2474      /**
2475       * @return array
2476       */
2477  	function getAllMessages() {
2478          return self::$dataCache->getItem( $this->mCode, 'messages' );
2479      }
2480  
2481      /**
2482       * @param string $in
2483       * @param string $out
2484       * @param string $string
2485       * @return string
2486       */
2487  	function iconv( $in, $out, $string ) {
2488          # This is a wrapper for iconv in all languages except esperanto,
2489          # which does some nasty x-conversions beforehand
2490  
2491          # Even with //IGNORE iconv can whine about illegal characters in
2492          # *input* string. We just ignore those too.
2493          # REF: http://bugs.php.net/bug.php?id=37166
2494          # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885
2495          wfSuppressWarnings();
2496          $text = iconv( $in, $out . '//IGNORE', $string );
2497          wfRestoreWarnings();
2498          return $text;
2499      }
2500  
2501      // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
2502  
2503      /**
2504       * @param array $matches
2505       * @return mixed|string
2506       */
2507  	function ucwordbreaksCallbackAscii( $matches ) {
2508          return $this->ucfirst( $matches[1] );
2509      }
2510  
2511      /**
2512       * @param array $matches
2513       * @return string
2514       */
2515  	function ucwordbreaksCallbackMB( $matches ) {
2516          return mb_strtoupper( $matches[0] );
2517      }
2518  
2519      /**
2520       * @param array $matches
2521       * @return string
2522       */
2523  	function ucCallback( $matches ) {
2524          list( $wikiUpperChars ) = self::getCaseMaps();
2525          return strtr( $matches[1], $wikiUpperChars );
2526      }
2527  
2528      /**
2529       * @param array $matches
2530       * @return string
2531       */
2532  	function lcCallback( $matches ) {
2533          list( , $wikiLowerChars ) = self::getCaseMaps();
2534          return strtr( $matches[1], $wikiLowerChars );
2535      }
2536  
2537      /**
2538       * @param array $matches
2539       * @return string
2540       */
2541  	function ucwordsCallbackMB( $matches ) {
2542          return mb_strtoupper( $matches[0] );
2543      }
2544  
2545      /**
2546       * @param array $matches
2547       * @return string
2548       */
2549  	function ucwordsCallbackWiki( $matches ) {
2550          list( $wikiUpperChars ) = self::getCaseMaps();
2551          return strtr( $matches[0], $wikiUpperChars );
2552      }
2553  
2554      /**
2555       * Make a string's first character uppercase
2556       *
2557       * @param string $str
2558       *
2559       * @return string
2560       */
2561  	function ucfirst( $str ) {
2562          $o = ord( $str );
2563          if ( $o < 96 ) { // if already uppercase...
2564              return $str;
2565          } elseif ( $o < 128 ) {
2566              return ucfirst( $str ); // use PHP's ucfirst()
2567          } else {
2568              // fall back to more complex logic in case of multibyte strings
2569              return $this->uc( $str, true );
2570          }
2571      }
2572  
2573      /**
2574       * Convert a string to uppercase
2575       *
2576       * @param string $str
2577       * @param bool $first
2578       *
2579       * @return string
2580       */
2581      function uc( $str, $first = false ) {
2582          if ( function_exists( 'mb_strtoupper' ) ) {
2583              if ( $first ) {
2584                  if ( $this->isMultibyte( $str ) ) {
2585                      return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2586                  } else {
2587                      return ucfirst( $str );
2588                  }
2589              } else {
2590                  return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2591              }
2592          } else {
2593              if ( $this->isMultibyte( $str ) ) {
2594                  $x = $first ? '^' : '';
2595                  return preg_replace_callback(
2596                      "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
2597                      array( $this, 'ucCallback' ),
2598                      $str
2599                  );
2600              } else {
2601                  return $first ? ucfirst( $str ) : strtoupper( $str );
2602              }
2603          }
2604      }
2605  
2606      /**
2607       * @param string $str
2608       * @return mixed|string
2609       */
2610  	function lcfirst( $str ) {
2611          $o = ord( $str );
2612          if ( !$o ) {
2613              return strval( $str );
2614          } elseif ( $o >= 128 ) {
2615              return $this->lc( $str, true );
2616          } elseif ( $o > 96 ) {
2617              return $str;
2618          } else {
2619              $str[0] = strtolower( $str[0] );
2620              return $str;
2621          }
2622      }
2623  
2624      /**
2625       * @param string $str
2626       * @param bool $first
2627       * @return mixed|string
2628       */
2629      function lc( $str, $first = false ) {
2630          if ( function_exists( 'mb_strtolower' ) ) {
2631              if ( $first ) {
2632                  if ( $this->isMultibyte( $str ) ) {
2633                      return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2634                  } else {
2635                      return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2636                  }
2637              } else {
2638                  return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2639              }
2640          } else {
2641              if ( $this->isMultibyte( $str ) ) {
2642                  $x = $first ? '^' : '';
2643                  return preg_replace_callback(
2644                      "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
2645                      array( $this, 'lcCallback' ),
2646                      $str
2647                  );
2648              } else {
2649                  return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
2650              }
2651          }
2652      }
2653  
2654      /**
2655       * @param string $str
2656       * @return bool
2657       */
2658  	function isMultibyte( $str ) {
2659          return (bool)preg_match( '/[\x80-\xff]/', $str );
2660      }
2661  
2662      /**
2663       * @param string $str
2664       * @return mixed|string
2665       */
2666  	function ucwords( $str ) {
2667          if ( $this->isMultibyte( $str ) ) {
2668              $str = $this->lc( $str );
2669  
2670              // regexp to find first letter in each word (i.e. after each space)
2671              $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2672  
2673              // function to use to capitalize a single char
2674              if ( function_exists( 'mb_strtoupper' ) ) {
2675                  return preg_replace_callback(
2676                      $replaceRegexp,
2677                      array( $this, 'ucwordsCallbackMB' ),
2678                      $str
2679                  );
2680              } else {
2681                  return preg_replace_callback(
2682                      $replaceRegexp,
2683                      array( $this, 'ucwordsCallbackWiki' ),
2684                      $str
2685                  );
2686              }
2687          } else {
2688              return ucwords( strtolower( $str ) );
2689          }
2690      }
2691  
2692      /**
2693       * capitalize words at word breaks
2694       *
2695       * @param string $str
2696       * @return mixed
2697       */
2698  	function ucwordbreaks( $str ) {
2699          if ( $this->isMultibyte( $str ) ) {
2700              $str = $this->lc( $str );
2701  
2702              // since \b doesn't work for UTF-8, we explicitely define word break chars
2703              $breaks = "[ \-\(\)\}\{\.,\?!]";
2704  
2705              // find first letter after word break
2706              $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
2707                  "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2708  
2709              if ( function_exists( 'mb_strtoupper' ) ) {
2710                  return preg_replace_callback(
2711                      $replaceRegexp,
2712                      array( $this, 'ucwordbreaksCallbackMB' ),
2713                      $str
2714                  );
2715              } else {
2716                  return preg_replace_callback(
2717                      $replaceRegexp,
2718                      array( $this, 'ucwordsCallbackWiki' ),
2719                      $str
2720                  );
2721              }
2722          } else {
2723              return preg_replace_callback(
2724                  '/\b([\w\x80-\xff]+)\b/',
2725                  array( $this, 'ucwordbreaksCallbackAscii' ),
2726                  $str
2727              );
2728          }
2729      }
2730  
2731      /**
2732       * Return a case-folded representation of $s
2733       *
2734       * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
2735       * and $s2 are the same except for the case of their characters. It is not
2736       * necessary for the value returned to make sense when displayed.
2737       *
2738       * Do *not* perform any other normalisation in this function. If a caller
2739       * uses this function when it should be using a more general normalisation
2740       * function, then fix the caller.
2741       *
2742       * @param string $s
2743       *
2744       * @return string
2745       */
2746  	function caseFold( $s ) {
2747          return $this->uc( $s );
2748      }
2749  
2750      /**
2751       * @param string $s
2752       * @return string
2753       */
2754  	function checkTitleEncoding( $s ) {
2755          if ( is_array( $s ) ) {
2756              throw new MWException( 'Given array to checkTitleEncoding.' );
2757          }
2758          if ( StringUtils::isUtf8( $s ) ) {
2759              return $s;
2760          }
2761  
2762          return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
2763      }
2764  
2765      /**
2766       * @return array
2767       */
2768  	function fallback8bitEncoding() {
2769          return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
2770      }
2771  
2772      /**
2773       * Most writing systems use whitespace to break up words.
2774       * Some languages such as Chinese don't conventionally do this,
2775       * which requires special handling when breaking up words for
2776       * searching etc.
2777       *
2778       * @return bool
2779       */
2780  	function hasWordBreaks() {
2781          return true;
2782      }
2783  
2784      /**
2785       * Some languages such as Chinese require word segmentation,
2786       * Specify such segmentation when overridden in derived class.
2787       *
2788       * @param string $string
2789       * @return string
2790       */
2791  	function segmentByWord( $string ) {
2792          return $string;
2793      }
2794  
2795      /**
2796       * Some languages have special punctuation need to be normalized.
2797       * Make such changes here.
2798       *
2799       * @param string $string
2800       * @return string
2801       */
2802  	function normalizeForSearch( $string ) {
2803          return self::convertDoubleWidth( $string );
2804      }
2805  
2806      /**
2807       * convert double-width roman characters to single-width.
2808       * range: ff00-ff5f ~= 0020-007f
2809       *
2810       * @param string $string
2811       *
2812       * @return string
2813       */
2814  	protected static function convertDoubleWidth( $string ) {
2815          static $full = null;
2816          static $half = null;
2817  
2818          if ( $full === null ) {
2819              $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2820              $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2821              $full = str_split( $fullWidth, 3 );
2822              $half = str_split( $halfWidth );
2823          }
2824  
2825          $string = str_replace( $full, $half, $string );
2826          return $string;
2827      }
2828  
2829      /**
2830       * @param string $string
2831       * @param string $pattern
2832       * @return string
2833       */
2834  	protected static function insertSpace( $string, $pattern ) {
2835          $string = preg_replace( $pattern, " $1 ", $string );
2836          $string = preg_replace( '/ +/', ' ', $string );
2837          return $string;
2838      }
2839  
2840      /**
2841       * @param array $termsArray
2842       * @return array
2843       */
2844  	function convertForSearchResult( $termsArray ) {
2845          # some languages, e.g. Chinese, need to do a conversion
2846          # in order for search results to be displayed correctly
2847          return $termsArray;
2848      }
2849  
2850      /**
2851       * Get the first character of a string.
2852       *
2853       * @param string $s
2854       * @return string
2855       */
2856  	function firstChar( $s ) {
2857          $matches = array();
2858          preg_match(
2859              '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2860                  '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2861              $s,
2862              $matches
2863          );
2864  
2865          if ( isset( $matches[1] ) ) {
2866              if ( strlen( $matches[1] ) != 3 ) {
2867                  return $matches[1];
2868              }
2869  
2870              // Break down Hangul syllables to grab the first jamo
2871              $code = utf8ToCodepoint( $matches[1] );
2872              if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2873                  return $matches[1];
2874              } elseif ( $code < 0xb098 ) {
2875                  return "\xe3\x84\xb1";
2876              } elseif ( $code < 0xb2e4 ) {
2877                  return "\xe3\x84\xb4";
2878              } elseif ( $code < 0xb77c ) {
2879                  return "\xe3\x84\xb7";
2880              } elseif ( $code < 0xb9c8 ) {
2881                  return "\xe3\x84\xb9";
2882              } elseif ( $code < 0xbc14 ) {
2883                  return "\xe3\x85\x81";
2884              } elseif ( $code < 0xc0ac ) {
2885                  return "\xe3\x85\x82";
2886              } elseif ( $code < 0xc544 ) {
2887                  return "\xe3\x85\x85";
2888              } elseif ( $code < 0xc790 ) {
2889                  return "\xe3\x85\x87";
2890              } elseif ( $code < 0xcc28 ) {
2891                  return "\xe3\x85\x88";
2892              } elseif ( $code < 0xce74 ) {
2893                  return "\xe3\x85\x8a";
2894              } elseif ( $code < 0xd0c0 ) {
2895                  return "\xe3\x85\x8b";
2896              } elseif ( $code < 0xd30c ) {
2897                  return "\xe3\x85\x8c";
2898              } elseif ( $code < 0xd558 ) {
2899                  return "\xe3\x85\x8d";
2900              } else {
2901                  return "\xe3\x85\x8e";
2902              }
2903          } else {
2904              return '';
2905          }
2906      }
2907  
2908  	function initEncoding() {
2909          # Some languages may have an alternate char encoding option
2910          # (Esperanto X-coding, Japanese furigana conversion, etc)
2911          # If this language is used as the primary content language,
2912          # an override to the defaults can be set here on startup.
2913      }
2914  
2915      /**
2916       * @param string $s
2917       * @return string
2918       */
2919  	function recodeForEdit( $s ) {
2920          # For some languages we'll want to explicitly specify
2921          # which characters make it into the edit box raw
2922          # or are converted in some way or another.
2923          global $wgEditEncoding;
2924          if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) {
2925              return $s;
2926          } else {
2927              return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
2928          }
2929      }
2930  
2931      /**
2932       * @param string $s
2933       * @return string
2934       */
2935  	function recodeInput( $s ) {
2936          # Take the previous into account.
2937          global $wgEditEncoding;
2938          if ( $wgEditEncoding != '' ) {
2939              $enc = $wgEditEncoding;
2940          } else {
2941              $enc = 'UTF-8';
2942          }
2943          if ( $enc == 'UTF-8' ) {
2944              return $s;
2945          } else {
2946              return $this->iconv( $enc, 'UTF-8', $s );
2947          }
2948      }
2949  
2950      /**
2951       * Convert a UTF-8 string to normal form C. In Malayalam and Arabic, this
2952       * also cleans up certain backwards-compatible sequences, converting them
2953       * to the modern Unicode equivalent.
2954       *
2955       * This is language-specific for performance reasons only.
2956       *
2957       * @param string $s
2958       *
2959       * @return string
2960       */
2961  	function normalize( $s ) {
2962          global $wgAllUnicodeFixes;
2963          $s = UtfNormal::cleanUp( $s );
2964          if ( $wgAllUnicodeFixes ) {
2965              $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
2966              $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
2967          }
2968  
2969          return $s;
2970      }
2971  
2972      /**
2973       * Transform a string using serialized data stored in the given file (which
2974       * must be in the serialized subdirectory of $IP). The file contains pairs
2975       * mapping source characters to destination characters.
2976       *
2977       * The data is cached in process memory. This will go faster if you have the
2978       * FastStringSearch extension.
2979       *
2980       * @param string $file
2981       * @param string $string
2982       *
2983       * @throws MWException
2984       * @return string
2985       */
2986  	function transformUsingPairFile( $file, $string ) {
2987          if ( !isset( $this->transformData[$file] ) ) {
2988              $data = wfGetPrecompiledData( $file );
2989              if ( $data === false ) {
2990                  throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
2991              }
2992              $this->transformData[$file] = new ReplacementArray( $data );
2993          }
2994          return $this->transformData[$file]->replace( $string );
2995      }
2996  
2997      /**
2998       * For right-to-left language support
2999       *
3000       * @return bool
3001       */
3002  	function isRTL() {
3003          return self::$dataCache->getItem( $this->mCode, 'rtl' );
3004      }
3005  
3006      /**
3007       * Return the correct HTML 'dir' attribute value for this language.
3008       * @return string
3009       */
3010  	function getDir() {
3011          return $this->isRTL() ? 'rtl' : 'ltr';
3012      }
3013  
3014      /**
3015       * Return 'left' or 'right' as appropriate alignment for line-start
3016       * for this language's text direction.
3017       *
3018       * Should be equivalent to CSS3 'start' text-align value....
3019       *
3020       * @return string
3021       */
3022  	function alignStart() {
3023          return $this->isRTL() ? 'right' : 'left';
3024      }
3025  
3026      /**
3027       * Return 'right' or 'left' as appropriate alignment for line-end
3028       * for this language's text direction.
3029       *
3030       * Should be equivalent to CSS3 'end' text-align value....
3031       *
3032       * @return string
3033       */
3034  	function alignEnd() {
3035          return $this->isRTL() ? 'left' : 'right';
3036      }
3037  
3038      /**
3039       * A hidden direction mark (LRM or RLM), depending on the language direction.
3040       * Unlike getDirMark(), this function returns the character as an HTML entity.
3041       * This function should be used when the output is guaranteed to be HTML,
3042       * because it makes the output HTML source code more readable. When
3043       * the output is plain text or can be escaped, getDirMark() should be used.
3044       *
3045       * @param bool $opposite Get the direction mark opposite to your language
3046       * @return string
3047       * @since 1.20
3048       */
3049  	function getDirMarkEntity( $opposite = false ) {
3050          if ( $opposite ) {
3051              return $this->isRTL() ? '&lrm;' : '&rlm;';
3052          }
3053          return $this->isRTL() ? '&rlm;' : '&lrm;';
3054      }
3055  
3056      /**
3057       * A hidden direction mark (LRM or RLM), depending on the language direction.
3058       * This function produces them as invisible Unicode characters and
3059       * the output may be hard to read and debug, so it should only be used
3060       * when the output is plain text or can be escaped. When the output is
3061       * HTML, use getDirMarkEntity() instead.
3062       *
3063       * @param bool $opposite Get the direction mark opposite to your language
3064       * @return string
3065       */
3066  	function getDirMark( $opposite = false ) {
3067          $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
3068          $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
3069          if ( $opposite ) {
3070              return $this->isRTL() ? $lrm : $rlm;
3071          }
3072          return $this->isRTL() ? $rlm : $lrm;
3073      }
3074  
3075      /**
3076       * @return array
3077       */
3078  	function capitalizeAllNouns() {
3079          return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
3080      }
3081  
3082      /**
3083       * An arrow, depending on the language direction.
3084       *
3085       * @param string $direction The direction of the arrow: forwards (default),
3086       *   backwards, left, right, up, down.
3087       * @return string
3088       */
3089  	function getArrow( $direction = 'forwards' ) {
3090          switch ( $direction ) {
3091          case 'forwards':
3092              return $this->isRTL() ? '←' : '→';
3093          case 'backwards':
3094              return $this->isRTL() ? '→' : '←';
3095          case 'left':
3096              return '←';
3097          case 'right':
3098              return '→';
3099          case 'up':
3100              return '↑';
3101          case 'down':
3102              return '↓';
3103          }
3104      }
3105  
3106      /**
3107       * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
3108       *
3109       * @return bool
3110       */
3111  	function linkPrefixExtension() {
3112          return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
3113      }
3114  
3115      /**
3116       * Get all magic words from cache.
3117       * @return array
3118       */
3119  	function getMagicWords() {
3120          return self::$dataCache->getItem( $this->mCode, 'magicWords' );
3121      }
3122  
3123      /**
3124       * Run the LanguageGetMagic hook once.
3125       */
3126  	protected function doMagicHook() {
3127          if ( $this->mMagicHookDone ) {
3128              return;
3129          }
3130          $this->mMagicHookDone = true;
3131          wfProfileIn( 'LanguageGetMagic' );
3132          wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
3133          wfProfileOut( 'LanguageGetMagic' );
3134      }
3135  
3136      /**
3137       * Fill a MagicWord object with data from here
3138       *
3139       * @param MagicWord $mw
3140       */
3141  	function getMagic( $mw ) {
3142          // Saves a function call
3143          if ( !$this->mMagicHookDone ) {
3144              $this->doMagicHook();
3145          }
3146  
3147          if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3148              $rawEntry = $this->mMagicExtensions[$mw->mId];
3149          } else {
3150              $rawEntry = self::$dataCache->getSubitem(
3151                  $this->mCode, 'magicWords', $mw->mId );
3152          }
3153  
3154          if ( !is_array( $rawEntry ) ) {
3155              wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3156          } else {
3157              $mw->mCaseSensitive = $rawEntry[0];
3158              $mw->mSynonyms = array_slice( $rawEntry, 1 );
3159          }
3160      }
3161  
3162      /**
3163       * Add magic words to the extension array
3164       *
3165       * @param array $newWords
3166       */
3167  	function addMagicWordsByLang( $newWords ) {
3168          $fallbackChain = $this->getFallbackLanguages();
3169          $fallbackChain = array_reverse( $fallbackChain );
3170          foreach ( $fallbackChain as $code ) {
3171              if ( isset( $newWords[$code] ) ) {
3172                  $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
3173              }
3174          }
3175      }
3176  
3177      /**
3178       * Get special page names, as an associative array
3179       *   case folded alias => real name
3180       * @return array
3181       */
3182  	function getSpecialPageAliases() {
3183          // Cache aliases because it may be slow to load them
3184          if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3185              // Initialise array
3186              $this->mExtendedSpecialPageAliases =
3187                  self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
3188              wfRunHooks( 'LanguageGetSpecialPageAliases',
3189                  array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
3190          }
3191  
3192          return $this->mExtendedSpecialPageAliases;
3193      }
3194  
3195      /**
3196       * Italic is unsuitable for some languages
3197       *
3198       * @param string $text The text to be emphasized.
3199       * @return string
3200       */
3201  	function emphasize( $text ) {
3202          return "<em>$text</em>";
3203      }
3204  
3205      /**
3206       * Normally we output all numbers in plain en_US style, that is
3207       * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
3208       * point twohundredthirtyfive. However this is not suitable for all
3209       * languages, some such as Punjabi want ੨੯੩,੨੯੫.੨੩੫ and others such as
3210       * Icelandic just want to use commas instead of dots, and dots instead
3211       * of commas like "293.291,235".
3212       *
3213       * An example of this function being called:
3214       * <code>
3215       * wfMessage( 'message' )->numParams( $num )->text()
3216       * </code>
3217       *
3218       * See $separatorTransformTable on MessageIs.php for
3219       * the , => . and . => , implementation.
3220       *
3221       * @todo check if it's viable to use localeconv() for the decimal separator thing.
3222       * @param int|float $number The string to be formatted, should be an integer
3223       *   or a floating point number.
3224       * @param bool $nocommafy Set to true for special numbers like dates
3225       * @return string
3226       */
3227  	public function formatNum( $number, $nocommafy = false ) {
3228          global $wgTranslateNumerals;
3229          if ( !$nocommafy ) {
3230              $number = $this->commafy( $number );
3231              $s = $this->separatorTransformTable();
3232              if ( $s ) {
3233                  $number = strtr( $number, $s );
3234              }
3235          }
3236  
3237          if ( $wgTranslateNumerals ) {
3238              $s = $this->digitTransformTable();
3239              if ( $s ) {
3240                  $number = strtr( $number, $s );
3241              }
3242          }
3243  
3244          return $number;
3245      }
3246  
3247      /**
3248       * Front-end for non-commafied formatNum
3249       *
3250       * @param int|float $number The string to be formatted, should be an integer
3251       *        or a floating point number.
3252       * @since 1.21
3253       * @return string
3254       */
3255  	public function formatNumNoSeparators( $number ) {
3256          return $this->formatNum( $number, true );
3257      }
3258  
3259      /**
3260       * @param string $number
3261       * @return string
3262       */
3263  	public function parseFormattedNumber( $number ) {
3264          $s = $this->digitTransformTable();
3265          if ( $s ) {
3266              // eliminate empty array values such as ''. (bug 64347)
3267              $s = array_filter( $s );
3268              $number = strtr( $number, array_flip( $s ) );
3269          }
3270  
3271          $s = $this->separatorTransformTable();
3272          if ( $s ) {
3273              // eliminate empty array values such as ''. (bug 64347)
3274              $s = array_filter( $s );
3275              $number = strtr( $number, array_flip( $s ) );
3276          }
3277  
3278          $number = strtr( $number, array( ',' => '' ) );
3279          return $number;
3280      }
3281  
3282      /**
3283       * Adds commas to a given number
3284       * @since 1.19
3285       * @param mixed $number
3286       * @return string
3287       */
3288  	function commafy( $number ) {
3289          $digitGroupingPattern = $this->digitGroupingPattern();
3290          if ( $number === null ) {
3291              return '';
3292          }
3293  
3294          if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
3295              // default grouping is at thousands,  use the same for ###,###,### pattern too.
3296              return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
3297          } else {
3298              // Ref: http://cldr.unicode.org/translation/number-patterns
3299              $sign = "";
3300              if ( intval( $number ) < 0 ) {
3301                  // For negative numbers apply the algorithm like positive number and add sign.
3302                  $sign = "-";
3303                  $number = substr( $number, 1 );
3304              }
3305              $integerPart = array();
3306              $decimalPart = array();
3307              $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
3308              preg_match( "/\d+/", $number, $integerPart );
3309              preg_match( "/\.\d*/", $number, $decimalPart );
3310              $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
3311              if ( $groupedNumber === $number ) {
3312                  // the string does not have any number part. Eg: .12345
3313                  return $sign . $groupedNumber;
3314              }
3315              $start = $end = strlen( $integerPart[0] );
3316              while ( $start > 0 ) {
3317                  $match = $matches[0][$numMatches - 1];
3318                  $matchLen = strlen( $match );
3319                  $start = $end - $matchLen;
3320                  if ( $start < 0 ) {
3321                      $start = 0;
3322                  }
3323                  $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3324                  $end = $start;
3325                  if ( $numMatches > 1 ) {
3326                      // use the last pattern for the rest of the number
3327                      $numMatches--;
3328                  }
3329                  if ( $start > 0 ) {
3330                      $groupedNumber = "," . $groupedNumber;
3331                  }
3332              }
3333              return $sign . $groupedNumber;
3334          }
3335      }
3336  
3337      /**
3338       * @return string
3339       */
3340  	function digitGroupingPattern() {
3341          return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
3342      }
3343  
3344      /**
3345       * @return array
3346       */
3347  	function digitTransformTable() {
3348          return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
3349      }
3350  
3351      /**
3352       * @return array
3353       */
3354  	function separatorTransformTable() {
3355          return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
3356      }
3357  
3358      /**
3359       * Take a list of strings and build a locale-friendly comma-separated
3360       * list, using the local comma-separator message.
3361       * The last two strings are chained with an "and".
3362       * NOTE: This function will only work with standard numeric array keys (0, 1, 2…)
3363       *
3364       * @param string[] $l
3365       * @return string
3366       */
3367  	function listToText( array $l ) {
3368          $m = count( $l ) - 1;
3369          if ( $m < 0 ) {
3370              return '';
3371          }
3372          if ( $m > 0 ) {
3373              $and = $this->getMessageFromDB( 'and' );
3374              $space = $this->getMessageFromDB( 'word-separator' );
3375              if ( $m > 1 ) {
3376                  $comma = $this->getMessageFromDB( 'comma-separator' );
3377              }
3378          }
3379          $s = $l[$m];
3380          for ( $i = $m - 1; $i >= 0; $i-- ) {
3381              if ( $i == $m - 1 ) {
3382                  $s = $l[$i] . $and . $space . $s;
3383              } else {
3384                  $s = $l[$i] . $comma . $s;
3385              }
3386          }
3387          return $s;
3388      }
3389  
3390      /**
3391       * Take a list of strings and build a locale-friendly comma-separated
3392       * list, using the local comma-separator message.
3393       * @param string[] $list Array of strings to put in a comma list
3394       * @return string
3395       */
3396  	function commaList( array $list ) {
3397          return implode(
3398              wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
3399              $list
3400          );
3401      }
3402  
3403      /**
3404       * Take a list of strings and build a locale-friendly semicolon-separated
3405       * list, using the local semicolon-separator message.
3406       * @param string[] $list Array of strings to put in a semicolon list
3407       * @return string
3408       */
3409  	function semicolonList( array $list ) {
3410          return implode(
3411              wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
3412              $list
3413          );
3414      }
3415  
3416      /**
3417       * Same as commaList, but separate it with the pipe instead.
3418       * @param string[] $list Array of strings to put in a pipe list
3419       * @return string
3420       */
3421  	function pipeList( array $list ) {
3422          return implode(
3423              wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
3424              $list
3425          );
3426      }
3427  
3428      /**
3429       * Truncate a string to a specified length in bytes, appending an optional
3430       * string (e.g. for ellipses)
3431       *
3432       * The database offers limited byte lengths for some columns in the database;
3433       * multi-byte character sets mean we need to ensure that only whole characters
3434       * are included, otherwise broken characters can be passed to the user
3435       *
3436       * If $length is negative, the string will be truncated from the beginning
3437       *
3438       * @param string $string String to truncate
3439       * @param int $length Maximum length (including ellipses)
3440       * @param string $ellipsis String to append to the truncated text
3441       * @param bool $adjustLength Subtract length of ellipsis from $length.
3442       *    $adjustLength was introduced in 1.18, before that behaved as if false.
3443       * @return string
3444       */
3445  	function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
3446          # Use the localized ellipsis character
3447          if ( $ellipsis == '...' ) {
3448              $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3449          }
3450          # Check if there is no need to truncate
3451          if ( $length == 0 ) {
3452              return $ellipsis; // convention
3453          } elseif ( strlen( $string ) <= abs( $length ) ) {
3454              return $string; // no need to truncate
3455          }
3456          $stringOriginal = $string;
3457          # If ellipsis length is >= $length then we can't apply $adjustLength
3458          if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3459              $string = $ellipsis; // this can be slightly unexpected
3460          # Otherwise, truncate and add ellipsis...
3461          } else {
3462              $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3463              if ( $length > 0 ) {
3464                  $length -= $eLength;
3465                  $string = substr( $string, 0, $length ); // xyz...
3466                  $string = $this->removeBadCharLast( $string );
3467                  $string = rtrim( $string );
3468                  $string = $string . $ellipsis;
3469              } else {
3470                  $length += $eLength;
3471                  $string = substr( $string, $length ); // ...xyz
3472                  $string = $this->removeBadCharFirst( $string );
3473                  $string = ltrim( $string );
3474                  $string = $ellipsis . $string;
3475              }
3476          }
3477          # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
3478          # This check is *not* redundant if $adjustLength, due to the single case where
3479          # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3480          if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3481              return $string;
3482          } else {
3483              return $stringOriginal;
3484          }
3485      }
3486  
3487      /**
3488       * Remove bytes that represent an incomplete Unicode character
3489       * at the end of string (e.g. bytes of the char are missing)
3490       *
3491       * @param string $string
3492       * @return string
3493       */
3494  	protected function removeBadCharLast( $string ) {
3495          if ( $string != '' ) {
3496              $char = ord( $string[strlen( $string ) - 1] );
3497              $m = array();
3498              if ( $char >= 0xc0 ) {
3499                  # We got the first byte only of a multibyte char; remove it.
3500                  $string = substr( $string, 0, -1 );
3501              } elseif ( $char >= 0x80 &&
3502                  preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3503                      '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m )
3504              ) {
3505                  # We chopped in the middle of a character; remove it
3506                  $string = $m[1];
3507              }
3508          }
3509          return $string;
3510      }
3511  
3512      /**
3513       * Remove bytes that represent an incomplete Unicode character
3514       * at the start of string (e.g. bytes of the char are missing)
3515       *
3516       * @param string $string
3517       * @return string
3518       */
3519  	protected function removeBadCharFirst( $string ) {
3520          if ( $string != '' ) {
3521              $char = ord( $string[0] );
3522              if ( $char >= 0x80 && $char < 0xc0 ) {
3523                  # We chopped in the middle of a character; remove the whole thing
3524                  $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
3525              }
3526          }
3527          return $string;
3528      }
3529  
3530      /**
3531       * Truncate a string of valid HTML to a specified length in bytes,
3532       * appending an optional string (e.g. for ellipses), and return valid HTML
3533       *
3534       * This is only intended for styled/linked text, such as HTML with
3535       * tags like <span> and <a>, were the tags are self-contained (valid HTML).
3536       * Also, this will not detect things like "display:none" CSS.
3537       *
3538       * Note: since 1.18 you do not need to leave extra room in $length for ellipses.
3539       *
3540       * @param string $text HTML string to truncate
3541       * @param int $length (zero/positive) Maximum length (including ellipses)
3542       * @param string $ellipsis String to append to the truncated text
3543       * @return string
3544       */
3545  	function truncateHtml( $text, $length, $ellipsis = '...' ) {
3546          # Use the localized ellipsis character
3547          if ( $ellipsis == '...' ) {
3548              $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
3549          }
3550          # Check if there is clearly no need to truncate
3551          if ( $length <= 0 ) {
3552              return $ellipsis; // no text shown, nothing to format (convention)
3553          } elseif ( strlen( $text ) <= $length ) {
3554              return $text; // string short enough even *with* HTML (short-circuit)
3555          }
3556  
3557          $dispLen = 0; // innerHTML legth so far
3558          $testingEllipsis = false; // checking if ellipses will make string longer/equal?
3559          $tagType = 0; // 0-open, 1-close
3560          $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
3561          $entityState = 0; // 0-not entity, 1-entity
3562          $tag = $ret = ''; // accumulated tag name, accumulated result string
3563          $openTags = array(); // open tag stack
3564          $maybeState = null; // possible truncation state
3565  
3566          $textLen = strlen( $text );
3567          $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
3568          for ( $pos = 0; true; ++$pos ) {
3569              # Consider truncation once the display length has reached the maximim.
3570              # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3571              # Check that we're not in the middle of a bracket/entity...
3572              if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3573                  if ( !$testingEllipsis ) {
3574                      $testingEllipsis = true;
3575                      # Save where we are; we will truncate here unless there turn out to
3576                      # be so few remaining characters that truncation is not necessary.
3577                      if ( !$maybeState ) { // already saved? ($neLength = 0 case)
3578                          $maybeState = array( $ret, $openTags ); // save state
3579                      }
3580                  } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3581                      # String in fact does need truncation, the truncation point was OK.
3582                      list( $ret, $openTags ) = $maybeState; // reload state
3583                      $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
3584                      $ret .= $ellipsis; // add ellipsis
3585                      break;
3586                  }
3587              }
3588              if ( $pos >= $textLen ) {
3589                  break; // extra iteration just for above checks
3590              }
3591  
3592              # Read the next char...
3593              $ch = $text[$pos];
3594              $lastCh = $pos ? $text[$pos - 1] : '';
3595              $ret .= $ch; // add to result string
3596              if ( $ch == '<' ) {
3597                  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
3598                  $entityState = 0; // for bad HTML
3599                  $bracketState = 1; // tag started (checking for backslash)
3600              } elseif ( $ch == '>' ) {
3601                  $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
3602                  $entityState = 0; // for bad HTML
3603                  $bracketState = 0; // out of brackets
3604              } elseif ( $bracketState == 1 ) {
3605                  if ( $ch == '/' ) {
3606                      $tagType = 1; // close tag (e.g. "</span>")
3607                  } else {
3608                      $tagType = 0; // open tag (e.g. "<span>")
3609                      $tag .= $ch;
3610                  }
3611                  $bracketState = 2; // building tag name
3612              } elseif ( $bracketState == 2 ) {
3613                  if ( $ch != ' ' ) {
3614                      $tag .= $ch;
3615                  } else {
3616                      // Name found (e.g. "<a href=..."), add on tag attributes...
3617                      $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
3618                  }
3619              } elseif ( $bracketState == 0 ) {
3620                  if ( $entityState ) {
3621                      if ( $ch == ';' ) {
3622                          $entityState = 0;
3623                          $dispLen++; // entity is one displayed char
3624                      }
3625                  } else {
3626                      if ( $neLength == 0 && !$maybeState ) {
3627                          // Save state without $ch. We want to *hit* the first
3628                          // display char (to get tags) but not *use* it if truncating.
3629                          $maybeState = array( substr( $ret, 0, -1 ), $openTags );
3630                      }
3631                      if ( $ch == '&' ) {
3632                          $entityState = 1; // entity found, (e.g. "&#160;")
3633                      } else {
3634                          $dispLen++; // this char is displayed
3635                          // Add the next $max display text chars after this in one swoop...
3636                          $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3637                          $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
3638                          $dispLen += $skipped;
3639                          $pos += $skipped;
3640                      }
3641                  }
3642              }
3643          }
3644          // Close the last tag if left unclosed by bad HTML
3645          $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
3646          while ( count( $openTags ) > 0 ) {
3647              $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
3648          }
3649          return $ret;
3650      }
3651  
3652      /**
3653       * truncateHtml() helper function
3654       * like strcspn() but adds the skipped chars to $ret
3655       *
3656       * @param string $ret
3657       * @param string $text
3658       * @param string $search
3659       * @param int $start
3660       * @param null|int $len
3661       * @return int
3662       */
3663  	private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
3664          if ( $len === null ) {
3665              $len = -1; // -1 means "no limit" for strcspn
3666          } elseif ( $len < 0 ) {
3667              $len = 0; // sanity
3668          }
3669          $skipCount = 0;
3670          if ( $start < strlen( $text ) ) {
3671              $skipCount = strcspn( $text, $search, $start, $len );
3672              $ret .= substr( $text, $start, $skipCount );
3673          }
3674          return $skipCount;
3675      }
3676  
3677      /**
3678       * truncateHtml() helper function
3679       * (a) push or pop $tag from $openTags as needed
3680       * (b) clear $tag value
3681       * @param string &$tag Current HTML tag name we are looking at
3682       * @param int $tagType (0-open tag, 1-close tag)
3683       * @param string $lastCh Character before the '>' that ended this tag
3684       * @param array &$openTags Open tag stack (not accounting for $tag)
3685       */
3686  	private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
3687          $tag = ltrim( $tag );
3688          if ( $tag != '' ) {
3689              if ( $tagType == 0 && $lastCh != '/' ) {
3690                  $openTags[] = $tag; // tag opened (didn't close itself)
3691              } elseif ( $tagType == 1 ) {
3692                  if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3693                      array_pop( $openTags ); // tag closed
3694                  }
3695              }
3696              $tag = '';
3697          }
3698      }
3699  
3700      /**
3701       * Grammatical transformations, needed for inflected languages
3702       * Invoked by putting {{grammar:case|word}} in a message
3703       *
3704       * @param string $word
3705       * @param string $case
3706       * @return string
3707       */
3708  	function convertGrammar( $word, $case ) {
3709          global $wgGrammarForms;
3710          if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
3711              return $wgGrammarForms[$this->getCode()][$case][$word];
3712          }
3713  
3714          return $word;
3715      }
3716      /**
3717       * Get the grammar forms for the content language
3718       * @return array Array of grammar forms
3719       * @since 1.20
3720       */
3721  	function getGrammarForms() {
3722          global $wgGrammarForms;
3723          if ( isset( $wgGrammarForms[$this->getCode()] )
3724              && is_array( $wgGrammarForms[$this->getCode()] )
3725          ) {
3726              return $wgGrammarForms[$this->getCode()];
3727          }
3728  
3729          return array();
3730      }
3731      /**
3732       * Provides an alternative text depending on specified gender.
3733       * Usage {{gender:username|masculine|feminine|unknown}}.
3734       * username is optional, in which case the gender of current user is used,
3735       * but only in (some) interface messages; otherwise default gender is used.
3736       *
3737       * If no forms are given, an empty string is returned. If only one form is
3738       * given, it will be returned unconditionally. These details are implied by
3739       * the caller and cannot be overridden in subclasses.
3740       *
3741       * If three forms are given, the default is to use the third (unknown) form.
3742       * If fewer than three forms are given, the default is to use the first (masculine) form.
3743       * These details can be overridden in subclasses.
3744       *
3745       * @param string $gender
3746       * @param array $forms
3747       *
3748       * @return string
3749       */
3750  	function gender( $gender, $forms ) {
3751          if ( !count( $forms ) ) {
3752              return '';
3753          }
3754          $forms = $this->preConvertPlural( $forms, 2 );
3755          if ( $gender === 'male' ) {
3756              return $forms[0];
3757          }
3758          if ( $gender === 'female' ) {
3759              return $forms[1];
3760          }
3761          return isset( $forms[2] ) ? $forms[2] : $forms[0];
3762      }
3763  
3764      /**
3765       * Plural form transformations, needed for some languages.
3766       * For example, there are 3 form of plural in Russian and Polish,
3767       * depending on "count mod 10". See [[w:Plural]]
3768       * For English it is pretty simple.
3769       *
3770       * Invoked by putting {{plural:count|wordform1|wordform2}}
3771       * or {{plural:count|wordform1|wordform2|wordform3}}
3772       *
3773       * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
3774       *
3775       * @param int $count Non-localized number
3776       * @param array $forms Different plural forms
3777       * @return string Correct form of plural for $count in this language
3778       */
3779  	function convertPlural( $count, $forms ) {
3780          // Handle explicit n=pluralform cases
3781          $forms = $this->handleExplicitPluralForms( $count, $forms );
3782          if ( is_string( $forms ) ) {
3783              return $forms;
3784          }
3785          if ( !count( $forms ) ) {
3786              return '';
3787          }
3788  
3789          $pluralForm = $this->getPluralRuleIndexNumber( $count );
3790          $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3791          return $forms[$pluralForm];
3792      }
3793  
3794      /**
3795       * Handles explicit plural forms for Language::convertPlural()
3796       *
3797       * In {{PLURAL:$1|0=nothing|one|many}}, 0=nothing will be returned if $1 equals zero.
3798       * If an explicitly defined plural form matches the $count, then
3799       * string value returned, otherwise array returned for further consideration
3800       * by CLDR rules or overridden convertPlural().
3801       *
3802       * @since 1.23
3803       *
3804       * @param int $count Non-localized number
3805       * @param array $forms Different plural forms
3806       *
3807       * @return array|string
3808       */
3809  	protected function handleExplicitPluralForms( $count, array $forms ) {
3810          foreach ( $forms as $index => $form ) {
3811              if ( preg_match( '/\d+=/i', $form ) ) {
3812                  $pos = strpos( $form, '=' );
3813                  if ( substr( $form, 0, $pos ) === (string)$count ) {
3814                      return substr( $form, $pos + 1 );
3815                  }
3816                  unset( $forms[$index] );
3817              }
3818          }
3819          return array_values( $forms );
3820      }
3821  
3822      /**
3823       * Checks that convertPlural was given an array and pads it to requested
3824       * amount of forms by copying the last one.
3825       *
3826       * @param array $forms Array of forms given to convertPlural
3827       * @param int $count How many forms should there be at least
3828       * @return array Padded array of forms or an exception if not an array
3829       */
3830  	protected function preConvertPlural( /* Array */ $forms, $count ) {
3831          while ( count( $forms ) < $count ) {
3832              $forms[] = $forms[count( $forms ) - 1];
3833          }
3834          return $forms;
3835      }
3836  
3837      /**
3838       * @todo Maybe translate block durations.  Note that this function is somewhat misnamed: it
3839       * deals with translating the *duration* ("1 week", "4 days", etc), not the expiry time
3840       * (which is an absolute timestamp). Please note: do NOT add this blindly, as it is used
3841       * on old expiry lengths recorded in log entries. You'd need to provide the start date to
3842       * match up with it.
3843       *
3844       * @param string $str The validated block duration in English
3845       * @return string Somehow translated block duration
3846       * @see LanguageFi.php for example implementation
3847       */
3848  	function translateBlockExpiry( $str ) {
3849          $duration = SpecialBlock::getSuggestedDurations( $this );
3850          foreach ( $duration as $show => $value ) {
3851              if ( strcmp( $str, $value ) == 0 ) {
3852                  return htmlspecialchars( trim( $show ) );
3853              }
3854          }
3855  
3856          // Since usually only infinite or indefinite is only on list, so try
3857          // equivalents if still here.
3858          $indefs = array( 'infinite', 'infinity', 'indefinite' );
3859          if ( in_array( $str, $indefs ) ) {
3860              foreach ( $indefs as $val ) {
3861                  $show = array_search( $val, $duration, true );
3862                  if ( $show !== false ) {
3863                      return htmlspecialchars( trim( $show ) );
3864                  }
3865              }
3866          }
3867  
3868          // If all else fails, return a standard duration or timestamp description.
3869          $time = strtotime( $str, 0 );
3870          if ( $time === false ) { // Unknown format. Return it as-is in case.
3871              return $str;
3872          } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp.
3873              // $time is relative to 0 so it's a duration length.
3874              return $this->formatDuration( $time );
3875          } else { // It's an absolute timestamp.
3876              if ( $time === 0 ) {
3877                  // wfTimestamp() handles 0 as current time instead of epoch.
3878                  return $this->timeanddate( '19700101000000' );
3879              } else {
3880                  return $this->timeanddate( $time );
3881              }
3882          }
3883      }
3884  
3885      /**
3886       * languages like Chinese need to be segmented in order for the diff
3887       * to be of any use
3888       *
3889       * @param string $text
3890       * @return string
3891       */
3892  	public function segmentForDiff( $text ) {
3893          return $text;
3894      }
3895  
3896      /**
3897       * and unsegment to show the result
3898       *
3899       * @param string $text
3900       * @return string
3901       */
3902  	public function unsegmentForDiff( $text ) {
3903          return $text;
3904      }
3905  
3906      /**
3907       * Return the LanguageConverter used in the Language
3908       *
3909       * @since 1.19
3910       * @return LanguageConverter
3911       */
3912  	public function getConverter() {
3913          return $this->mConverter;
3914      }
3915  
3916      /**
3917       * convert text to all supported variants
3918       *
3919       * @param string $text
3920       * @return array
3921       */
3922  	public function autoConvertToAllVariants( $text ) {
3923          return $this->mConverter->autoConvertToAllVariants( $text );
3924      }
3925  
3926      /**
3927       * convert text to different variants of a language.
3928       *
3929       * @param string $text
3930       * @return string
3931       */
3932  	public function convert( $text ) {
3933          return $this->mConverter->convert( $text );
3934      }
3935  
3936      /**
3937       * Convert a Title object to a string in the preferred variant
3938       *
3939       * @param Title $title
3940       * @return string
3941       */
3942  	public function convertTitle( $title ) {
3943          return $this->mConverter->convertTitle( $title );
3944      }
3945  
3946      /**
3947       * Convert a namespace index to a string in the preferred variant
3948       *
3949       * @param int $ns
3950       * @return string
3951       */
3952  	public function convertNamespace( $ns ) {
3953          return $this->mConverter->convertNamespace( $ns );
3954      }
3955  
3956      /**
3957       * Check if this is a language with variants
3958       *
3959       * @return bool
3960       */
3961  	public function hasVariants() {
3962          return count( $this->getVariants() ) > 1;
3963      }
3964  
3965      /**
3966       * Check if the language has the specific variant
3967       *
3968       * @since 1.19
3969       * @param string $variant
3970       * @return bool
3971       */
3972  	public function hasVariant( $variant ) {
3973          return (bool)$this->mConverter->validateVariant( $variant );
3974      }
3975  
3976      /**
3977       * Put custom tags (e.g. -{ }-) around math to prevent conversion
3978       *
3979       * @param string $text
3980       * @return string
3981       * @deprecated since 1.22 is no longer used
3982       */
3983  	public function armourMath( $text ) {
3984          return $this->mConverter->armourMath( $text );
3985      }
3986  
3987      /**
3988       * Perform output conversion on a string, and encode for safe HTML output.
3989       * @param string $text Text to be converted
3990       * @param bool $isTitle Whether this conversion is for the article title
3991       * @return string
3992       * @todo this should get integrated somewhere sane
3993       */
3994  	public function convertHtml( $text, $isTitle = false ) {
3995          return htmlspecialchars( $this->convert( $text, $isTitle ) );
3996      }
3997  
3998      /**
3999       * @param string $key
4000       * @return string
4001       */
4002  	public function convertCategoryKey( $key ) {
4003          return $this->mConverter->convertCategoryKey( $key );
4004      }
4005  
4006      /**
4007       * Get the list of variants supported by this language
4008       * see sample implementation in LanguageZh.php
4009       *
4010       * @return array An array of language codes
4011       */
4012  	public function getVariants() {
4013          return $this->mConverter->getVariants();
4014      }
4015  
4016      /**
4017       * @return string
4018       */
4019  	public function getPreferredVariant() {
4020          return $this->mConverter->getPreferredVariant();
4021      }
4022  
4023      /**
4024       * @return string
4025       */
4026  	public function getDefaultVariant() {
4027          return $this->mConverter->getDefaultVariant();
4028      }
4029  
4030      /**
4031       * @return string
4032       */
4033  	public function getURLVariant() {
4034          return $this->mConverter->getURLVariant();
4035      }
4036  
4037      /**
4038       * If a language supports multiple variants, it is
4039       * possible that non-existing link in one variant
4040       * actually exists in another variant. this function
4041       * tries to find it. See e.g. LanguageZh.php
4042       * The input parameters may be modified upon return
4043       *
4044       * @param string &$link The name of the link
4045       * @param Title &$nt The title object of the link
4046       * @param bool $ignoreOtherCond To disable other conditions when
4047       *   we need to transclude a template or update a category's link
4048       */
4049  	public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
4050          $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
4051      }
4052  
4053      /**
4054       * returns language specific options used by User::getPageRenderHash()
4055       * for example, the preferred language variant
4056       *
4057       * @return string
4058       */
4059  	function getExtraHashOptions() {
4060          return $this->mConverter->getExtraHashOptions();
4061      }
4062  
4063      /**
4064       * For languages that support multiple variants, the title of an
4065       * article may be displayed differently in different variants. this
4066       * function returns the apporiate title defined in the body of the article.
4067       *
4068       * @return string
4069       */
4070  	public function getParsedTitle() {
4071          return $this->mConverter->getParsedTitle();
4072      }
4073  
4074      /**
4075       * Prepare external link text for conversion. When the text is
4076       * a URL, it shouldn't be converted, and it'll be wrapped in
4077       * the "raw" tag (-{R| }-) to prevent conversion.
4078       *
4079       * This function is called "markNoConversion" for historical
4080       * reasons.
4081       *
4082       * @param string $text Text to be used for external link
4083       * @param bool $noParse Wrap it without confirming it's a real URL first
4084       * @return string The tagged text
4085       */
4086  	public function markNoConversion( $text, $noParse = false ) {
4087          // Excluding protocal-relative URLs may avoid many false positives.
4088          if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
4089              return $this->mConverter->markNoConversion( $text );
4090          } else {
4091              return $text;
4092          }
4093      }
4094  
4095      /**
4096       * A regular expression to match legal word-trailing characters
4097       * which should be merged onto a link of the form [[foo]]bar.
4098       *
4099       * @return string
4100       */
4101  	public function linkTrail() {
4102          return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
4103      }
4104  
4105      /**
4106       * A regular expression character set to match legal word-prefixing
4107       * characters which should be merged onto a link of the form foo[[bar]].
4108       *
4109       * @return string
4110       */
4111  	public function linkPrefixCharset() {
4112          return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
4113      }
4114  
4115      /**
4116       * @deprecated since 1.24, will be removed in 1.25
4117       * @return Language
4118       */
4119  	function getLangObj() {
4120          wfDeprecated( __METHOD__, '1.24' );
4121          return $this;
4122      }
4123  
4124      /**
4125       * Get the "parent" language which has a converter to convert a "compatible" language
4126       * (in another variant) to this language (eg. zh for zh-cn, but not en for en-gb).
4127       *
4128       * @return Language|null
4129       * @since 1.22
4130       */
4131  	public function getParentLanguage() {
4132          if ( $this->mParentLanguage !== false ) {
4133              return $this->mParentLanguage;
4134          }
4135  
4136          $pieces = explode( '-', $this->getCode() );
4137          $code = $pieces[0];
4138          if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4139              $this->mParentLanguage = null;
4140              return null;
4141          }
4142          $lang = Language::factory( $code );
4143          if ( !$lang->hasVariant( $this->getCode() ) ) {
4144              $this->mParentLanguage = null;
4145              return null;
4146          }
4147  
4148          $this->mParentLanguage = $lang;
4149          return $lang;
4150      }
4151  
4152      /**
4153       * Get the RFC 3066 code for this language object
4154       *
4155       * NOTE: The return value of this function is NOT HTML-safe and must be escaped with
4156       * htmlspecialchars() or similar
4157       *
4158       * @return string
4159       */
4160  	public function getCode() {
4161          return $this->mCode;
4162      }
4163  
4164      /**
4165       * Get the code in Bcp47 format which we can use
4166       * inside of html lang="" tags.
4167       *
4168       * NOTE: The return value of this function is NOT HTML-safe and must be escaped with
4169       * htmlspecialchars() or similar.
4170       *
4171       * @since 1.19
4172       * @return string
4173       */
4174  	public function getHtmlCode() {
4175          if ( is_null( $this->mHtmlCode ) ) {
4176              $this->mHtmlCode = wfBCP47( $this->getCode() );
4177          }
4178          return $this->mHtmlCode;
4179      }
4180  
4181      /**
4182       * @param string $code
4183       */
4184  	public function setCode( $code ) {
4185          $this->mCode = $code;
4186          // Ensure we don't leave incorrect cached data lying around
4187          $this->mHtmlCode = null;
4188          $this->mParentLanguage = false;
4189      }
4190  
4191      /**
4192       * Get the name of a file for a certain language code
4193       * @param string $prefix Prepend this to the filename
4194       * @param string $code Language code
4195       * @param string $suffix Append this to the filename
4196       * @throws MWException
4197       * @return string $prefix . $mangledCode . $suffix
4198       */
4199  	public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
4200          if ( !self::isValidBuiltInCode( $code ) ) {
4201              throw new MWException( "Invalid language code \"$code\"" );
4202          }
4203  
4204          return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
4205      }
4206  
4207      /**
4208       * Get the language code from a file name. Inverse of getFileName()
4209       * @param string $filename $prefix . $languageCode . $suffix
4210       * @param string $prefix Prefix before the language code
4211       * @param string $suffix Suffix after the language code
4212       * @return string Language code, or false if $prefix or $suffix isn't found
4213       */
4214  	public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
4215          $m = null;
4216          preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
4217              preg_quote( $suffix, '/' ) . '/', $filename, $m );
4218          if ( !count( $m ) ) {
4219              return false;
4220          }
4221          return str_replace( '_', '-', strtolower( $m[1] ) );
4222      }
4223  
4224      /**
4225       * @param string $code
4226       * @return string
4227       */
4228  	public static function getMessagesFileName( $code ) {
4229          global $IP;
4230          $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
4231          wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) );
4232          return $file;
4233      }
4234  
4235      /**
4236       * @param string $code
4237       * @return string
4238       * @since 1.23
4239       */
4240  	public static function getJsonMessagesFileName( $code ) {
4241          global $IP;
4242  
4243          if ( !self::isValidBuiltInCode( $code ) ) {
4244              throw new MWException( "Invalid language code \"$code\"" );
4245          }
4246  
4247          return "$IP/languages/i18n/$code.json";
4248      }
4249  
4250      /**
4251       * @param string $code
4252       * @return string
4253       */
4254  	public static function getClassFileName( $code ) {
4255          global $IP;
4256          return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
4257      }
4258  
4259      /**
4260       * Get the first fallback for a given language.
4261       *
4262       * @param string $code
4263       *
4264       * @return bool|string
4265       */
4266  	public static function getFallbackFor( $code ) {
4267          if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4268              return false;
4269          } else {
4270              $fallbacks = self::getFallbacksFor( $code );
4271              $first = array_shift( $fallbacks );
4272              return $first;
4273          }
4274      }
4275  
4276      /**
4277       * Get the ordered list of fallback languages.
4278       *
4279       * @since 1.19
4280       * @param string $code Language code
4281       * @return array
4282       */
4283  	public static function getFallbacksFor( $code ) {
4284          if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
4285              return array();
4286          } else {
4287              $v = self::getLocalisationCache()->getItem( $code, 'fallback' );
4288              $v = array_map( 'trim', explode( ',', $v ) );
4289              if ( $v[count( $v ) - 1] !== 'en' ) {
4290                  $v[] = 'en';
4291              }
4292              return $v;
4293          }
4294      }
4295  
4296      /**
4297       * Get the ordered list of fallback languages, ending with the fallback
4298       * language chain for the site language.
4299       *
4300       * @since 1.22
4301       * @param string $code Language code
4302       * @return array Array( fallbacks, site fallbacks )
4303       */
4304  	public static function getFallbacksIncludingSiteLanguage( $code ) {
4305          global $wgLanguageCode;
4306  
4307          // Usually, we will only store a tiny number of fallback chains, so we
4308          // keep them in static memory.
4309          $cacheKey = "{$code}-{$wgLanguageCode}";
4310  
4311          if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4312              $fallbacks = self::getFallbacksFor( $code );
4313  
4314              // Append the site's fallback chain, including the site language itself
4315              $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
4316              array_unshift( $siteFallbacks, $wgLanguageCode );
4317  
4318              // Eliminate any languages already included in the chain
4319              $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4320  
4321              self::$fallbackLanguageCache[$cacheKey] = array( $fallbacks, $siteFallbacks );
4322          }
4323          return self::$fallbackLanguageCache[$cacheKey];
4324      }
4325  
4326      /**
4327       * Get all messages for a given language
4328       * WARNING: this may take a long time. If you just need all message *keys*
4329       * but need the *contents* of only a few messages, consider using getMessageKeysFor().
4330       *
4331       * @param string $code
4332       *
4333       * @return array
4334       */
4335  	public static function getMessagesFor( $code ) {
4336          return self::getLocalisationCache()->getItem( $code, 'messages' );
4337      }
4338  
4339      /**
4340       * Get a message for a given language
4341       *
4342       * @param string $key
4343       * @param string $code
4344       *
4345       * @return string
4346       */
4347  	public static function getMessageFor( $key, $code ) {
4348          return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
4349      }
4350  
4351      /**
4352       * Get all message keys for a given language. This is a faster alternative to
4353       * array_keys( Language::getMessagesFor( $code ) )
4354       *
4355       * @since 1.19
4356       * @param string $code Language code
4357       * @return array Array of message keys (strings)
4358       */
4359  	public static function getMessageKeysFor( $code ) {
4360          return self::getLocalisationCache()->getSubItemList( $code, 'messages' );
4361      }
4362  
4363      /**
4364       * @param string $talk
4365       * @return mixed
4366       */
4367  	function fixVariableInNamespace( $talk ) {
4368          if ( strpos( $talk, '$1' ) === false ) {
4369              return $talk;
4370          }
4371  
4372          global $wgMetaNamespace;
4373          $talk = str_replace( '$1', $wgMetaNamespace, $talk );
4374  
4375          # Allow grammar transformations
4376          # Allowing full message-style parsing would make simple requests
4377          # such as action=raw much more expensive than they need to be.
4378          # This will hopefully cover most cases.
4379          $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
4380              array( &$this, 'replaceGrammarInNamespace' ), $talk );
4381          return str_replace( ' ', '_', $talk );
4382      }
4383  
4384      /**
4385       * @param string $m
4386       * @return string
4387       */
4388  	function replaceGrammarInNamespace( $m ) {
4389          return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
4390      }
4391  
4392      /**
4393       * @throws MWException
4394       * @return array
4395       */
4396  	static function getCaseMaps() {
4397          static $wikiUpperChars, $wikiLowerChars;
4398          if ( isset( $wikiUpperChars ) ) {
4399              return array( $wikiUpperChars, $wikiLowerChars );
4400          }
4401  
4402          wfProfileIn( __METHOD__ );
4403          $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
4404          if ( $arr === false ) {
4405              throw new MWException(
4406                  "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
4407          }
4408          $wikiUpperChars = $arr['wikiUpperChars'];
4409          $wikiLowerChars = $arr['wikiLowerChars'];
4410          wfProfileOut( __METHOD__ );
4411          return array( $wikiUpperChars, $wikiLowerChars );
4412      }
4413  
4414      /**
4415       * Decode an expiry (block, protection, etc) which has come from the DB
4416       *
4417       * @todo FIXME: why are we returnings DBMS-dependent strings???
4418       *
4419       * @param string $expiry Database expiry String
4420       * @param bool|int $format True to process using language functions, or TS_ constant
4421       *     to return the expiry in a given timestamp
4422       * @return string
4423       * @since 1.18
4424       */
4425  	public function formatExpiry( $expiry, $format = true ) {
4426          static $infinity;
4427          if ( $infinity === null ) {
4428              $infinity = wfGetDB( DB_SLAVE )->getInfinity();
4429          }
4430  
4431          if ( $expiry == '' || $expiry == $infinity ) {
4432              return $format === true
4433                  ? $this->getMessageFromDB( 'infiniteblock' )
4434                  : $infinity;
4435          } else {
4436              return $format === true
4437                  ? $this->timeanddate( $expiry, /* User preference timezone */ true )
4438                  : wfTimestamp( $format, $expiry );
4439          }
4440      }
4441  
4442      /**
4443       * @todo Document
4444       * @param int|float $seconds
4445       * @param array $format Optional
4446       *   If $format['avoid'] === 'avoidseconds': don't mention seconds if $seconds >= 1 hour.
4447       *   If $format['avoid'] === 'avoidminutes': don't mention seconds/minutes if $seconds > 48 hours.
4448       *   If $format['noabbrevs'] is true: use 'seconds' and friends instead of 'seconds-abbrev'
4449       *     and friends.
4450       *   For backwards compatibility, $format may also be one of the strings 'avoidseconds'
4451       *     or 'avoidminutes'.
4452       * @return string
4453       */
4454  	function formatTimePeriod( $seconds, $format = array() ) {
4455          if ( !is_array( $format ) ) {
4456              $format = array( 'avoid' => $format ); // For backwards compatibility
4457          }
4458          if ( !isset( $format['avoid'] ) ) {
4459              $format['avoid'] = false;
4460          }
4461          if ( !isset( $format['noabbrevs'] ) ) {
4462              $format['noabbrevs'] = false;
4463          }
4464          $secondsMsg = wfMessage(
4465              $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
4466          $minutesMsg = wfMessage(
4467              $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
4468          $hoursMsg = wfMessage(
4469              $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
4470          $daysMsg = wfMessage(
4471              $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
4472  
4473          if ( round( $seconds * 10 ) < 100 ) {
4474              $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
4475              $s = $secondsMsg->params( $s )->text();
4476          } elseif ( round( $seconds ) < 60 ) {
4477              $s = $this->formatNum( round( $seconds ) );
4478              $s = $secondsMsg->params( $s )->text();
4479          } elseif ( round( $seconds ) < 3600 ) {
4480              $minutes = floor( $seconds / 60 );
4481              $secondsPart = round( fmod( $seconds, 60 ) );
4482              if ( $secondsPart == 60 ) {
4483                  $secondsPart = 0;
4484                  $minutes++;
4485              }
4486              $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4487              $s .= ' ';
4488              $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4489          } elseif ( round( $seconds ) <= 2 * 86400 ) {
4490              $hours = floor( $seconds / 3600 );
4491              $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4492              $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4493              if ( $secondsPart == 60 ) {
4494                  $secondsPart = 0;
4495                  $minutes++;
4496              }
4497              if ( $minutes == 60 ) {
4498                  $minutes = 0;
4499                  $hours++;
4500              }
4501              $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
4502              $s .= ' ';
4503              $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4504              if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) {
4505                  $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
4506              }
4507          } else {
4508              $days = floor( $seconds / 86400 );
4509              if ( $format['avoid'] === 'avoidminutes' ) {
4510                  $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4511                  if ( $hours == 24 ) {
4512                      $hours = 0;
4513                      $days++;
4514                  }
4515                  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4516                  $s .= ' ';
4517                  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4518              } elseif ( $format['avoid'] === 'avoidseconds' ) {
4519                  $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4520                  $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4521                  if ( $minutes == 60 ) {
4522                      $minutes = 0;
4523                      $hours++;
4524                  }
4525                  if ( $hours == 24 ) {
4526                      $hours = 0;
4527                      $days++;
4528                  }
4529                  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4530                  $s .= ' ';
4531                  $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
4532                  $s .= ' ';
4533                  $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
4534              } else {
4535                  $s = $daysMsg->params( $this->formatNum( $days ) )->text();
4536                  $s .= ' ';
4537                  $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
4538              }
4539          }
4540          return $s;
4541      }
4542  
4543      /**
4544       * Format a bitrate for output, using an appropriate
4545       * unit (bps, kbps, Mbps, Gbps, Tbps, Pbps, Ebps, Zbps or Ybps) according to
4546       *   the magnitude in question.
4547       *
4548       * This use base 1000. For base 1024 use formatSize(), for another base
4549       * see formatComputingNumbers().
4550       *
4551       * @param int $bps
4552       * @return string
4553       */
4554  	function formatBitrate( $bps ) {
4555          return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
4556      }
4557  
4558      /**
4559       * @param int $size Size of the unit
4560       * @param int $boundary Size boundary (1000, or 1024 in most cases)
4561       * @param string $messageKey Message key to be uesd
4562       * @return string
4563       */
4564  	function formatComputingNumbers( $size, $boundary, $messageKey ) {
4565          if ( $size <= 0 ) {
4566              return str_replace( '$1', $this->formatNum( $size ),
4567                  $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
4568              );
4569          }
4570          $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' );
4571          $index = 0;
4572  
4573          $maxIndex = count( $sizes ) - 1;
4574          while ( $size >= $boundary && $index < $maxIndex ) {
4575              $index++;
4576              $size /= $boundary;
4577          }
4578  
4579          // For small sizes no decimal places necessary
4580          $round = 0;
4581          if ( $index > 1 ) {
4582              // For MB and bigger two decimal places are smarter
4583              $round = 2;
4584          }
4585          $msg = str_replace( '$1', $sizes[$index], $messageKey );
4586  
4587          $size = round( $size, $round );
4588          $text = $this->getMessageFromDB( $msg );
4589          return str_replace( '$1', $this->formatNum( $size ), $text );
4590      }
4591  
4592      /**
4593       * Format a size in bytes for output, using an appropriate
4594       * unit (B, KB, MB, GB, TB, PB, EB, ZB or YB) according to the magnitude in question
4595       *
4596       * This method use base 1024. For base 1000 use formatBitrate(), for
4597       * another base see formatComputingNumbers()
4598       *
4599       * @param int $size Size to format
4600       * @return string Plain text (not HTML)
4601       */
4602  	function formatSize( $size ) {
4603          return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
4604      }
4605  
4606      /**
4607       * Make a list item, used by various special pages
4608       *
4609       * @param string $page Page link
4610       * @param string $details Text between brackets
4611       * @param bool $oppositedm Add the direction mark opposite to your
4612       *   language, to display text properly
4613       * @return string
4614       */
4615  	function specialList( $page, $details, $oppositedm = true ) {
4616          $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) .
4617              $this->getDirMark();
4618          $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) .
4619              wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : '';
4620          return $page . $details;
4621      }
4622  
4623      /**
4624       * Generate (prev x| next x) (20|50|100...) type links for paging
4625       *
4626       * @param Title $title Title object to link
4627       * @param int $offset
4628       * @param int $limit
4629       * @param array $query Optional URL query parameter string
4630       * @param bool $atend Optional param for specified if this is the last page
4631       * @return string
4632       */
4633  	public function viewPrevNext( Title $title, $offset, $limit,
4634          array $query = array(), $atend = false
4635      ) {
4636          // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
4637  
4638          # Make 'previous' link
4639          $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4640          if ( $offset > 0 ) {
4641              $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
4642                  $query, $prev, 'prevn-title', 'mw-prevlink' );
4643          } else {
4644              $plink = htmlspecialchars( $prev );
4645          }
4646  
4647          # Make 'next' link
4648          $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
4649          if ( $atend ) {
4650              $nlink = htmlspecialchars( $next );
4651          } else {
4652              $nlink = $this->numLink( $title, $offset + $limit, $limit,
4653                  $query, $next, 'nextn-title', 'mw-nextlink' );
4654          }
4655  
4656          # Make links to set number of items per page
4657          $numLinks = array();
4658          foreach ( array( 20, 50, 100, 250, 500 ) as $num ) {
4659              $numLinks[] = $this->numLink( $title, $offset, $num,
4660                  $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
4661          }
4662  
4663          return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
4664              )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
4665      }
4666  
4667      /**
4668       * Helper function for viewPrevNext() that generates links
4669       *
4670       * @param Title $title Title object to link
4671       * @param int $offset
4672       * @param int $limit
4673       * @param array $query Extra query parameters
4674       * @param string $link Text to use for the link; will be escaped
4675       * @param string $tooltipMsg Name of the message to use as tooltip
4676       * @param string $class Value of the "class" attribute of the link
4677       * @return string HTML fragment
4678       */
4679  	private function numLink( Title $title, $offset, $limit, array $query, $link,
4680          $tooltipMsg, $class
4681      ) {
4682          $query = array( 'limit' => $limit, 'offset' => $offset ) + $query;
4683          $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
4684              ->numParams( $limit )->text();
4685  
4686          return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ),
4687              'title' => $tooltip, 'class' => $class ), $link );
4688      }
4689  
4690      /**
4691       * Get the conversion rule title, if any.
4692       *
4693       * @return string
4694       */
4695  	public function getConvRuleTitle() {
4696          return $this->mConverter->getConvRuleTitle();
4697      }
4698  
4699      /**
4700       * Get the compiled plural rules for the language
4701       * @since 1.20
4702       * @return array Associative array with plural form, and plural rule as key-value pairs
4703       */
4704  	public function getCompiledPluralRules() {
4705          $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
4706          $fallbacks = Language::getFallbacksFor( $this->mCode );
4707          if ( !$pluralRules ) {
4708              foreach ( $fallbacks as $fallbackCode ) {
4709                  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
4710                  if ( $pluralRules ) {
4711                      break;
4712                  }
4713              }
4714          }
4715          return $pluralRules;
4716      }
4717  
4718      /**
4719       * Get the plural rules for the language
4720       * @since 1.20
4721       * @return array Associative array with plural form number and plural rule as key-value pairs
4722       */
4723  	public function getPluralRules() {
4724          $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
4725          $fallbacks = Language::getFallbacksFor( $this->mCode );
4726          if ( !$pluralRules ) {
4727              foreach ( $fallbacks as $fallbackCode ) {
4728                  $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
4729                  if ( $pluralRules ) {
4730                      break;
4731                  }
4732              }
4733          }
4734          return $pluralRules;
4735      }
4736  
4737      /**
4738       * Get the plural rule types for the language
4739       * @since 1.22
4740       * @return array Associative array with plural form number and plural rule type as key-value pairs
4741       */
4742  	public function getPluralRuleTypes() {
4743          $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
4744          $fallbacks = Language::getFallbacksFor( $this->mCode );
4745          if ( !$pluralRuleTypes ) {
4746              foreach ( $fallbacks as $fallbackCode ) {
4747                  $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
4748                  if ( $pluralRuleTypes ) {
4749                      break;
4750                  }
4751              }
4752          }
4753          return $pluralRuleTypes;
4754      }
4755  
4756      /**
4757       * Find the index number of the plural rule appropriate for the given number
4758       * @param int $number
4759       * @return int The index number of the plural rule
4760       */
4761  	public function getPluralRuleIndexNumber( $number ) {
4762          $pluralRules = $this->getCompiledPluralRules();
4763          $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules );
4764          return $form;
4765      }
4766  
4767      /**
4768       * Find the plural rule type appropriate for the given number
4769       * For example, if the language is set to Arabic, getPluralType(5) should
4770       * return 'few'.
4771       * @since 1.22
4772       * @param int $number
4773       * @return string The name of the plural rule type, e.g. one, two, few, many
4774       */
4775  	public function getPluralRuleType( $number ) {
4776          $index = $this->getPluralRuleIndexNumber( $number );
4777          $pluralRuleTypes = $this->getPluralRuleTypes();
4778          if ( isset( $pluralRuleTypes[$index] ) ) {
4779              return $pluralRuleTypes[$index];
4780          } else {
4781              return 'other';
4782          }
4783      }
4784  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1