MediaWiki  REL1_24
Language.php
Go to the documentation of this file.
00001 <?php
00028 if ( !defined( 'MEDIAWIKI' ) ) {
00029     echo "This file is part of MediaWiki, it is not a valid entry point.\n";
00030     exit( 1 );
00031 }
00032 
00033 if ( function_exists( 'mb_strtoupper' ) ) {
00034     mb_internal_encoding( 'UTF-8' );
00035 }
00036 
00041 class Language {
00045     public $mConverter;
00046 
00047     public $mVariants, $mCode, $mLoaded = false;
00048     public $mMagicExtensions = array(), $mMagicHookDone = false;
00049     private $mHtmlCode = null, $mParentLanguage = false;
00050 
00051     public $dateFormatStrings = array();
00052     public $mExtendedSpecialPageAliases;
00053 
00054     protected $namespaceNames, $mNamespaceIds, $namespaceAliases;
00055 
00059     public $transformData = array();
00060 
00064     static public $dataCache;
00065 
00066     static public $mLangObjCache = array();
00067 
00068     static public $mWeekdayMsgs = array(
00069         'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
00070         'friday', 'saturday'
00071     );
00072 
00073     static public $mWeekdayAbbrevMsgs = array(
00074         'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
00075     );
00076 
00077     static public $mMonthMsgs = array(
00078         'january', 'february', 'march', 'april', 'may_long', 'june',
00079         'july', 'august', 'september', 'october', 'november',
00080         'december'
00081     );
00082     static public $mMonthGenMsgs = array(
00083         'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
00084         'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
00085         'december-gen'
00086     );
00087     static public $mMonthAbbrevMsgs = array(
00088         'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
00089         'sep', 'oct', 'nov', 'dec'
00090     );
00091 
00092     static public $mIranianCalendarMonthMsgs = array(
00093         'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
00094         'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
00095         'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
00096         'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
00097     );
00098 
00099     static public $mHebrewCalendarMonthMsgs = array(
00100         'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
00101         'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
00102         'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
00103         'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
00104         'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
00105     );
00106 
00107     static public $mHebrewCalendarMonthGenMsgs = array(
00108         'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
00109         'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
00110         'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
00111         'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
00112         'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
00113     );
00114 
00115     static public $mHijriCalendarMonthMsgs = array(
00116         'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
00117         'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
00118         'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
00119         'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
00120     );
00121 
00126     static public $durationIntervals = array(
00127         'millennia' => 31556952000,
00128         'centuries' => 3155695200,
00129         'decades' => 315569520,
00130         'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
00131         'weeks' => 604800,
00132         'days' => 86400,
00133         'hours' => 3600,
00134         'minutes' => 60,
00135         'seconds' => 1,
00136     );
00137 
00144     static private $fallbackLanguageCache = array();
00145 
00151     static function factory( $code ) {
00152         global $wgDummyLanguageCodes, $wgLangObjCacheSize;
00153 
00154         if ( isset( $wgDummyLanguageCodes[$code] ) ) {
00155             $code = $wgDummyLanguageCodes[$code];
00156         }
00157 
00158         // get the language object to process
00159         $langObj = isset( self::$mLangObjCache[$code] )
00160             ? self::$mLangObjCache[$code]
00161             : self::newFromCode( $code );
00162 
00163         // merge the language object in to get it up front in the cache
00164         self::$mLangObjCache = array_merge( array( $code => $langObj ), self::$mLangObjCache );
00165         // get rid of the oldest ones in case we have an overflow
00166         self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
00167 
00168         return $langObj;
00169     }
00170 
00177     protected static function newFromCode( $code ) {
00178         // Protect against path traversal below
00179         if ( !Language::isValidCode( $code )
00180             || strcspn( $code, ":/\\\000" ) !== strlen( $code )
00181         ) {
00182             throw new MWException( "Invalid language code \"$code\"" );
00183         }
00184 
00185         if ( !Language::isValidBuiltInCode( $code ) ) {
00186             // It's not possible to customise this code with class files, so
00187             // just return a Language object. This is to support uselang= hacks.
00188             $lang = new Language;
00189             $lang->setCode( $code );
00190             return $lang;
00191         }
00192 
00193         // Check if there is a language class for the code
00194         $class = self::classFromCode( $code );
00195         self::preloadLanguageClass( $class );
00196         if ( class_exists( $class ) ) {
00197             $lang = new $class;
00198             return $lang;
00199         }
00200 
00201         // Keep trying the fallback list until we find an existing class
00202         $fallbacks = Language::getFallbacksFor( $code );
00203         foreach ( $fallbacks as $fallbackCode ) {
00204             if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
00205                 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
00206             }
00207 
00208             $class = self::classFromCode( $fallbackCode );
00209             self::preloadLanguageClass( $class );
00210             if ( class_exists( $class ) ) {
00211                 $lang = Language::newFromCode( $fallbackCode );
00212                 $lang->setCode( $code );
00213                 return $lang;
00214             }
00215         }
00216 
00217         throw new MWException( "Invalid fallback sequence for language '$code'" );
00218     }
00219 
00228     public static function isSupportedLanguage( $code ) {
00229         return self::isValidBuiltInCode( $code )
00230             && ( is_readable( self::getMessagesFileName( $code ) )
00231                 || is_readable( self::getJsonMessagesFileName( $code ) )
00232         );
00233     }
00234 
00250     public static function isWellFormedLanguageTag( $code, $lenient = false ) {
00251         $alpha = '[a-z]';
00252         $digit = '[0-9]';
00253         $alphanum = '[a-z0-9]';
00254         $x = 'x'; # private use singleton
00255         $singleton = '[a-wy-z]'; # other singleton
00256         $s = $lenient ? '[-_]' : '-';
00257 
00258         $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
00259         $script = "$alpha{4}"; # ISO 15924
00260         $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
00261         $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
00262         $extension = "$singleton(?:$s$alphanum{2,8})+";
00263         $privateUse = "$x(?:$s$alphanum{1,8})+";
00264 
00265         # Define certain grandfathered codes, since otherwise the regex is pretty useless.
00266         # Since these are limited, this is safe even later changes to the registry --
00267         # the only oddity is that it might change the type of the tag, and thus
00268         # the results from the capturing groups.
00269         # http://www.iana.org/assignments/language-subtag-registry
00270 
00271         $grandfathered = "en{$s}GB{$s}oed"
00272             . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
00273             . "|no{$s}(?:bok|nyn)"
00274             . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
00275             . "|zh{$s}min{$s}nan";
00276 
00277         $variantList = "$variant(?:$s$variant)*";
00278         $extensionList = "$extension(?:$s$extension)*";
00279 
00280         $langtag = "(?:($language)"
00281             . "(?:$s$script)?"
00282             . "(?:$s$region)?"
00283             . "(?:$s$variantList)?"
00284             . "(?:$s$extensionList)?"
00285             . "(?:$s$privateUse)?)";
00286 
00287         # The final breakdown, with capturing groups for each of these components
00288         # The variants, extensions, grandfathered, and private-use may have interior '-'
00289 
00290         $root = "^(?:$langtag|$privateUse|$grandfathered)$";
00291 
00292         return (bool)preg_match( "/$root/", strtolower( $code ) );
00293     }
00294 
00304     public static function isValidCode( $code ) {
00305         static $cache = array();
00306         if ( isset( $cache[$code] ) ) {
00307             return $cache[$code];
00308         }
00309         // People think language codes are html safe, so enforce it.
00310         // Ideally we should only allow a-zA-Z0-9-
00311         // but, .+ and other chars are often used for {{int:}} hacks
00312         // see bugs 37564, 37587, 36938
00313         $cache[$code] =
00314             strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
00315             && !preg_match( Title::getTitleInvalidRegex(), $code );
00316 
00317         return $cache[$code];
00318     }
00319 
00330     public static function isValidBuiltInCode( $code ) {
00331 
00332         if ( !is_string( $code ) ) {
00333             if ( is_object( $code ) ) {
00334                 $addmsg = " of class " . get_class( $code );
00335             } else {
00336                 $addmsg = '';
00337             }
00338             $type = gettype( $code );
00339             throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
00340         }
00341 
00342         return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code );
00343     }
00344 
00353     public static function isKnownLanguageTag( $tag ) {
00354         static $coreLanguageNames;
00355 
00356         // Quick escape for invalid input to avoid exceptions down the line
00357         // when code tries to process tags which are not valid at all.
00358         if ( !self::isValidBuiltInCode( $tag ) ) {
00359             return false;
00360         }
00361 
00362         if ( $coreLanguageNames === null ) {
00363             global $IP;
00364             include "$IP/languages/Names.php";
00365         }
00366 
00367         if ( isset( $coreLanguageNames[$tag] )
00368             || self::fetchLanguageName( $tag, $tag ) !== ''
00369         ) {
00370             return true;
00371         }
00372 
00373         return false;
00374     }
00375 
00380     public static function classFromCode( $code ) {
00381         if ( $code == 'en' ) {
00382             return 'Language';
00383         } else {
00384             return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
00385         }
00386     }
00387 
00393     public static function preloadLanguageClass( $class ) {
00394         global $IP;
00395 
00396         if ( $class === 'Language' ) {
00397             return;
00398         }
00399 
00400         if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
00401             include_once "$IP/languages/classes/$class.php";
00402         }
00403     }
00404 
00410     public static function getLocalisationCache() {
00411         if ( is_null( self::$dataCache ) ) {
00412             global $wgLocalisationCacheConf;
00413             $class = $wgLocalisationCacheConf['class'];
00414             self::$dataCache = new $class( $wgLocalisationCacheConf );
00415         }
00416         return self::$dataCache;
00417     }
00418 
00419     function __construct() {
00420         $this->mConverter = new FakeConverter( $this );
00421         // Set the code to the name of the descendant
00422         if ( get_class( $this ) == 'Language' ) {
00423             $this->mCode = 'en';
00424         } else {
00425             $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
00426         }
00427         self::getLocalisationCache();
00428     }
00429 
00433     function __destruct() {
00434         foreach ( $this as $name => $value ) {
00435             unset( $this->$name );
00436         }
00437     }
00438 
00443     function initContLang() {
00444     }
00445 
00450     function getFallbackLanguages() {
00451         return self::getFallbacksFor( $this->mCode );
00452     }
00453 
00458     function getBookstoreList() {
00459         return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
00460     }
00461 
00468     public function getNamespaces() {
00469         if ( is_null( $this->namespaceNames ) ) {
00470             global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces;
00471 
00472             $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
00473             $validNamespaces = MWNamespace::getCanonicalNamespaces();
00474 
00475             $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
00476 
00477             $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
00478             if ( $wgMetaNamespaceTalk ) {
00479                 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
00480             } else {
00481                 $talk = $this->namespaceNames[NS_PROJECT_TALK];
00482                 $this->namespaceNames[NS_PROJECT_TALK] =
00483                     $this->fixVariableInNamespace( $talk );
00484             }
00485 
00486             # Sometimes a language will be localised but not actually exist on this wiki.
00487             foreach ( $this->namespaceNames as $key => $text ) {
00488                 if ( !isset( $validNamespaces[$key] ) ) {
00489                     unset( $this->namespaceNames[$key] );
00490                 }
00491             }
00492 
00493             # The above mixing may leave namespaces out of canonical order.
00494             # Re-order by namespace ID number...
00495             ksort( $this->namespaceNames );
00496 
00497             wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) );
00498         }
00499 
00500         return $this->namespaceNames;
00501     }
00502 
00507     public function setNamespaces( array $namespaces ) {
00508         $this->namespaceNames = $namespaces;
00509         $this->mNamespaceIds = null;
00510     }
00511 
00515     public function resetNamespaces() {
00516         $this->namespaceNames = null;
00517         $this->mNamespaceIds = null;
00518         $this->namespaceAliases = null;
00519     }
00520 
00529     function getFormattedNamespaces() {
00530         $ns = $this->getNamespaces();
00531         foreach ( $ns as $k => $v ) {
00532             $ns[$k] = strtr( $v, '_', ' ' );
00533         }
00534         return $ns;
00535     }
00536 
00547     function getNsText( $index ) {
00548         $ns = $this->getNamespaces();
00549 
00550         return isset( $ns[$index] ) ? $ns[$index] : false;
00551     }
00552 
00566     function getFormattedNsText( $index ) {
00567         $ns = $this->getNsText( $index );
00568 
00569         return strtr( $ns, '_', ' ' );
00570     }
00571 
00580     function getGenderNsText( $index, $gender ) {
00581         global $wgExtraGenderNamespaces;
00582 
00583         $ns = $wgExtraGenderNamespaces +
00584             self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00585 
00586         return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
00587     }
00588 
00595     function needsGenderDistinction() {
00596         global $wgExtraGenderNamespaces, $wgExtraNamespaces;
00597         if ( count( $wgExtraGenderNamespaces ) > 0 ) {
00598             // $wgExtraGenderNamespaces overrides everything
00599             return true;
00600         } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
00602             // $wgExtraNamespaces overrides any gender aliases specified in i18n files
00603             return false;
00604         } else {
00605             // Check what is in i18n files
00606             $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00607             return count( $aliases ) > 0;
00608         }
00609     }
00610 
00619     function getLocalNsIndex( $text ) {
00620         $lctext = $this->lc( $text );
00621         $ids = $this->getNamespaceIds();
00622         return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
00623     }
00624 
00628     function getNamespaceAliases() {
00629         if ( is_null( $this->namespaceAliases ) ) {
00630             $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
00631             if ( !$aliases ) {
00632                 $aliases = array();
00633             } else {
00634                 foreach ( $aliases as $name => $index ) {
00635                     if ( $index === NS_PROJECT_TALK ) {
00636                         unset( $aliases[$name] );
00637                         $name = $this->fixVariableInNamespace( $name );
00638                         $aliases[$name] = $index;
00639                     }
00640                 }
00641             }
00642 
00643             global $wgExtraGenderNamespaces;
00644             $genders = $wgExtraGenderNamespaces +
00645                 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00646             foreach ( $genders as $index => $forms ) {
00647                 foreach ( $forms as $alias ) {
00648                     $aliases[$alias] = $index;
00649                 }
00650             }
00651 
00652             # Also add converted namespace names as aliases, to avoid confusion.
00653             $convertedNames = array();
00654             foreach ( $this->getVariants() as $variant ) {
00655                 if ( $variant === $this->mCode ) {
00656                     continue;
00657                 }
00658                 foreach ( $this->getNamespaces() as $ns => $_ ) {
00659                     $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns;
00660                 }
00661             }
00662 
00663             $this->namespaceAliases = $aliases + $convertedNames;
00664         }
00665 
00666         return $this->namespaceAliases;
00667     }
00668 
00672     function getNamespaceIds() {
00673         if ( is_null( $this->mNamespaceIds ) ) {
00674             global $wgNamespaceAliases;
00675             # Put namespace names and aliases into a hashtable.
00676             # If this is too slow, then we should arrange it so that it is done
00677             # before caching. The catch is that at pre-cache time, the above
00678             # class-specific fixup hasn't been done.
00679             $this->mNamespaceIds = array();
00680             foreach ( $this->getNamespaces() as $index => $name ) {
00681                 $this->mNamespaceIds[$this->lc( $name )] = $index;
00682             }
00683             foreach ( $this->getNamespaceAliases() as $name => $index ) {
00684                 $this->mNamespaceIds[$this->lc( $name )] = $index;
00685             }
00686             if ( $wgNamespaceAliases ) {
00687                 foreach ( $wgNamespaceAliases as $name => $index ) {
00688                     $this->mNamespaceIds[$this->lc( $name )] = $index;
00689                 }
00690             }
00691         }
00692         return $this->mNamespaceIds;
00693     }
00694 
00702     function getNsIndex( $text ) {
00703         $lctext = $this->lc( $text );
00704         $ns = MWNamespace::getCanonicalIndex( $lctext );
00705         if ( $ns !== null ) {
00706             return $ns;
00707         }
00708         $ids = $this->getNamespaceIds();
00709         return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
00710     }
00711 
00719     function getVariantname( $code, $usemsg = true ) {
00720         $msg = "variantname-$code";
00721         if ( $usemsg && wfMessage( $msg )->exists() ) {
00722             return $this->getMessageFromDB( $msg );
00723         }
00724         $name = self::fetchLanguageName( $code );
00725         if ( $name ) {
00726             return $name; # if it's defined as a language name, show that
00727         } else {
00728             # otherwise, output the language code
00729             return $code;
00730         }
00731     }
00732 
00737     function specialPage( $name ) {
00738         $aliases = $this->getSpecialPageAliases();
00739         if ( isset( $aliases[$name][0] ) ) {
00740             $name = $aliases[$name][0];
00741         }
00742         return $this->getNsText( NS_SPECIAL ) . ':' . $name;
00743     }
00744 
00748     function getDatePreferences() {
00749         return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
00750     }
00751 
00755     function getDateFormats() {
00756         return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
00757     }
00758 
00762     function getDefaultDateFormat() {
00763         $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
00764         if ( $df === 'dmy or mdy' ) {
00765             global $wgAmericanDates;
00766             return $wgAmericanDates ? 'mdy' : 'dmy';
00767         } else {
00768             return $df;
00769         }
00770     }
00771 
00775     function getDatePreferenceMigrationMap() {
00776         return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
00777     }
00778 
00783     function getImageFile( $image ) {
00784         return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
00785     }
00786 
00791     function getImageFiles() {
00792         return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
00793     }
00794 
00798     function getExtraUserToggles() {
00799         return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
00800     }
00801 
00806     function getUserToggle( $tog ) {
00807         return $this->getMessageFromDB( "tog-$tog" );
00808     }
00809 
00820     public static function getLanguageNames( $customisedOnly = false ) {
00821         return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' );
00822     }
00823 
00833     public static function getTranslatedLanguageNames( $code ) {
00834         return self::fetchLanguageNames( $code, 'all' );
00835     }
00836 
00848     public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
00849         global $wgExtraLanguageNames;
00850         static $coreLanguageNames;
00851 
00852         if ( $coreLanguageNames === null ) {
00853             global $IP;
00854             include "$IP/languages/Names.php";
00855         }
00856 
00857         // If passed an invalid language code to use, fallback to en
00858         if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
00859             $inLanguage = 'en';
00860         }
00861 
00862         $names = array();
00863 
00864         if ( $inLanguage ) {
00865             # TODO: also include when $inLanguage is null, when this code is more efficient
00866             wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) );
00867         }
00868 
00869         $mwNames = $wgExtraLanguageNames + $coreLanguageNames;
00870         foreach ( $mwNames as $mwCode => $mwName ) {
00871             # - Prefer own MediaWiki native name when not using the hook
00872             # - For other names just add if not added through the hook
00873             if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
00874                 $names[$mwCode] = $mwName;
00875             }
00876         }
00877 
00878         if ( $include === 'all' ) {
00879             return $names;
00880         }
00881 
00882         $returnMw = array();
00883         $coreCodes = array_keys( $mwNames );
00884         foreach ( $coreCodes as $coreCode ) {
00885             $returnMw[$coreCode] = $names[$coreCode];
00886         }
00887 
00888         if ( $include === 'mwfile' ) {
00889             $namesMwFile = array();
00890             # We do this using a foreach over the codes instead of a directory
00891             # loop so that messages files in extensions will work correctly.
00892             foreach ( $returnMw as $code => $value ) {
00893                 if ( is_readable( self::getMessagesFileName( $code ) )
00894                     || is_readable( self::getJsonMessagesFileName( $code ) )
00895                 ) {
00896                     $namesMwFile[$code] = $names[$code];
00897                 }
00898             }
00899 
00900             return $namesMwFile;
00901         }
00902 
00903         # 'mw' option; default if it's not one of the other two options (all/mwfile)
00904         return $returnMw;
00905     }
00906 
00914     public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
00915         $code = strtolower( $code );
00916         $array = self::fetchLanguageNames( $inLanguage, $include );
00917         return !array_key_exists( $code, $array ) ? '' : $array[$code];
00918     }
00919 
00926     function getMessageFromDB( $msg ) {
00927         return wfMessage( $msg )->inLanguage( $this )->text();
00928     }
00929 
00937     function getLanguageName( $code ) {
00938         return self::fetchLanguageName( $code );
00939     }
00940 
00945     function getMonthName( $key ) {
00946         return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
00947     }
00948 
00952     function getMonthNamesArray() {
00953         $monthNames = array( '' );
00954         for ( $i = 1; $i < 13; $i++ ) {
00955             $monthNames[] = $this->getMonthName( $i );
00956         }
00957         return $monthNames;
00958     }
00959 
00964     function getMonthNameGen( $key ) {
00965         return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
00966     }
00967 
00972     function getMonthAbbreviation( $key ) {
00973         return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
00974     }
00975 
00979     function getMonthAbbreviationsArray() {
00980         $monthNames = array( '' );
00981         for ( $i = 1; $i < 13; $i++ ) {
00982             $monthNames[] = $this->getMonthAbbreviation( $i );
00983         }
00984         return $monthNames;
00985     }
00986 
00991     function getWeekdayName( $key ) {
00992         return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
00993     }
00994 
00999     function getWeekdayAbbreviation( $key ) {
01000         return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
01001     }
01002 
01007     function getIranianCalendarMonthName( $key ) {
01008         return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
01009     }
01010 
01015     function getHebrewCalendarMonthName( $key ) {
01016         return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
01017     }
01018 
01023     function getHebrewCalendarMonthNameGen( $key ) {
01024         return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
01025     }
01026 
01031     function getHijriCalendarMonthName( $key ) {
01032         return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
01033     }
01034 
01043     private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) {
01044         if ( !$dateTimeObj ) {
01045             $dateTimeObj = DateTime::createFromFormat(
01046                 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' )
01047             );
01048         }
01049         return $dateTimeObj->format( $code );
01050     }
01051 
01119     function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = null ) {
01120         $s = '';
01121         $raw = false;
01122         $roman = false;
01123         $hebrewNum = false;
01124         $dateTimeObj = false;
01125         $rawToggle = false;
01126         $iranian = false;
01127         $hebrew = false;
01128         $hijri = false;
01129         $thai = false;
01130         $minguo = false;
01131         $tenno = false;
01132 
01133         $usedSecond = false;
01134         $usedMinute = false;
01135         $usedHour = false;
01136         $usedAMPM = false;
01137         $usedDay = false;
01138         $usedWeek = false;
01139         $usedMonth = false;
01140         $usedYear = false;
01141         $usedISOYear = false;
01142         $usedIsLeapYear = false;
01143 
01144         $usedHebrewMonth = false;
01145         $usedIranianMonth = false;
01146         $usedHijriMonth = false;
01147         $usedHebrewYear = false;
01148         $usedIranianYear = false;
01149         $usedHijriYear = false;
01150         $usedTennoYear = false;
01151 
01152         if ( strlen( $ts ) !== 14 ) {
01153             throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
01154         }
01155 
01156         if ( !ctype_digit( $ts ) ) {
01157             throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
01158         }
01159 
01160         $formatLength = strlen( $format );
01161         for ( $p = 0; $p < $formatLength; $p++ ) {
01162             $num = false;
01163             $code = $format[$p];
01164             if ( $code == 'x' && $p < $formatLength - 1 ) {
01165                 $code .= $format[++$p];
01166             }
01167 
01168             if ( ( $code === 'xi'
01169                     || $code === 'xj'
01170                     || $code === 'xk'
01171                     || $code === 'xm'
01172                     || $code === 'xo'
01173                     || $code === 'xt' )
01174                 && $p < $formatLength - 1 ) {
01175                 $code .= $format[++$p];
01176             }
01177 
01178             switch ( $code ) {
01179                 case 'xx':
01180                     $s .= 'x';
01181                     break;
01182                 case 'xn':
01183                     $raw = true;
01184                     break;
01185                 case 'xN':
01186                     $rawToggle = !$rawToggle;
01187                     break;
01188                 case 'xr':
01189                     $roman = true;
01190                     break;
01191                 case 'xh':
01192                     $hebrewNum = true;
01193                     break;
01194                 case 'xg':
01195                     $usedMonth = true;
01196                     $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
01197                     break;
01198                 case 'xjx':
01199                     $usedHebrewMonth = true;
01200                     if ( !$hebrew ) {
01201                         $hebrew = self::tsToHebrew( $ts );
01202                     }
01203                     $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
01204                     break;
01205                 case 'd':
01206                     $usedDay = true;
01207                     $num = substr( $ts, 6, 2 );
01208                     break;
01209                 case 'D':
01210                     $usedDay = true;
01211                     $s .= $this->getWeekdayAbbreviation( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1 );
01212                     break;
01213                 case 'j':
01214                     $usedDay = true;
01215                     $num = intval( substr( $ts, 6, 2 ) );
01216                     break;
01217                 case 'xij':
01218                     $usedDay = true;
01219                     if ( !$iranian ) {
01220                         $iranian = self::tsToIranian( $ts );
01221                     }
01222                     $num = $iranian[2];
01223                     break;
01224                 case 'xmj':
01225                     $usedDay = true;
01226                     if ( !$hijri ) {
01227                         $hijri = self::tsToHijri( $ts );
01228                     }
01229                     $num = $hijri[2];
01230                     break;
01231                 case 'xjj':
01232                     $usedDay = true;
01233                     if ( !$hebrew ) {
01234                         $hebrew = self::tsToHebrew( $ts );
01235                     }
01236                     $num = $hebrew[2];
01237                     break;
01238                 case 'l':
01239                     $usedDay = true;
01240                     $s .= $this->getWeekdayName( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1 );
01241                     break;
01242                 case 'F':
01243                     $usedMonth = true;
01244                     $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
01245                     break;
01246                 case 'xiF':
01247                     $usedIranianMonth = true;
01248                     if ( !$iranian ) {
01249                         $iranian = self::tsToIranian( $ts );
01250                     }
01251                     $s .= $this->getIranianCalendarMonthName( $iranian[1] );
01252                     break;
01253                 case 'xmF':
01254                     $usedHijriMonth = true;
01255                     if ( !$hijri ) {
01256                         $hijri = self::tsToHijri( $ts );
01257                     }
01258                     $s .= $this->getHijriCalendarMonthName( $hijri[1] );
01259                     break;
01260                 case 'xjF':
01261                     $usedHebrewMonth = true;
01262                     if ( !$hebrew ) {
01263                         $hebrew = self::tsToHebrew( $ts );
01264                     }
01265                     $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
01266                     break;
01267                 case 'm':
01268                     $usedMonth = true;
01269                     $num = substr( $ts, 4, 2 );
01270                     break;
01271                 case 'M':
01272                     $usedMonth = true;
01273                     $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
01274                     break;
01275                 case 'n':
01276                     $usedMonth = true;
01277                     $num = intval( substr( $ts, 4, 2 ) );
01278                     break;
01279                 case 'xin':
01280                     $usedIranianMonth = true;
01281                     if ( !$iranian ) {
01282                         $iranian = self::tsToIranian( $ts );
01283                     }
01284                     $num = $iranian[1];
01285                     break;
01286                 case 'xmn':
01287                     $usedHijriMonth = true;
01288                     if ( !$hijri ) {
01289                         $hijri = self::tsToHijri ( $ts );
01290                     }
01291                     $num = $hijri[1];
01292                     break;
01293                 case 'xjn':
01294                     $usedHebrewMonth = true;
01295                     if ( !$hebrew ) {
01296                         $hebrew = self::tsToHebrew( $ts );
01297                     }
01298                     $num = $hebrew[1];
01299                     break;
01300                 case 'xjt':
01301                     $usedHebrewMonth = true;
01302                     if ( !$hebrew ) {
01303                         $hebrew = self::tsToHebrew( $ts );
01304                     }
01305                     $num = $hebrew[3];
01306                     break;
01307                 case 'Y':
01308                     $usedYear = true;
01309                     $num = substr( $ts, 0, 4 );
01310                     break;
01311                 case 'xiY':
01312                     $usedIranianYear = true;
01313                     if ( !$iranian ) {
01314                         $iranian = self::tsToIranian( $ts );
01315                     }
01316                     $num = $iranian[0];
01317                     break;
01318                 case 'xmY':
01319                     $usedHijriYear = true;
01320                     if ( !$hijri ) {
01321                         $hijri = self::tsToHijri( $ts );
01322                     }
01323                     $num = $hijri[0];
01324                     break;
01325                 case 'xjY':
01326                     $usedHebrewYear = true;
01327                     if ( !$hebrew ) {
01328                         $hebrew = self::tsToHebrew( $ts );
01329                     }
01330                     $num = $hebrew[0];
01331                     break;
01332                 case 'xkY':
01333                     $usedYear = true;
01334                     if ( !$thai ) {
01335                         $thai = self::tsToYear( $ts, 'thai' );
01336                     }
01337                     $num = $thai[0];
01338                     break;
01339                 case 'xoY':
01340                     $usedYear = true;
01341                     if ( !$minguo ) {
01342                         $minguo = self::tsToYear( $ts, 'minguo' );
01343                     }
01344                     $num = $minguo[0];
01345                     break;
01346                 case 'xtY':
01347                     $usedTennoYear = true;
01348                     if ( !$tenno ) {
01349                         $tenno = self::tsToYear( $ts, 'tenno' );
01350                     }
01351                     $num = $tenno[0];
01352                     break;
01353                 case 'y':
01354                     $usedYear = true;
01355                     $num = substr( $ts, 2, 2 );
01356                     break;
01357                 case 'xiy':
01358                     $usedIranianYear = true;
01359                     if ( !$iranian ) {
01360                         $iranian = self::tsToIranian( $ts );
01361                     }
01362                     $num = substr( $iranian[0], -2 );
01363                     break;
01364                 case 'a':
01365                     $usedAMPM = true;
01366                     $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
01367                     break;
01368                 case 'A':
01369                     $usedAMPM = true;
01370                     $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
01371                     break;
01372                 case 'g':
01373                     $usedHour = true;
01374                     $h = substr( $ts, 8, 2 );
01375                     $num = $h % 12 ? $h % 12 : 12;
01376                     break;
01377                 case 'G':
01378                     $usedHour = true;
01379                     $num = intval( substr( $ts, 8, 2 ) );
01380                     break;
01381                 case 'h':
01382                     $usedHour = true;
01383                     $h = substr( $ts, 8, 2 );
01384                     $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
01385                     break;
01386                 case 'H':
01387                     $usedHour = true;
01388                     $num = substr( $ts, 8, 2 );
01389                     break;
01390                 case 'i':
01391                     $usedMinute = true;
01392                     $num = substr( $ts, 10, 2 );
01393                     break;
01394                 case 's':
01395                     $usedSecond = true;
01396                     $num = substr( $ts, 12, 2 );
01397                     break;
01398                 case 'c':
01399                 case 'r':
01400                     $usedSecond = true;
01401                     // fall through
01402                 case 'e':
01403                 case 'O':
01404                 case 'P':
01405                 case 'T':
01406                     $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
01407                     break;
01408                 case 'w':
01409                 case 'N':
01410                 case 'z':
01411                     $usedDay = true;
01412                     $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
01413                     break;
01414                 case 'W':
01415                     $usedWeek = true;
01416                     $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
01417                     break;
01418                 case 't':
01419                     $usedMonth = true;
01420                     $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
01421                     break;
01422                 case 'L':
01423                     $usedIsLeapYear = true;
01424                     $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
01425                     break;
01426                 case 'o':
01427                     $usedISOYear = true;
01428                     $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
01429                     break;
01430                 case 'U':
01431                     $usedSecond = true;
01432                     // fall through
01433                 case 'I':
01434                 case 'Z':
01435                     $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code );
01436                     break;
01437                 case '\\':
01438                     # Backslash escaping
01439                     if ( $p < $formatLength - 1 ) {
01440                         $s .= $format[++$p];
01441                     } else {
01442                         $s .= '\\';
01443                     }
01444                     break;
01445                 case '"':
01446                     # Quoted literal
01447                     if ( $p < $formatLength - 1 ) {
01448                         $endQuote = strpos( $format, '"', $p + 1 );
01449                         if ( $endQuote === false ) {
01450                             # No terminating quote, assume literal "
01451                             $s .= '"';
01452                         } else {
01453                             $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
01454                             $p = $endQuote;
01455                         }
01456                     } else {
01457                         # Quote at end of string, assume literal "
01458                         $s .= '"';
01459                     }
01460                     break;
01461                 default:
01462                     $s .= $format[$p];
01463             }
01464             if ( $num !== false ) {
01465                 if ( $rawToggle || $raw ) {
01466                     $s .= $num;
01467                     $raw = false;
01468                 } elseif ( $roman ) {
01469                     $s .= Language::romanNumeral( $num );
01470                     $roman = false;
01471                 } elseif ( $hebrewNum ) {
01472                     $s .= self::hebrewNumeral( $num );
01473                     $hebrewNum = false;
01474                 } else {
01475                     $s .= $this->formatNum( $num, true );
01476                 }
01477             }
01478         }
01479 
01480         if ( $usedSecond ) {
01481             $ttl = 1;
01482         } elseif ( $usedMinute ) {
01483             $ttl = 60 - substr( $ts, 12, 2 );
01484         } elseif ( $usedHour ) {
01485             $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
01486         } elseif ( $usedAMPM ) {
01487             $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
01488         } elseif ( $usedDay || $usedHebrewMonth || $usedIranianMonth || $usedHijriMonth || $usedHebrewYear || $usedIranianYear || $usedHijriYear || $usedTennoYear ) {
01489             // @todo Someone who understands the non-Gregorian calendars should write proper logic for them
01490             // so that they don't need purged every day.
01491             $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
01492         } else {
01493             $possibleTtls = array();
01494             $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 );
01495             if ( $usedWeek ) {
01496                 $possibleTtls[] = ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 + $timeRemainingInDay;
01497             } elseif ( $usedISOYear ) {
01498                 // December 28th falls on the last ISO week of the year, every year.
01499                 // The last ISO week of a year can be 52 or 53.
01500                 $lastWeekOfISOYear = DateTime::createFromFormat( 'Ymd', substr( $ts, 0, 4 ) . '1228', $zone ?: new DateTimeZone( 'UTC' ) )->format( 'W' );
01501                 $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' );
01502                 $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek;
01503                 $timeRemainingInWeek = ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 + $timeRemainingInDay;
01504                 $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek;
01505             }
01506 
01507             if ( $usedMonth ) {
01508                 $possibleTtls[] = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) - substr( $ts, 6, 2 ) ) * 86400 + $timeRemainingInDay;
01509             } elseif ( $usedYear ) {
01510                 $possibleTtls[] = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
01511                     + $timeRemainingInDay;
01512             } elseif ( $usedIsLeapYear ) {
01513                 $year = substr( $ts, 0, 4 );
01514                 $timeRemainingInYear = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400
01515                     + $timeRemainingInDay;
01516                 $mod = $year % 4;
01517                 if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) {
01518                     // this isn't a leap year. see when the next one starts
01519                     $nextCandidate = $year - $mod + 4;
01520                     if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) {
01521                         $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 + $timeRemainingInYear;
01522                     } else {
01523                         $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 + $timeRemainingInYear;
01524                     }
01525                 } else {
01526                     // this is a leap year, so the next year isn't
01527                     $possibleTtls[] = $timeRemainingInYear;
01528                 }
01529             }
01530 
01531             if ( $possibleTtls ) {
01532                 $ttl = min( $possibleTtls );
01533             }
01534         }
01535 
01536         return $s;
01537     }
01538 
01539     private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
01540     private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
01541 
01554     private static function tsToIranian( $ts ) {
01555         $gy = substr( $ts, 0, 4 ) -1600;
01556         $gm = substr( $ts, 4, 2 ) -1;
01557         $gd = substr( $ts, 6, 2 ) -1;
01558 
01559         # Days passed from the beginning (including leap years)
01560         $gDayNo = 365 * $gy
01561             + floor( ( $gy + 3 ) / 4 )
01562             - floor( ( $gy + 99 ) / 100 )
01563             + floor( ( $gy + 399 ) / 400 );
01564 
01565         // Add days of the past months of this year
01566         for ( $i = 0; $i < $gm; $i++ ) {
01567             $gDayNo += self::$GREG_DAYS[$i];
01568         }
01569 
01570         // Leap years
01571         if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
01572             $gDayNo++;
01573         }
01574 
01575         // Days passed in current month
01576         $gDayNo += (int)$gd;
01577 
01578         $jDayNo = $gDayNo - 79;
01579 
01580         $jNp = floor( $jDayNo / 12053 );
01581         $jDayNo %= 12053;
01582 
01583         $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
01584         $jDayNo %= 1461;
01585 
01586         if ( $jDayNo >= 366 ) {
01587             $jy += floor( ( $jDayNo - 1 ) / 365 );
01588             $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
01589         }
01590 
01591         for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
01592             $jDayNo -= self::$IRANIAN_DAYS[$i];
01593         }
01594 
01595         $jm = $i + 1;
01596         $jd = $jDayNo + 1;
01597 
01598         return array( $jy, $jm, $jd );
01599     }
01600 
01612     private static function tsToHijri( $ts ) {
01613         $year = substr( $ts, 0, 4 );
01614         $month = substr( $ts, 4, 2 );
01615         $day = substr( $ts, 6, 2 );
01616 
01617         $zyr = $year;
01618         $zd = $day;
01619         $zm = $month;
01620         $zy = $zyr;
01621 
01622         if (
01623             ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
01624             ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
01625         ) {
01626             $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
01627                     (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
01628                     (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
01629                     $zd - 32075;
01630         } else {
01631             $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
01632                                 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
01633         }
01634 
01635         $zl = $zjd -1948440 + 10632;
01636         $zn = (int)( ( $zl - 1 ) / 10631 );
01637         $zl = $zl - 10631 * $zn + 354;
01638         $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) +
01639             ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
01640         $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) -
01641             ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
01642         $zm = (int)( ( 24 * $zl ) / 709 );
01643         $zd = $zl - (int)( ( 709 * $zm ) / 24 );
01644         $zy = 30 * $zn + $zj - 30;
01645 
01646         return array( $zy, $zm, $zd );
01647     }
01648 
01664     private static function tsToHebrew( $ts ) {
01665         # Parse date
01666         $year = substr( $ts, 0, 4 );
01667         $month = substr( $ts, 4, 2 );
01668         $day = substr( $ts, 6, 2 );
01669 
01670         # Calculate Hebrew year
01671         $hebrewYear = $year + 3760;
01672 
01673         # Month number when September = 1, August = 12
01674         $month += 4;
01675         if ( $month > 12 ) {
01676             # Next year
01677             $month -= 12;
01678             $year++;
01679             $hebrewYear++;
01680         }
01681 
01682         # Calculate day of year from 1 September
01683         $dayOfYear = $day;
01684         for ( $i = 1; $i < $month; $i++ ) {
01685             if ( $i == 6 ) {
01686                 # February
01687                 $dayOfYear += 28;
01688                 # Check if the year is leap
01689                 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
01690                     $dayOfYear++;
01691                 }
01692             } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
01693                 $dayOfYear += 30;
01694             } else {
01695                 $dayOfYear += 31;
01696             }
01697         }
01698 
01699         # Calculate the start of the Hebrew year
01700         $start = self::hebrewYearStart( $hebrewYear );
01701 
01702         # Calculate next year's start
01703         if ( $dayOfYear <= $start ) {
01704             # Day is before the start of the year - it is the previous year
01705             # Next year's start
01706             $nextStart = $start;
01707             # Previous year
01708             $year--;
01709             $hebrewYear--;
01710             # Add days since previous year's 1 September
01711             $dayOfYear += 365;
01712             if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
01713                 # Leap year
01714                 $dayOfYear++;
01715             }
01716             # Start of the new (previous) year
01717             $start = self::hebrewYearStart( $hebrewYear );
01718         } else {
01719             # Next year's start
01720             $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
01721         }
01722 
01723         # Calculate Hebrew day of year
01724         $hebrewDayOfYear = $dayOfYear - $start;
01725 
01726         # Difference between year's days
01727         $diff = $nextStart - $start;
01728         # Add 12 (or 13 for leap years) days to ignore the difference between
01729         # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
01730         # difference is only about the year type
01731         if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
01732             $diff += 13;
01733         } else {
01734             $diff += 12;
01735         }
01736 
01737         # Check the year pattern, and is leap year
01738         # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
01739         # This is mod 30, to work on both leap years (which add 30 days of Adar I)
01740         # and non-leap years
01741         $yearPattern = $diff % 30;
01742         # Check if leap year
01743         $isLeap = $diff >= 30;
01744 
01745         # Calculate day in the month from number of day in the Hebrew year
01746         # Don't check Adar - if the day is not in Adar, we will stop before;
01747         # if it is in Adar, we will use it to check if it is Adar I or Adar II
01748         $hebrewDay = $hebrewDayOfYear;
01749         $hebrewMonth = 1;
01750         $days = 0;
01751         while ( $hebrewMonth <= 12 ) {
01752             # Calculate days in this month
01753             if ( $isLeap && $hebrewMonth == 6 ) {
01754                 # Adar in a leap year
01755                 if ( $isLeap ) {
01756                     # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
01757                     $days = 30;
01758                     if ( $hebrewDay <= $days ) {
01759                         # Day in Adar I
01760                         $hebrewMonth = 13;
01761                     } else {
01762                         # Subtract the days of Adar I
01763                         $hebrewDay -= $days;
01764                         # Try Adar II
01765                         $days = 29;
01766                         if ( $hebrewDay <= $days ) {
01767                             # Day in Adar II
01768                             $hebrewMonth = 14;
01769                         }
01770                     }
01771                 }
01772             } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
01773                 # Cheshvan in a complete year (otherwise as the rule below)
01774                 $days = 30;
01775             } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
01776                 # Kislev in an incomplete year (otherwise as the rule below)
01777                 $days = 29;
01778             } else {
01779                 # Odd months have 30 days, even have 29
01780                 $days = 30 - ( $hebrewMonth - 1 ) % 2;
01781             }
01782             if ( $hebrewDay <= $days ) {
01783                 # In the current month
01784                 break;
01785             } else {
01786                 # Subtract the days of the current month
01787                 $hebrewDay -= $days;
01788                 # Try in the next month
01789                 $hebrewMonth++;
01790             }
01791         }
01792 
01793         return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
01794     }
01795 
01805     private static function hebrewYearStart( $year ) {
01806         $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
01807         $b = intval( ( $year - 1 ) % 4 );
01808         $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
01809         if ( $m < 0 ) {
01810             $m--;
01811         }
01812         $Mar = intval( $m );
01813         if ( $m < 0 ) {
01814             $m++;
01815         }
01816         $m -= $Mar;
01817 
01818         $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
01819         if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
01820             $Mar++;
01821         } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
01822             $Mar += 2;
01823         } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
01824             $Mar++;
01825         }
01826 
01827         $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
01828         return $Mar;
01829     }
01830 
01843     private static function tsToYear( $ts, $cName ) {
01844         $gy = substr( $ts, 0, 4 );
01845         $gm = substr( $ts, 4, 2 );
01846         $gd = substr( $ts, 6, 2 );
01847 
01848         if ( !strcmp( $cName, 'thai' ) ) {
01849             # Thai solar dates
01850             # Add 543 years to the Gregorian calendar
01851             # Months and days are identical
01852             $gy_offset = $gy + 543;
01853         } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
01854             # Minguo dates
01855             # Deduct 1911 years from the Gregorian calendar
01856             # Months and days are identical
01857             $gy_offset = $gy - 1911;
01858         } elseif ( !strcmp( $cName, 'tenno' ) ) {
01859             # Nengō dates up to Meiji period
01860             # Deduct years from the Gregorian calendar
01861             # depending on the nengo periods
01862             # Months and days are identical
01863             if ( ( $gy < 1912 )
01864                 || ( ( $gy == 1912 ) && ( $gm < 7 ) )
01865                 || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) )
01866             ) {
01867                 # Meiji period
01868                 $gy_gannen = $gy - 1868 + 1;
01869                 $gy_offset = $gy_gannen;
01870                 if ( $gy_gannen == 1 ) {
01871                     $gy_offset = '元';
01872                 }
01873                 $gy_offset = '明治' . $gy_offset;
01874             } elseif (
01875                 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
01876                 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
01877                 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
01878                 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
01879                 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
01880             ) {
01881                 # Taishō period
01882                 $gy_gannen = $gy - 1912 + 1;
01883                 $gy_offset = $gy_gannen;
01884                 if ( $gy_gannen == 1 ) {
01885                     $gy_offset = '元';
01886                 }
01887                 $gy_offset = '大正' . $gy_offset;
01888             } elseif (
01889                 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
01890                 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
01891                 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
01892             ) {
01893                 # Shōwa period
01894                 $gy_gannen = $gy - 1926 + 1;
01895                 $gy_offset = $gy_gannen;
01896                 if ( $gy_gannen == 1 ) {
01897                     $gy_offset = '元';
01898                 }
01899                 $gy_offset = '昭和' . $gy_offset;
01900             } else {
01901                 # Heisei period
01902                 $gy_gannen = $gy - 1989 + 1;
01903                 $gy_offset = $gy_gannen;
01904                 if ( $gy_gannen == 1 ) {
01905                     $gy_offset = '元';
01906                 }
01907                 $gy_offset = '平成' . $gy_offset;
01908             }
01909         } else {
01910             $gy_offset = $gy;
01911         }
01912 
01913         return array( $gy_offset, $gm, $gd );
01914     }
01915 
01923     static function romanNumeral( $num ) {
01924         static $table = array(
01925             array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
01926             array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
01927             array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
01928             array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM',
01929                 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' )
01930         );
01931 
01932         $num = intval( $num );
01933         if ( $num > 10000 || $num <= 0 ) {
01934             return $num;
01935         }
01936 
01937         $s = '';
01938         for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
01939             if ( $num >= $pow10 ) {
01940                 $s .= $table[$i][(int)floor( $num / $pow10 )];
01941             }
01942             $num = $num % $pow10;
01943         }
01944         return $s;
01945     }
01946 
01954     static function hebrewNumeral( $num ) {
01955         static $table = array(
01956             array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
01957             array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
01958             array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
01959             array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
01960         );
01961 
01962         $num = intval( $num );
01963         if ( $num > 9999 || $num <= 0 ) {
01964             return $num;
01965         }
01966 
01967         $s = '';
01968         for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
01969             if ( $num >= $pow10 ) {
01970                 if ( $num == 15 || $num == 16 ) {
01971                     $s .= $table[0][9] . $table[0][$num - 9];
01972                     $num = 0;
01973                 } else {
01974                     $s .= $table[$i][intval( ( $num / $pow10 ) )];
01975                     if ( $pow10 == 1000 ) {
01976                         $s .= "'";
01977                     }
01978                 }
01979             }
01980             $num = $num % $pow10;
01981         }
01982         if ( strlen( $s ) == 2 ) {
01983             $str = $s . "'";
01984         } else {
01985             $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
01986             $str .= substr( $s, strlen( $s ) - 2, 2 );
01987         }
01988         $start = substr( $str, 0, strlen( $str ) - 2 );
01989         $end = substr( $str, strlen( $str ) - 2 );
01990         switch ( $end ) {
01991             case 'כ':
01992                 $str = $start . 'ך';
01993                 break;
01994             case 'מ':
01995                 $str = $start . 'ם';
01996                 break;
01997             case 'נ':
01998                 $str = $start . 'ן';
01999                 break;
02000             case 'פ':
02001                 $str = $start . 'ף';
02002                 break;
02003             case 'צ':
02004                 $str = $start . 'ץ';
02005                 break;
02006         }
02007         return $str;
02008     }
02009 
02018     function userAdjust( $ts, $tz = false ) {
02019         global $wgUser, $wgLocalTZoffset;
02020 
02021         if ( $tz === false ) {
02022             $tz = $wgUser->getOption( 'timecorrection' );
02023         }
02024 
02025         $data = explode( '|', $tz, 3 );
02026 
02027         if ( $data[0] == 'ZoneInfo' ) {
02028             wfSuppressWarnings();
02029             $userTZ = timezone_open( $data[2] );
02030             wfRestoreWarnings();
02031             if ( $userTZ !== false ) {
02032                 $date = date_create( $ts, timezone_open( 'UTC' ) );
02033                 date_timezone_set( $date, $userTZ );
02034                 $date = date_format( $date, 'YmdHis' );
02035                 return $date;
02036             }
02037             # Unrecognized timezone, default to 'Offset' with the stored offset.
02038             $data[0] = 'Offset';
02039         }
02040 
02041         if ( $data[0] == 'System' || $tz == '' ) {
02042             # Global offset in minutes.
02043             $minDiff = $wgLocalTZoffset;
02044         } elseif ( $data[0] == 'Offset' ) {
02045             $minDiff = intval( $data[1] );
02046         } else {
02047             $data = explode( ':', $tz );
02048             if ( count( $data ) == 2 ) {
02049                 $data[0] = intval( $data[0] );
02050                 $data[1] = intval( $data[1] );
02051                 $minDiff = abs( $data[0] ) * 60 + $data[1];
02052                 if ( $data[0] < 0 ) {
02053                     $minDiff = -$minDiff;
02054                 }
02055             } else {
02056                 $minDiff = intval( $data[0] ) * 60;
02057             }
02058         }
02059 
02060         # No difference ? Return time unchanged
02061         if ( 0 == $minDiff ) {
02062             return $ts;
02063         }
02064 
02065         wfSuppressWarnings(); // E_STRICT system time bitching
02066         # Generate an adjusted date; take advantage of the fact that mktime
02067         # will normalize out-of-range values so we don't have to split $minDiff
02068         # into hours and minutes.
02069         $t = mktime( (
02070             (int)substr( $ts, 8, 2 ) ), # Hours
02071             (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
02072             (int)substr( $ts, 12, 2 ), # Seconds
02073             (int)substr( $ts, 4, 2 ), # Month
02074             (int)substr( $ts, 6, 2 ), # Day
02075             (int)substr( $ts, 0, 4 ) ); # Year
02076 
02077         $date = date( 'YmdHis', $t );
02078         wfRestoreWarnings();
02079 
02080         return $date;
02081     }
02082 
02100     function dateFormat( $usePrefs = true ) {
02101         global $wgUser;
02102 
02103         if ( is_bool( $usePrefs ) ) {
02104             if ( $usePrefs ) {
02105                 $datePreference = $wgUser->getDatePreference();
02106             } else {
02107                 $datePreference = (string)User::getDefaultOption( 'date' );
02108             }
02109         } else {
02110             $datePreference = (string)$usePrefs;
02111         }
02112 
02113         // return int
02114         if ( $datePreference == '' ) {
02115             return 'default';
02116         }
02117 
02118         return $datePreference;
02119     }
02120 
02130     function getDateFormatString( $type, $pref ) {
02131         if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
02132             if ( $pref == 'default' ) {
02133                 $pref = $this->getDefaultDateFormat();
02134                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
02135             } else {
02136                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
02137 
02138                 if ( $type === 'pretty' && $df === null ) {
02139                     $df = $this->getDateFormatString( 'date', $pref );
02140                 }
02141 
02142                 if ( $df === null ) {
02143                     $pref = $this->getDefaultDateFormat();
02144                     $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
02145                 }
02146             }
02147             $this->dateFormatStrings[$type][$pref] = $df;
02148         }
02149         return $this->dateFormatStrings[$type][$pref];
02150     }
02151 
02162     function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
02163         $ts = wfTimestamp( TS_MW, $ts );
02164         if ( $adj ) {
02165             $ts = $this->userAdjust( $ts, $timecorrection );
02166         }
02167         $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
02168         return $this->sprintfDate( $df, $ts );
02169     }
02170 
02181     function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
02182         $ts = wfTimestamp( TS_MW, $ts );
02183         if ( $adj ) {
02184             $ts = $this->userAdjust( $ts, $timecorrection );
02185         }
02186         $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
02187         return $this->sprintfDate( $df, $ts );
02188     }
02189 
02201     function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
02202         $ts = wfTimestamp( TS_MW, $ts );
02203         if ( $adj ) {
02204             $ts = $this->userAdjust( $ts, $timecorrection );
02205         }
02206         $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
02207         return $this->sprintfDate( $df, $ts );
02208     }
02209 
02220     public function formatDuration( $seconds, array $chosenIntervals = array() ) {
02221         $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
02222 
02223         $segments = array();
02224 
02225         foreach ( $intervals as $intervalName => $intervalValue ) {
02226             // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks,
02227             // duration-years, duration-decades, duration-centuries, duration-millennia
02228             $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
02229             $segments[] = $message->inLanguage( $this )->escaped();
02230         }
02231 
02232         return $this->listToText( $segments );
02233     }
02234 
02246     public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) {
02247         if ( empty( $chosenIntervals ) ) {
02248             $chosenIntervals = array(
02249                 'millennia',
02250                 'centuries',
02251                 'decades',
02252                 'years',
02253                 'days',
02254                 'hours',
02255                 'minutes',
02256                 'seconds'
02257             );
02258         }
02259 
02260         $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
02261         $sortedNames = array_keys( $intervals );
02262         $smallestInterval = array_pop( $sortedNames );
02263 
02264         $segments = array();
02265 
02266         foreach ( $intervals as $name => $length ) {
02267             $value = floor( $seconds / $length );
02268 
02269             if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
02270                 $seconds -= $value * $length;
02271                 $segments[$name] = $value;
02272             }
02273         }
02274 
02275         return $segments;
02276     }
02277 
02297     private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
02298         $ts = wfTimestamp( TS_MW, $ts );
02299         $options += array( 'timecorrection' => true, 'format' => true );
02300         if ( $options['timecorrection'] !== false ) {
02301             if ( $options['timecorrection'] === true ) {
02302                 $offset = $user->getOption( 'timecorrection' );
02303             } else {
02304                 $offset = $options['timecorrection'];
02305             }
02306             $ts = $this->userAdjust( $ts, $offset );
02307         }
02308         if ( $options['format'] === true ) {
02309             $format = $user->getDatePreference();
02310         } else {
02311             $format = $options['format'];
02312         }
02313         $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
02314         return $this->sprintfDate( $df, $ts );
02315     }
02316 
02336     public function userDate( $ts, User $user, array $options = array() ) {
02337         return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
02338     }
02339 
02359     public function userTime( $ts, User $user, array $options = array() ) {
02360         return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
02361     }
02362 
02382     public function userTimeAndDate( $ts, User $user, array $options = array() ) {
02383         return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
02384     }
02385 
02401     public function getHumanTimestamp( MWTimestamp $ts, MWTimestamp $relativeTo, User $user ) {
02402         $diff = $ts->diff( $relativeTo );
02403         $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) -
02404             (int)$relativeTo->timestamp->format( 'w' ) );
02405         $days = $diff->days ?: (int)$diffDay;
02406         if ( $diff->invert || $days > 5
02407             && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' )
02408         ) {
02409             // Timestamps are in different years: use full timestamp
02410             // Also do full timestamp for future dates
02414             $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
02415             $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
02416         } elseif ( $days > 5 ) {
02417             // Timestamps are in same year,  but more than 5 days ago: show day and month only.
02418             $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
02419             $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
02420         } elseif ( $days > 1 ) {
02421             // Timestamp within the past week: show the day of the week and time
02422             $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
02423             $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
02424             // Messages:
02425             // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at
02426             $ts = wfMessage( "$weekday-at" )
02427                 ->inLanguage( $this )
02428                 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
02429                 ->text();
02430         } elseif ( $days == 1 ) {
02431             // Timestamp was yesterday: say 'yesterday' and the time.
02432             $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
02433             $ts = wfMessage( 'yesterday-at' )
02434                 ->inLanguage( $this )
02435                 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
02436                 ->text();
02437         } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
02438             // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
02439             $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
02440             $ts = wfMessage( 'today-at' )
02441                 ->inLanguage( $this )
02442                 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
02443                 ->text();
02444 
02445         // From here on in, the timestamp was soon enough ago so that we can simply say
02446         // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
02447         } elseif ( $diff->h == 1 ) {
02448             // Less than 90 minutes, but more than an hour ago.
02449             $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
02450         } elseif ( $diff->i >= 1 ) {
02451             // A few minutes ago.
02452             $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
02453         } elseif ( $diff->s >= 30 ) {
02454             // Less than a minute, but more than 30 sec ago.
02455             $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
02456         } else {
02457             // Less than 30 seconds ago.
02458             $ts = wfMessage( 'just-now' )->text();
02459         }
02460 
02461         return $ts;
02462     }
02463 
02468     function getMessage( $key ) {
02469         return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
02470     }
02471 
02475     function getAllMessages() {
02476         return self::$dataCache->getItem( $this->mCode, 'messages' );
02477     }
02478 
02485     function iconv( $in, $out, $string ) {
02486         # This is a wrapper for iconv in all languages except esperanto,
02487         # which does some nasty x-conversions beforehand
02488 
02489         # Even with //IGNORE iconv can whine about illegal characters in
02490         # *input* string. We just ignore those too.
02491         # REF: http://bugs.php.net/bug.php?id=37166
02492         # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885
02493         wfSuppressWarnings();
02494         $text = iconv( $in, $out . '//IGNORE', $string );
02495         wfRestoreWarnings();
02496         return $text;
02497     }
02498 
02499     // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
02500 
02505     function ucwordbreaksCallbackAscii( $matches ) {
02506         return $this->ucfirst( $matches[1] );
02507     }
02508 
02513     function ucwordbreaksCallbackMB( $matches ) {
02514         return mb_strtoupper( $matches[0] );
02515     }
02516 
02521     function ucCallback( $matches ) {
02522         list( $wikiUpperChars ) = self::getCaseMaps();
02523         return strtr( $matches[1], $wikiUpperChars );
02524     }
02525 
02530     function lcCallback( $matches ) {
02531         list( , $wikiLowerChars ) = self::getCaseMaps();
02532         return strtr( $matches[1], $wikiLowerChars );
02533     }
02534 
02539     function ucwordsCallbackMB( $matches ) {
02540         return mb_strtoupper( $matches[0] );
02541     }
02542 
02547     function ucwordsCallbackWiki( $matches ) {
02548         list( $wikiUpperChars ) = self::getCaseMaps();
02549         return strtr( $matches[0], $wikiUpperChars );
02550     }
02551 
02559     function ucfirst( $str ) {
02560         $o = ord( $str );
02561         if ( $o < 96 ) { // if already uppercase...
02562             return $str;
02563         } elseif ( $o < 128 ) {
02564             return ucfirst( $str ); // use PHP's ucfirst()
02565         } else {
02566             // fall back to more complex logic in case of multibyte strings
02567             return $this->uc( $str, true );
02568         }
02569     }
02570 
02579     function uc( $str, $first = false ) {
02580         if ( function_exists( 'mb_strtoupper' ) ) {
02581             if ( $first ) {
02582                 if ( $this->isMultibyte( $str ) ) {
02583                     return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
02584                 } else {
02585                     return ucfirst( $str );
02586                 }
02587             } else {
02588                 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
02589             }
02590         } else {
02591             if ( $this->isMultibyte( $str ) ) {
02592                 $x = $first ? '^' : '';
02593                 return preg_replace_callback(
02594                     "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
02595                     array( $this, 'ucCallback' ),
02596                     $str
02597                 );
02598             } else {
02599                 return $first ? ucfirst( $str ) : strtoupper( $str );
02600             }
02601         }
02602     }
02603 
02608     function lcfirst( $str ) {
02609         $o = ord( $str );
02610         if ( !$o ) {
02611             return strval( $str );
02612         } elseif ( $o >= 128 ) {
02613             return $this->lc( $str, true );
02614         } elseif ( $o > 96 ) {
02615             return $str;
02616         } else {
02617             $str[0] = strtolower( $str[0] );
02618             return $str;
02619         }
02620     }
02621 
02627     function lc( $str, $first = false ) {
02628         if ( function_exists( 'mb_strtolower' ) ) {
02629             if ( $first ) {
02630                 if ( $this->isMultibyte( $str ) ) {
02631                     return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
02632                 } else {
02633                     return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
02634                 }
02635             } else {
02636                 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
02637             }
02638         } else {
02639             if ( $this->isMultibyte( $str ) ) {
02640                 $x = $first ? '^' : '';
02641                 return preg_replace_callback(
02642                     "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
02643                     array( $this, 'lcCallback' ),
02644                     $str
02645                 );
02646             } else {
02647                 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
02648             }
02649         }
02650     }
02651 
02656     function isMultibyte( $str ) {
02657         return (bool)preg_match( '/[\x80-\xff]/', $str );
02658     }
02659 
02664     function ucwords( $str ) {
02665         if ( $this->isMultibyte( $str ) ) {
02666             $str = $this->lc( $str );
02667 
02668             // regexp to find first letter in each word (i.e. after each space)
02669             $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
02670 
02671             // function to use to capitalize a single char
02672             if ( function_exists( 'mb_strtoupper' ) ) {
02673                 return preg_replace_callback(
02674                     $replaceRegexp,
02675                     array( $this, 'ucwordsCallbackMB' ),
02676                     $str
02677                 );
02678             } else {
02679                 return preg_replace_callback(
02680                     $replaceRegexp,
02681                     array( $this, 'ucwordsCallbackWiki' ),
02682                     $str
02683                 );
02684             }
02685         } else {
02686             return ucwords( strtolower( $str ) );
02687         }
02688     }
02689 
02696     function ucwordbreaks( $str ) {
02697         if ( $this->isMultibyte( $str ) ) {
02698             $str = $this->lc( $str );
02699 
02700             // since \b doesn't work for UTF-8, we explicitely define word break chars
02701             $breaks = "[ \-\(\)\}\{\.,\?!]";
02702 
02703             // find first letter after word break
02704             $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" .
02705                 "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
02706 
02707             if ( function_exists( 'mb_strtoupper' ) ) {
02708                 return preg_replace_callback(
02709                     $replaceRegexp,
02710                     array( $this, 'ucwordbreaksCallbackMB' ),
02711                     $str
02712                 );
02713             } else {
02714                 return preg_replace_callback(
02715                     $replaceRegexp,
02716                     array( $this, 'ucwordsCallbackWiki' ),
02717                     $str
02718                 );
02719             }
02720         } else {
02721             return preg_replace_callback(
02722                 '/\b([\w\x80-\xff]+)\b/',
02723                 array( $this, 'ucwordbreaksCallbackAscii' ),
02724                 $str
02725             );
02726         }
02727     }
02728 
02744     function caseFold( $s ) {
02745         return $this->uc( $s );
02746     }
02747 
02752     function checkTitleEncoding( $s ) {
02753         if ( is_array( $s ) ) {
02754             throw new MWException( 'Given array to checkTitleEncoding.' );
02755         }
02756         if ( StringUtils::isUtf8( $s ) ) {
02757             return $s;
02758         }
02759 
02760         return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
02761     }
02762 
02766     function fallback8bitEncoding() {
02767         return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
02768     }
02769 
02778     function hasWordBreaks() {
02779         return true;
02780     }
02781 
02789     function segmentByWord( $string ) {
02790         return $string;
02791     }
02792 
02800     function normalizeForSearch( $string ) {
02801         return self::convertDoubleWidth( $string );
02802     }
02803 
02812     protected static function convertDoubleWidth( $string ) {
02813         static $full = null;
02814         static $half = null;
02815 
02816         if ( $full === null ) {
02817             $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
02818             $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
02819             $full = str_split( $fullWidth, 3 );
02820             $half = str_split( $halfWidth );
02821         }
02822 
02823         $string = str_replace( $full, $half, $string );
02824         return $string;
02825     }
02826 
02832     protected static function insertSpace( $string, $pattern ) {
02833         $string = preg_replace( $pattern, " $1 ", $string );
02834         $string = preg_replace( '/ +/', ' ', $string );
02835         return $string;
02836     }
02837 
02842     function convertForSearchResult( $termsArray ) {
02843         # some languages, e.g. Chinese, need to do a conversion
02844         # in order for search results to be displayed correctly
02845         return $termsArray;
02846     }
02847 
02854     function firstChar( $s ) {
02855         $matches = array();
02856         preg_match(
02857             '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
02858                 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
02859             $s,
02860             $matches
02861         );
02862 
02863         if ( isset( $matches[1] ) ) {
02864             if ( strlen( $matches[1] ) != 3 ) {
02865                 return $matches[1];
02866             }
02867 
02868             // Break down Hangul syllables to grab the first jamo
02869             $code = utf8ToCodepoint( $matches[1] );
02870             if ( $code < 0xac00 || 0xd7a4 <= $code ) {
02871                 return $matches[1];
02872             } elseif ( $code < 0xb098 ) {
02873                 return "\xe3\x84\xb1";
02874             } elseif ( $code < 0xb2e4 ) {
02875                 return "\xe3\x84\xb4";
02876             } elseif ( $code < 0xb77c ) {
02877                 return "\xe3\x84\xb7";
02878             } elseif ( $code < 0xb9c8 ) {
02879                 return "\xe3\x84\xb9";
02880             } elseif ( $code < 0xbc14 ) {
02881                 return "\xe3\x85\x81";
02882             } elseif ( $code < 0xc0ac ) {
02883                 return "\xe3\x85\x82";
02884             } elseif ( $code < 0xc544 ) {
02885                 return "\xe3\x85\x85";
02886             } elseif ( $code < 0xc790 ) {
02887                 return "\xe3\x85\x87";
02888             } elseif ( $code < 0xcc28 ) {
02889                 return "\xe3\x85\x88";
02890             } elseif ( $code < 0xce74 ) {
02891                 return "\xe3\x85\x8a";
02892             } elseif ( $code < 0xd0c0 ) {
02893                 return "\xe3\x85\x8b";
02894             } elseif ( $code < 0xd30c ) {
02895                 return "\xe3\x85\x8c";
02896             } elseif ( $code < 0xd558 ) {
02897                 return "\xe3\x85\x8d";
02898             } else {
02899                 return "\xe3\x85\x8e";
02900             }
02901         } else {
02902             return '';
02903         }
02904     }
02905 
02906     function initEncoding() {
02907         # Some languages may have an alternate char encoding option
02908         # (Esperanto X-coding, Japanese furigana conversion, etc)
02909         # If this language is used as the primary content language,
02910         # an override to the defaults can be set here on startup.
02911     }
02912 
02917     function recodeForEdit( $s ) {
02918         # For some languages we'll want to explicitly specify
02919         # which characters make it into the edit box raw
02920         # or are converted in some way or another.
02921         global $wgEditEncoding;
02922         if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) {
02923             return $s;
02924         } else {
02925             return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
02926         }
02927     }
02928 
02933     function recodeInput( $s ) {
02934         # Take the previous into account.
02935         global $wgEditEncoding;
02936         if ( $wgEditEncoding != '' ) {
02937             $enc = $wgEditEncoding;
02938         } else {
02939             $enc = 'UTF-8';
02940         }
02941         if ( $enc == 'UTF-8' ) {
02942             return $s;
02943         } else {
02944             return $this->iconv( $enc, 'UTF-8', $s );
02945         }
02946     }
02947 
02959     function normalize( $s ) {
02960         global $wgAllUnicodeFixes;
02961         $s = UtfNormal::cleanUp( $s );
02962         if ( $wgAllUnicodeFixes ) {
02963             $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
02964             $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
02965         }
02966 
02967         return $s;
02968     }
02969 
02984     function transformUsingPairFile( $file, $string ) {
02985         if ( !isset( $this->transformData[$file] ) ) {
02986             $data = wfGetPrecompiledData( $file );
02987             if ( $data === false ) {
02988                 throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
02989             }
02990             $this->transformData[$file] = new ReplacementArray( $data );
02991         }
02992         return $this->transformData[$file]->replace( $string );
02993     }
02994 
03000     function isRTL() {
03001         return self::$dataCache->getItem( $this->mCode, 'rtl' );
03002     }
03003 
03008     function getDir() {
03009         return $this->isRTL() ? 'rtl' : 'ltr';
03010     }
03011 
03020     function alignStart() {
03021         return $this->isRTL() ? 'right' : 'left';
03022     }
03023 
03032     function alignEnd() {
03033         return $this->isRTL() ? 'left' : 'right';
03034     }
03035 
03047     function getDirMarkEntity( $opposite = false ) {
03048         if ( $opposite ) {
03049             return $this->isRTL() ? '&lrm;' : '&rlm;';
03050         }
03051         return $this->isRTL() ? '&rlm;' : '&lrm;';
03052     }
03053 
03064     function getDirMark( $opposite = false ) {
03065         $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
03066         $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
03067         if ( $opposite ) {
03068             return $this->isRTL() ? $lrm : $rlm;
03069         }
03070         return $this->isRTL() ? $rlm : $lrm;
03071     }
03072 
03076     function capitalizeAllNouns() {
03077         return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
03078     }
03079 
03087     function getArrow( $direction = 'forwards' ) {
03088         switch ( $direction ) {
03089         case 'forwards':
03090             return $this->isRTL() ? '←' : '→';
03091         case 'backwards':
03092             return $this->isRTL() ? '→' : '←';
03093         case 'left':
03094             return '←';
03095         case 'right':
03096             return '→';
03097         case 'up':
03098             return '↑';
03099         case 'down':
03100             return '↓';
03101         }
03102     }
03103 
03109     function linkPrefixExtension() {
03110         return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
03111     }
03112 
03117     function getMagicWords() {
03118         return self::$dataCache->getItem( $this->mCode, 'magicWords' );
03119     }
03120 
03124     protected function doMagicHook() {
03125         if ( $this->mMagicHookDone ) {
03126             return;
03127         }
03128         $this->mMagicHookDone = true;
03129         wfProfileIn( 'LanguageGetMagic' );
03130         wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
03131         wfProfileOut( 'LanguageGetMagic' );
03132     }
03133 
03139     function getMagic( $mw ) {
03140         // Saves a function call
03141         if ( !$this->mMagicHookDone ) {
03142             $this->doMagicHook();
03143         }
03144 
03145         if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
03146             $rawEntry = $this->mMagicExtensions[$mw->mId];
03147         } else {
03148             $rawEntry = self::$dataCache->getSubitem(
03149                 $this->mCode, 'magicWords', $mw->mId );
03150         }
03151 
03152         if ( !is_array( $rawEntry ) ) {
03153             wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
03154         } else {
03155             $mw->mCaseSensitive = $rawEntry[0];
03156             $mw->mSynonyms = array_slice( $rawEntry, 1 );
03157         }
03158     }
03159 
03165     function addMagicWordsByLang( $newWords ) {
03166         $fallbackChain = $this->getFallbackLanguages();
03167         $fallbackChain = array_reverse( $fallbackChain );
03168         foreach ( $fallbackChain as $code ) {
03169             if ( isset( $newWords[$code] ) ) {
03170                 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
03171             }
03172         }
03173     }
03174 
03180     function getSpecialPageAliases() {
03181         // Cache aliases because it may be slow to load them
03182         if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
03183             // Initialise array
03184             $this->mExtendedSpecialPageAliases =
03185                 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
03186             wfRunHooks( 'LanguageGetSpecialPageAliases',
03187                 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
03188         }
03189 
03190         return $this->mExtendedSpecialPageAliases;
03191     }
03192 
03199     function emphasize( $text ) {
03200         return "<em>$text</em>";
03201     }
03202 
03225     public function formatNum( $number, $nocommafy = false ) {
03226         global $wgTranslateNumerals;
03227         if ( !$nocommafy ) {
03228             $number = $this->commafy( $number );
03229             $s = $this->separatorTransformTable();
03230             if ( $s ) {
03231                 $number = strtr( $number, $s );
03232             }
03233         }
03234 
03235         if ( $wgTranslateNumerals ) {
03236             $s = $this->digitTransformTable();
03237             if ( $s ) {
03238                 $number = strtr( $number, $s );
03239             }
03240         }
03241 
03242         return $number;
03243     }
03244 
03253     public function formatNumNoSeparators( $number ) {
03254         return $this->formatNum( $number, true );
03255     }
03256 
03261     public function parseFormattedNumber( $number ) {
03262         $s = $this->digitTransformTable();
03263         if ( $s ) {
03264             // eliminate empty array values such as ''. (bug 64347)
03265             $s = array_filter( $s );
03266             $number = strtr( $number, array_flip( $s ) );
03267         }
03268 
03269         $s = $this->separatorTransformTable();
03270         if ( $s ) {
03271             // eliminate empty array values such as ''. (bug 64347)
03272             $s = array_filter( $s );
03273             $number = strtr( $number, array_flip( $s ) );
03274         }
03275 
03276         $number = strtr( $number, array( ',' => '' ) );
03277         return $number;
03278     }
03279 
03286     function commafy( $number ) {
03287         $digitGroupingPattern = $this->digitGroupingPattern();
03288         if ( $number === null ) {
03289             return '';
03290         }
03291 
03292         if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
03293             // default grouping is at thousands,  use the same for ###,###,### pattern too.
03294             return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
03295         } else {
03296             // Ref: http://cldr.unicode.org/translation/number-patterns
03297             $sign = "";
03298             if ( intval( $number ) < 0 ) {
03299                 // For negative numbers apply the algorithm like positive number and add sign.
03300                 $sign = "-";
03301                 $number = substr( $number, 1 );
03302             }
03303             $integerPart = array();
03304             $decimalPart = array();
03305             $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
03306             preg_match( "/\d+/", $number, $integerPart );
03307             preg_match( "/\.\d*/", $number, $decimalPart );
03308             $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
03309             if ( $groupedNumber === $number ) {
03310                 // the string does not have any number part. Eg: .12345
03311                 return $sign . $groupedNumber;
03312             }
03313             $start = $end = strlen( $integerPart[0] );
03314             while ( $start > 0 ) {
03315                 $match = $matches[0][$numMatches - 1];
03316                 $matchLen = strlen( $match );
03317                 $start = $end - $matchLen;
03318                 if ( $start < 0 ) {
03319                     $start = 0;
03320                 }
03321                 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
03322                 $end = $start;
03323                 if ( $numMatches > 1 ) {
03324                     // use the last pattern for the rest of the number
03325                     $numMatches--;
03326                 }
03327                 if ( $start > 0 ) {
03328                     $groupedNumber = "," . $groupedNumber;
03329                 }
03330             }
03331             return $sign . $groupedNumber;
03332         }
03333     }
03334 
03338     function digitGroupingPattern() {
03339         return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
03340     }
03341 
03345     function digitTransformTable() {
03346         return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
03347     }
03348 
03352     function separatorTransformTable() {
03353         return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
03354     }
03355 
03365     function listToText( array $l ) {
03366         $m = count( $l ) - 1;
03367         if ( $m < 0 ) {
03368             return '';
03369         }
03370         if ( $m > 0 ) {
03371             $and = $this->getMessageFromDB( 'and' );
03372             $space = $this->getMessageFromDB( 'word-separator' );
03373             if ( $m > 1 ) {
03374                 $comma = $this->getMessageFromDB( 'comma-separator' );
03375             }
03376         }
03377         $s = $l[$m];
03378         for ( $i = $m - 1; $i >= 0; $i-- ) {
03379             if ( $i == $m - 1 ) {
03380                 $s = $l[$i] . $and . $space . $s;
03381             } else {
03382                 $s = $l[$i] . $comma . $s;
03383             }
03384         }
03385         return $s;
03386     }
03387 
03394     function commaList( array $list ) {
03395         return implode(
03396             wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
03397             $list
03398         );
03399     }
03400 
03407     function semicolonList( array $list ) {
03408         return implode(
03409             wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
03410             $list
03411         );
03412     }
03413 
03419     function pipeList( array $list ) {
03420         return implode(
03421             wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
03422             $list
03423         );
03424     }
03425 
03443     function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
03444         # Use the localized ellipsis character
03445         if ( $ellipsis == '...' ) {
03446             $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
03447         }
03448         # Check if there is no need to truncate
03449         if ( $length == 0 ) {
03450             return $ellipsis; // convention
03451         } elseif ( strlen( $string ) <= abs( $length ) ) {
03452             return $string; // no need to truncate
03453         }
03454         $stringOriginal = $string;
03455         # If ellipsis length is >= $length then we can't apply $adjustLength
03456         if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
03457             $string = $ellipsis; // this can be slightly unexpected
03458         # Otherwise, truncate and add ellipsis...
03459         } else {
03460             $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
03461             if ( $length > 0 ) {
03462                 $length -= $eLength;
03463                 $string = substr( $string, 0, $length ); // xyz...
03464                 $string = $this->removeBadCharLast( $string );
03465                 $string = rtrim( $string );
03466                 $string = $string . $ellipsis;
03467             } else {
03468                 $length += $eLength;
03469                 $string = substr( $string, $length ); // ...xyz
03470                 $string = $this->removeBadCharFirst( $string );
03471                 $string = ltrim( $string );
03472                 $string = $ellipsis . $string;
03473             }
03474         }
03475         # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
03476         # This check is *not* redundant if $adjustLength, due to the single case where
03477         # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
03478         if ( strlen( $string ) < strlen( $stringOriginal ) ) {
03479             return $string;
03480         } else {
03481             return $stringOriginal;
03482         }
03483     }
03484 
03492     protected function removeBadCharLast( $string ) {
03493         if ( $string != '' ) {
03494             $char = ord( $string[strlen( $string ) - 1] );
03495             $m = array();
03496             if ( $char >= 0xc0 ) {
03497                 # We got the first byte only of a multibyte char; remove it.
03498                 $string = substr( $string, 0, -1 );
03499             } elseif ( $char >= 0x80 &&
03500                 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
03501                     '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m )
03502             ) {
03503                 # We chopped in the middle of a character; remove it
03504                 $string = $m[1];
03505             }
03506         }
03507         return $string;
03508     }
03509 
03517     protected function removeBadCharFirst( $string ) {
03518         if ( $string != '' ) {
03519             $char = ord( $string[0] );
03520             if ( $char >= 0x80 && $char < 0xc0 ) {
03521                 # We chopped in the middle of a character; remove the whole thing
03522                 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
03523             }
03524         }
03525         return $string;
03526     }
03527 
03543     function truncateHtml( $text, $length, $ellipsis = '...' ) {
03544         # Use the localized ellipsis character
03545         if ( $ellipsis == '...' ) {
03546             $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
03547         }
03548         # Check if there is clearly no need to truncate
03549         if ( $length <= 0 ) {
03550             return $ellipsis; // no text shown, nothing to format (convention)
03551         } elseif ( strlen( $text ) <= $length ) {
03552             return $text; // string short enough even *with* HTML (short-circuit)
03553         }
03554 
03555         $dispLen = 0; // innerHTML legth so far
03556         $testingEllipsis = false; // checking if ellipses will make string longer/equal?
03557         $tagType = 0; // 0-open, 1-close
03558         $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
03559         $entityState = 0; // 0-not entity, 1-entity
03560         $tag = $ret = ''; // accumulated tag name, accumulated result string
03561         $openTags = array(); // open tag stack
03562         $maybeState = null; // possible truncation state
03563 
03564         $textLen = strlen( $text );
03565         $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
03566         for ( $pos = 0; true; ++$pos ) {
03567             # Consider truncation once the display length has reached the maximim.
03568             # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
03569             # Check that we're not in the middle of a bracket/entity...
03570             if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
03571                 if ( !$testingEllipsis ) {
03572                     $testingEllipsis = true;
03573                     # Save where we are; we will truncate here unless there turn out to
03574                     # be so few remaining characters that truncation is not necessary.
03575                     if ( !$maybeState ) { // already saved? ($neLength = 0 case)
03576                         $maybeState = array( $ret, $openTags ); // save state
03577                     }
03578                 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
03579                     # String in fact does need truncation, the truncation point was OK.
03580                     list( $ret, $openTags ) = $maybeState; // reload state
03581                     $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
03582                     $ret .= $ellipsis; // add ellipsis
03583                     break;
03584                 }
03585             }
03586             if ( $pos >= $textLen ) {
03587                 break; // extra iteration just for above checks
03588             }
03589 
03590             # Read the next char...
03591             $ch = $text[$pos];
03592             $lastCh = $pos ? $text[$pos - 1] : '';
03593             $ret .= $ch; // add to result string
03594             if ( $ch == '<' ) {
03595                 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
03596                 $entityState = 0; // for bad HTML
03597                 $bracketState = 1; // tag started (checking for backslash)
03598             } elseif ( $ch == '>' ) {
03599                 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
03600                 $entityState = 0; // for bad HTML
03601                 $bracketState = 0; // out of brackets
03602             } elseif ( $bracketState == 1 ) {
03603                 if ( $ch == '/' ) {
03604                     $tagType = 1; // close tag (e.g. "</span>")
03605                 } else {
03606                     $tagType = 0; // open tag (e.g. "<span>")
03607                     $tag .= $ch;
03608                 }
03609                 $bracketState = 2; // building tag name
03610             } elseif ( $bracketState == 2 ) {
03611                 if ( $ch != ' ' ) {
03612                     $tag .= $ch;
03613                 } else {
03614                     // Name found (e.g. "<a href=..."), add on tag attributes...
03615                     $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
03616                 }
03617             } elseif ( $bracketState == 0 ) {
03618                 if ( $entityState ) {
03619                     if ( $ch == ';' ) {
03620                         $entityState = 0;
03621                         $dispLen++; // entity is one displayed char
03622                     }
03623                 } else {
03624                     if ( $neLength == 0 && !$maybeState ) {
03625                         // Save state without $ch. We want to *hit* the first
03626                         // display char (to get tags) but not *use* it if truncating.
03627                         $maybeState = array( substr( $ret, 0, -1 ), $openTags );
03628                     }
03629                     if ( $ch == '&' ) {
03630                         $entityState = 1; // entity found, (e.g. "&#160;")
03631                     } else {
03632                         $dispLen++; // this char is displayed
03633                         // Add the next $max display text chars after this in one swoop...
03634                         $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
03635                         $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
03636                         $dispLen += $skipped;
03637                         $pos += $skipped;
03638                     }
03639                 }
03640             }
03641         }
03642         // Close the last tag if left unclosed by bad HTML
03643         $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
03644         while ( count( $openTags ) > 0 ) {
03645             $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
03646         }
03647         return $ret;
03648     }
03649 
03661     private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
03662         if ( $len === null ) {
03663             $len = -1; // -1 means "no limit" for strcspn
03664         } elseif ( $len < 0 ) {
03665             $len = 0; // sanity
03666         }
03667         $skipCount = 0;
03668         if ( $start < strlen( $text ) ) {
03669             $skipCount = strcspn( $text, $search, $start, $len );
03670             $ret .= substr( $text, $start, $skipCount );
03671         }
03672         return $skipCount;
03673     }
03674 
03684     private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
03685         $tag = ltrim( $tag );
03686         if ( $tag != '' ) {
03687             if ( $tagType == 0 && $lastCh != '/' ) {
03688                 $openTags[] = $tag; // tag opened (didn't close itself)
03689             } elseif ( $tagType == 1 ) {
03690                 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
03691                     array_pop( $openTags ); // tag closed
03692                 }
03693             }
03694             $tag = '';
03695         }
03696     }
03697 
03706     function convertGrammar( $word, $case ) {
03707         global $wgGrammarForms;
03708         if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
03709             return $wgGrammarForms[$this->getCode()][$case][$word];
03710         }
03711 
03712         return $word;
03713     }
03719     function getGrammarForms() {
03720         global $wgGrammarForms;
03721         if ( isset( $wgGrammarForms[$this->getCode()] )
03722             && is_array( $wgGrammarForms[$this->getCode()] )
03723         ) {
03724             return $wgGrammarForms[$this->getCode()];
03725         }
03726 
03727         return array();
03728     }
03748     function gender( $gender, $forms ) {
03749         if ( !count( $forms ) ) {
03750             return '';
03751         }
03752         $forms = $this->preConvertPlural( $forms, 2 );
03753         if ( $gender === 'male' ) {
03754             return $forms[0];
03755         }
03756         if ( $gender === 'female' ) {
03757             return $forms[1];
03758         }
03759         return isset( $forms[2] ) ? $forms[2] : $forms[0];
03760     }
03761 
03777     function convertPlural( $count, $forms ) {
03778         // Handle explicit n=pluralform cases
03779         $forms = $this->handleExplicitPluralForms( $count, $forms );
03780         if ( is_string( $forms ) ) {
03781             return $forms;
03782         }
03783         if ( !count( $forms ) ) {
03784             return '';
03785         }
03786 
03787         $pluralForm = $this->getPluralRuleIndexNumber( $count );
03788         $pluralForm = min( $pluralForm, count( $forms ) - 1 );
03789         return $forms[$pluralForm];
03790     }
03791 
03807     protected function handleExplicitPluralForms( $count, array $forms ) {
03808         foreach ( $forms as $index => $form ) {
03809             if ( preg_match( '/\d+=/i', $form ) ) {
03810                 $pos = strpos( $form, '=' );
03811                 if ( substr( $form, 0, $pos ) === (string)$count ) {
03812                     return substr( $form, $pos + 1 );
03813                 }
03814                 unset( $forms[$index] );
03815             }
03816         }
03817         return array_values( $forms );
03818     }
03819 
03828     protected function preConvertPlural( /* Array */ $forms, $count ) {
03829         while ( count( $forms ) < $count ) {
03830             $forms[] = $forms[count( $forms ) - 1];
03831         }
03832         return $forms;
03833     }
03834 
03846     function translateBlockExpiry( $str ) {
03847         $duration = SpecialBlock::getSuggestedDurations( $this );
03848         foreach ( $duration as $show => $value ) {
03849             if ( strcmp( $str, $value ) == 0 ) {
03850                 return htmlspecialchars( trim( $show ) );
03851             }
03852         }
03853 
03854         // Since usually only infinite or indefinite is only on list, so try
03855         // equivalents if still here.
03856         $indefs = array( 'infinite', 'infinity', 'indefinite' );
03857         if ( in_array( $str, $indefs ) ) {
03858             foreach ( $indefs as $val ) {
03859                 $show = array_search( $val, $duration, true );
03860                 if ( $show !== false ) {
03861                     return htmlspecialchars( trim( $show ) );
03862                 }
03863             }
03864         }
03865 
03866         // If all else fails, return a standard duration or timestamp description.
03867         $time = strtotime( $str, 0 );
03868         if ( $time === false ) { // Unknown format. Return it as-is in case.
03869             return $str;
03870         } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp.
03871             // $time is relative to 0 so it's a duration length.
03872             return $this->formatDuration( $time );
03873         } else { // It's an absolute timestamp.
03874             if ( $time === 0 ) {
03875                 // wfTimestamp() handles 0 as current time instead of epoch.
03876                 return $this->timeanddate( '19700101000000' );
03877             } else {
03878                 return $this->timeanddate( $time );
03879             }
03880         }
03881     }
03882 
03890     public function segmentForDiff( $text ) {
03891         return $text;
03892     }
03893 
03900     public function unsegmentForDiff( $text ) {
03901         return $text;
03902     }
03903 
03910     public function getConverter() {
03911         return $this->mConverter;
03912     }
03913 
03920     public function autoConvertToAllVariants( $text ) {
03921         return $this->mConverter->autoConvertToAllVariants( $text );
03922     }
03923 
03930     public function convert( $text ) {
03931         return $this->mConverter->convert( $text );
03932     }
03933 
03940     public function convertTitle( $title ) {
03941         return $this->mConverter->convertTitle( $title );
03942     }
03943 
03950     public function convertNamespace( $ns ) {
03951         return $this->mConverter->convertNamespace( $ns );
03952     }
03953 
03959     public function hasVariants() {
03960         return count( $this->getVariants() ) > 1;
03961     }
03962 
03970     public function hasVariant( $variant ) {
03971         return (bool)$this->mConverter->validateVariant( $variant );
03972     }
03973 
03981     public function armourMath( $text ) {
03982         return $this->mConverter->armourMath( $text );
03983     }
03984 
03992     public function convertHtml( $text, $isTitle = false ) {
03993         return htmlspecialchars( $this->convert( $text, $isTitle ) );
03994     }
03995 
04000     public function convertCategoryKey( $key ) {
04001         return $this->mConverter->convertCategoryKey( $key );
04002     }
04003 
04010     public function getVariants() {
04011         return $this->mConverter->getVariants();
04012     }
04013 
04017     public function getPreferredVariant() {
04018         return $this->mConverter->getPreferredVariant();
04019     }
04020 
04024     public function getDefaultVariant() {
04025         return $this->mConverter->getDefaultVariant();
04026     }
04027 
04031     public function getURLVariant() {
04032         return $this->mConverter->getURLVariant();
04033     }
04034 
04047     public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
04048         $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
04049     }
04050 
04057     function getExtraHashOptions() {
04058         return $this->mConverter->getExtraHashOptions();
04059     }
04060 
04068     public function getParsedTitle() {
04069         return $this->mConverter->getParsedTitle();
04070     }
04071 
04084     public function markNoConversion( $text, $noParse = false ) {
04085         // Excluding protocal-relative URLs may avoid many false positives.
04086         if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
04087             return $this->mConverter->markNoConversion( $text );
04088         } else {
04089             return $text;
04090         }
04091     }
04092 
04099     public function linkTrail() {
04100         return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
04101     }
04102 
04109     public function linkPrefixCharset() {
04110         return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
04111     }
04112 
04117     function getLangObj() {
04118         wfDeprecated( __METHOD__, '1.24' );
04119         return $this;
04120     }
04121 
04129     public function getParentLanguage() {
04130         if ( $this->mParentLanguage !== false ) {
04131             return $this->mParentLanguage;
04132         }
04133 
04134         $pieces = explode( '-', $this->getCode() );
04135         $code = $pieces[0];
04136         if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
04137             $this->mParentLanguage = null;
04138             return null;
04139         }
04140         $lang = Language::factory( $code );
04141         if ( !$lang->hasVariant( $this->getCode() ) ) {
04142             $this->mParentLanguage = null;
04143             return null;
04144         }
04145 
04146         $this->mParentLanguage = $lang;
04147         return $lang;
04148     }
04149 
04158     public function getCode() {
04159         return $this->mCode;
04160     }
04161 
04172     public function getHtmlCode() {
04173         if ( is_null( $this->mHtmlCode ) ) {
04174             $this->mHtmlCode = wfBCP47( $this->getCode() );
04175         }
04176         return $this->mHtmlCode;
04177     }
04178 
04182     public function setCode( $code ) {
04183         $this->mCode = $code;
04184         // Ensure we don't leave incorrect cached data lying around
04185         $this->mHtmlCode = null;
04186         $this->mParentLanguage = false;
04187     }
04188 
04197     public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
04198         if ( !self::isValidBuiltInCode( $code ) ) {
04199             throw new MWException( "Invalid language code \"$code\"" );
04200         }
04201 
04202         return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
04203     }
04204 
04212     public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
04213         $m = null;
04214         preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
04215             preg_quote( $suffix, '/' ) . '/', $filename, $m );
04216         if ( !count( $m ) ) {
04217             return false;
04218         }
04219         return str_replace( '_', '-', strtolower( $m[1] ) );
04220     }
04221 
04226     public static function getMessagesFileName( $code ) {
04227         global $IP;
04228         $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
04229         wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) );
04230         return $file;
04231     }
04232 
04238     public static function getJsonMessagesFileName( $code ) {
04239         global $IP;
04240 
04241         if ( !self::isValidBuiltInCode( $code ) ) {
04242             throw new MWException( "Invalid language code \"$code\"" );
04243         }
04244 
04245         return "$IP/languages/i18n/$code.json";
04246     }
04247 
04252     public static function getClassFileName( $code ) {
04253         global $IP;
04254         return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
04255     }
04256 
04264     public static function getFallbackFor( $code ) {
04265         if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
04266             return false;
04267         } else {
04268             $fallbacks = self::getFallbacksFor( $code );
04269             $first = array_shift( $fallbacks );
04270             return $first;
04271         }
04272     }
04273 
04281     public static function getFallbacksFor( $code ) {
04282         if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
04283             return array();
04284         } else {
04285             $v = self::getLocalisationCache()->getItem( $code, 'fallback' );
04286             $v = array_map( 'trim', explode( ',', $v ) );
04287             if ( $v[count( $v ) - 1] !== 'en' ) {
04288                 $v[] = 'en';
04289             }
04290             return $v;
04291         }
04292     }
04293 
04302     public static function getFallbacksIncludingSiteLanguage( $code ) {
04303         global $wgLanguageCode;
04304 
04305         // Usually, we will only store a tiny number of fallback chains, so we
04306         // keep them in static memory.
04307         $cacheKey = "{$code}-{$wgLanguageCode}";
04308 
04309         if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
04310             $fallbacks = self::getFallbacksFor( $code );
04311 
04312             // Append the site's fallback chain, including the site language itself
04313             $siteFallbacks = self::getFallbacksFor( $wgLanguageCode );
04314             array_unshift( $siteFallbacks, $wgLanguageCode );
04315 
04316             // Eliminate any languages already included in the chain
04317             $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
04318 
04319             self::$fallbackLanguageCache[$cacheKey] = array( $fallbacks, $siteFallbacks );
04320         }
04321         return self::$fallbackLanguageCache[$cacheKey];
04322     }
04323 
04333     public static function getMessagesFor( $code ) {
04334         return self::getLocalisationCache()->getItem( $code, 'messages' );
04335     }
04336 
04345     public static function getMessageFor( $key, $code ) {
04346         return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
04347     }
04348 
04357     public static function getMessageKeysFor( $code ) {
04358         return self::getLocalisationCache()->getSubItemList( $code, 'messages' );
04359     }
04360 
04365     function fixVariableInNamespace( $talk ) {
04366         if ( strpos( $talk, '$1' ) === false ) {
04367             return $talk;
04368         }
04369 
04370         global $wgMetaNamespace;
04371         $talk = str_replace( '$1', $wgMetaNamespace, $talk );
04372 
04373         # Allow grammar transformations
04374         # Allowing full message-style parsing would make simple requests
04375         # such as action=raw much more expensive than they need to be.
04376         # This will hopefully cover most cases.
04377         $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
04378             array( &$this, 'replaceGrammarInNamespace' ), $talk );
04379         return str_replace( ' ', '_', $talk );
04380     }
04381 
04386     function replaceGrammarInNamespace( $m ) {
04387         return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
04388     }
04389 
04394     static function getCaseMaps() {
04395         static $wikiUpperChars, $wikiLowerChars;
04396         if ( isset( $wikiUpperChars ) ) {
04397             return array( $wikiUpperChars, $wikiLowerChars );
04398         }
04399 
04400         wfProfileIn( __METHOD__ );
04401         $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
04402         if ( $arr === false ) {
04403             throw new MWException(
04404                 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
04405         }
04406         $wikiUpperChars = $arr['wikiUpperChars'];
04407         $wikiLowerChars = $arr['wikiLowerChars'];
04408         wfProfileOut( __METHOD__ );
04409         return array( $wikiUpperChars, $wikiLowerChars );
04410     }
04411 
04423     public function formatExpiry( $expiry, $format = true ) {
04424         static $infinity;
04425         if ( $infinity === null ) {
04426             $infinity = wfGetDB( DB_SLAVE )->getInfinity();
04427         }
04428 
04429         if ( $expiry == '' || $expiry == $infinity ) {
04430             return $format === true
04431                 ? $this->getMessageFromDB( 'infiniteblock' )
04432                 : $infinity;
04433         } else {
04434             return $format === true
04435                 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
04436                 : wfTimestamp( $format, $expiry );
04437         }
04438     }
04439 
04452     function formatTimePeriod( $seconds, $format = array() ) {
04453         if ( !is_array( $format ) ) {
04454             $format = array( 'avoid' => $format ); // For backwards compatibility
04455         }
04456         if ( !isset( $format['avoid'] ) ) {
04457             $format['avoid'] = false;
04458         }
04459         if ( !isset( $format['noabbrevs'] ) ) {
04460             $format['noabbrevs'] = false;
04461         }
04462         $secondsMsg = wfMessage(
04463             $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
04464         $minutesMsg = wfMessage(
04465             $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
04466         $hoursMsg = wfMessage(
04467             $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
04468         $daysMsg = wfMessage(
04469             $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
04470 
04471         if ( round( $seconds * 10 ) < 100 ) {
04472             $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
04473             $s = $secondsMsg->params( $s )->text();
04474         } elseif ( round( $seconds ) < 60 ) {
04475             $s = $this->formatNum( round( $seconds ) );
04476             $s = $secondsMsg->params( $s )->text();
04477         } elseif ( round( $seconds ) < 3600 ) {
04478             $minutes = floor( $seconds / 60 );
04479             $secondsPart = round( fmod( $seconds, 60 ) );
04480             if ( $secondsPart == 60 ) {
04481                 $secondsPart = 0;
04482                 $minutes++;
04483             }
04484             $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04485             $s .= ' ';
04486             $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
04487         } elseif ( round( $seconds ) <= 2 * 86400 ) {
04488             $hours = floor( $seconds / 3600 );
04489             $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
04490             $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
04491             if ( $secondsPart == 60 ) {
04492                 $secondsPart = 0;
04493                 $minutes++;
04494             }
04495             if ( $minutes == 60 ) {
04496                 $minutes = 0;
04497                 $hours++;
04498             }
04499             $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
04500             $s .= ' ';
04501             $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04502             if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) {
04503                 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
04504             }
04505         } else {
04506             $days = floor( $seconds / 86400 );
04507             if ( $format['avoid'] === 'avoidminutes' ) {
04508                 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
04509                 if ( $hours == 24 ) {
04510                     $hours = 0;
04511                     $days++;
04512                 }
04513                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04514                 $s .= ' ';
04515                 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
04516             } elseif ( $format['avoid'] === 'avoidseconds' ) {
04517                 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
04518                 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
04519                 if ( $minutes == 60 ) {
04520                     $minutes = 0;
04521                     $hours++;
04522                 }
04523                 if ( $hours == 24 ) {
04524                     $hours = 0;
04525                     $days++;
04526                 }
04527                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04528                 $s .= ' ';
04529                 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
04530                 $s .= ' ';
04531                 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04532             } else {
04533                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04534                 $s .= ' ';
04535                 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
04536             }
04537         }
04538         return $s;
04539     }
04540 
04552     function formatBitrate( $bps ) {
04553         return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
04554     }
04555 
04562     function formatComputingNumbers( $size, $boundary, $messageKey ) {
04563         if ( $size <= 0 ) {
04564             return str_replace( '$1', $this->formatNum( $size ),
04565                 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
04566             );
04567         }
04568         $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' );
04569         $index = 0;
04570 
04571         $maxIndex = count( $sizes ) - 1;
04572         while ( $size >= $boundary && $index < $maxIndex ) {
04573             $index++;
04574             $size /= $boundary;
04575         }
04576 
04577         // For small sizes no decimal places necessary
04578         $round = 0;
04579         if ( $index > 1 ) {
04580             // For MB and bigger two decimal places are smarter
04581             $round = 2;
04582         }
04583         $msg = str_replace( '$1', $sizes[$index], $messageKey );
04584 
04585         $size = round( $size, $round );
04586         $text = $this->getMessageFromDB( $msg );
04587         return str_replace( '$1', $this->formatNum( $size ), $text );
04588     }
04589 
04600     function formatSize( $size ) {
04601         return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
04602     }
04603 
04613     function specialList( $page, $details, $oppositedm = true ) {
04614         $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) .
04615             $this->getDirMark();
04616         $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) .
04617             wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : '';
04618         return $page . $details;
04619     }
04620 
04631     public function viewPrevNext( Title $title, $offset, $limit,
04632         array $query = array(), $atend = false
04633     ) {
04634         // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
04635 
04636         # Make 'previous' link
04637         $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04638         if ( $offset > 0 ) {
04639             $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
04640                 $query, $prev, 'prevn-title', 'mw-prevlink' );
04641         } else {
04642             $plink = htmlspecialchars( $prev );
04643         }
04644 
04645         # Make 'next' link
04646         $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04647         if ( $atend ) {
04648             $nlink = htmlspecialchars( $next );
04649         } else {
04650             $nlink = $this->numLink( $title, $offset + $limit, $limit,
04651                 $query, $next, 'nextn-title', 'mw-nextlink' );
04652         }
04653 
04654         # Make links to set number of items per page
04655         $numLinks = array();
04656         foreach ( array( 20, 50, 100, 250, 500 ) as $num ) {
04657             $numLinks[] = $this->numLink( $title, $offset, $num,
04658                 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
04659         }
04660 
04661         return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
04662             )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
04663     }
04664 
04677     private function numLink( Title $title, $offset, $limit, array $query, $link,
04678         $tooltipMsg, $class
04679     ) {
04680         $query = array( 'limit' => $limit, 'offset' => $offset ) + $query;
04681         $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )
04682             ->numParams( $limit )->text();
04683 
04684         return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ),
04685             'title' => $tooltip, 'class' => $class ), $link );
04686     }
04687 
04693     public function getConvRuleTitle() {
04694         return $this->mConverter->getConvRuleTitle();
04695     }
04696 
04702     public function getCompiledPluralRules() {
04703         $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
04704         $fallbacks = Language::getFallbacksFor( $this->mCode );
04705         if ( !$pluralRules ) {
04706             foreach ( $fallbacks as $fallbackCode ) {
04707                 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
04708                 if ( $pluralRules ) {
04709                     break;
04710                 }
04711             }
04712         }
04713         return $pluralRules;
04714     }
04715 
04721     public function getPluralRules() {
04722         $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
04723         $fallbacks = Language::getFallbacksFor( $this->mCode );
04724         if ( !$pluralRules ) {
04725             foreach ( $fallbacks as $fallbackCode ) {
04726                 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
04727                 if ( $pluralRules ) {
04728                     break;
04729                 }
04730             }
04731         }
04732         return $pluralRules;
04733     }
04734 
04740     public function getPluralRuleTypes() {
04741         $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' );
04742         $fallbacks = Language::getFallbacksFor( $this->mCode );
04743         if ( !$pluralRuleTypes ) {
04744             foreach ( $fallbacks as $fallbackCode ) {
04745                 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' );
04746                 if ( $pluralRuleTypes ) {
04747                     break;
04748                 }
04749             }
04750         }
04751         return $pluralRuleTypes;
04752     }
04753 
04759     public function getPluralRuleIndexNumber( $number ) {
04760         $pluralRules = $this->getCompiledPluralRules();
04761         $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules );
04762         return $form;
04763     }
04764 
04773     public function getPluralRuleType( $number ) {
04774         $index = $this->getPluralRuleIndexNumber( $number );
04775         $pluralRuleTypes = $this->getPluralRuleTypes();
04776         if ( isset( $pluralRuleTypes[$index] ) ) {
04777             return $pluralRuleTypes[$index];
04778         } else {
04779             return 'other';
04780         }
04781     }
04782 }