MediaWiki  REL1_21
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 # Read language names
00034 global $wgLanguageNames;
00035 require_once( __DIR__ . '/Names.php' );
00036 
00037 if ( function_exists( 'mb_strtoupper' ) ) {
00038         mb_internal_encoding( 'UTF-8' );
00039 }
00040 
00046 class FakeConverter {
00047 
00051         public $mLang;
00052         function __construct( $langobj ) { $this->mLang = $langobj; }
00053         function autoConvertToAllVariants( $text ) { return array( $this->mLang->getCode() => $text ); }
00054         function convert( $t ) { return $t; }
00055         function convertTo( $text, $variant ) { return $text; }
00056         function convertTitle( $t ) { return $t->getPrefixedText(); }
00057         function convertNamespace( $ns ) { return $this->mLang->getFormattedNsText( $ns ); }
00058         function getVariants() { return array( $this->mLang->getCode() ); }
00059         function getPreferredVariant() { return $this->mLang->getCode(); }
00060         function getDefaultVariant() { return $this->mLang->getCode(); }
00061         function getURLVariant() { return ''; }
00062         function getConvRuleTitle() { return false; }
00063         function findVariantLink( &$l, &$n, $ignoreOtherCond = false ) { }
00064         function getExtraHashOptions() { return ''; }
00065         function getParsedTitle() { return ''; }
00066         function markNoConversion( $text, $noParse = false ) { return $text; }
00067         function convertCategoryKey( $key ) { return $key; }
00068         function convertLinkToAllVariants( $text ) { return $this->autoConvertToAllVariants( $text ); }
00069         function armourMath( $text ) { return $text; }
00070 }
00071 
00076 class Language {
00077 
00081         public $mConverter;
00082 
00083         public $mVariants, $mCode, $mLoaded = false;
00084         public $mMagicExtensions = array(), $mMagicHookDone = false;
00085         private $mHtmlCode = null;
00086 
00087         public $dateFormatStrings = array();
00088         public $mExtendedSpecialPageAliases;
00089 
00090         protected $namespaceNames, $mNamespaceIds, $namespaceAliases;
00091 
00095         public $transformData = array();
00096 
00100         static public $dataCache;
00101 
00102         static public $mLangObjCache = array();
00103 
00104         static public $mWeekdayMsgs = array(
00105                 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
00106                 'friday', 'saturday'
00107         );
00108 
00109         static public $mWeekdayAbbrevMsgs = array(
00110                 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
00111         );
00112 
00113         static public $mMonthMsgs = array(
00114                 'january', 'february', 'march', 'april', 'may_long', 'june',
00115                 'july', 'august', 'september', 'october', 'november',
00116                 'december'
00117         );
00118         static public $mMonthGenMsgs = array(
00119                 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
00120                 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
00121                 'december-gen'
00122         );
00123         static public $mMonthAbbrevMsgs = array(
00124                 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
00125                 'sep', 'oct', 'nov', 'dec'
00126         );
00127 
00128         static public $mIranianCalendarMonthMsgs = array(
00129                 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
00130                 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
00131                 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
00132                 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
00133         );
00134 
00135         static public $mHebrewCalendarMonthMsgs = array(
00136                 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
00137                 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
00138                 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
00139                 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
00140                 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
00141         );
00142 
00143         static public $mHebrewCalendarMonthGenMsgs = array(
00144                 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
00145                 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
00146                 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
00147                 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
00148                 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
00149         );
00150 
00151         static public $mHijriCalendarMonthMsgs = array(
00152                 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
00153                 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
00154                 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
00155                 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
00156         );
00157 
00162         static public $durationIntervals = array(
00163                 'millennia' => 31556952000,
00164                 'centuries' => 3155695200,
00165                 'decades' => 315569520,
00166                 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 )
00167                 'weeks' => 604800,
00168                 'days' => 86400,
00169                 'hours' => 3600,
00170                 'minutes' => 60,
00171                 'seconds' => 1,
00172         );
00173 
00179         static function factory( $code ) {
00180                 global $wgDummyLanguageCodes, $wgLangObjCacheSize;
00181 
00182                 if ( isset( $wgDummyLanguageCodes[$code] ) ) {
00183                         $code = $wgDummyLanguageCodes[$code];
00184                 }
00185 
00186                 // get the language object to process
00187                 $langObj = isset( self::$mLangObjCache[$code] )
00188                         ? self::$mLangObjCache[$code]
00189                         : self::newFromCode( $code );
00190 
00191                 // merge the language object in to get it up front in the cache
00192                 self::$mLangObjCache = array_merge( array( $code => $langObj ), self::$mLangObjCache );
00193                 // get rid of the oldest ones in case we have an overflow
00194                 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true );
00195 
00196                 return $langObj;
00197         }
00198 
00205         protected static function newFromCode( $code ) {
00206                 // Protect against path traversal below
00207                 if ( !Language::isValidCode( $code )
00208                         || strcspn( $code, ":/\\\000" ) !== strlen( $code ) )
00209                 {
00210                         throw new MWException( "Invalid language code \"$code\"" );
00211                 }
00212 
00213                 if ( !Language::isValidBuiltInCode( $code ) ) {
00214                         // It's not possible to customise this code with class files, so
00215                         // just return a Language object. This is to support uselang= hacks.
00216                         $lang = new Language;
00217                         $lang->setCode( $code );
00218                         return $lang;
00219                 }
00220 
00221                 // Check if there is a language class for the code
00222                 $class = self::classFromCode( $code );
00223                 self::preloadLanguageClass( $class );
00224                 if ( MWInit::classExists( $class ) ) {
00225                         $lang = new $class;
00226                         return $lang;
00227                 }
00228 
00229                 // Keep trying the fallback list until we find an existing class
00230                 $fallbacks = Language::getFallbacksFor( $code );
00231                 foreach ( $fallbacks as $fallbackCode ) {
00232                         if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
00233                                 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
00234                         }
00235 
00236                         $class = self::classFromCode( $fallbackCode );
00237                         self::preloadLanguageClass( $class );
00238                         if ( MWInit::classExists( $class ) ) {
00239                                 $lang = Language::newFromCode( $fallbackCode );
00240                                 $lang->setCode( $code );
00241                                 return $lang;
00242                         }
00243                 }
00244 
00245                 throw new MWException( "Invalid fallback sequence for language '$code'" );
00246         }
00247 
00256         public static function isSupportedLanguage( $code ) {
00257                 return $code === strtolower( $code ) && is_readable( self::getMessagesFileName( $code ) );
00258         }
00259 
00275         public static function isWellFormedLanguageTag( $code, $lenient = false ) {
00276                 $alpha = '[a-z]';
00277                 $digit = '[0-9]';
00278                 $alphanum = '[a-z0-9]';
00279                 $x = 'x' ; # private use singleton
00280                 $singleton = '[a-wy-z]'; # other singleton
00281                 $s = $lenient ? '[-_]' : '-';
00282 
00283                 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
00284                 $script = "$alpha{4}"; # ISO 15924
00285                 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
00286                 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})";
00287                 $extension = "$singleton(?:$s$alphanum{2,8})+";
00288                 $privateUse = "$x(?:$s$alphanum{1,8})+";
00289 
00290                 # Define certain grandfathered codes, since otherwise the regex is pretty useless.
00291                 # Since these are limited, this is safe even later changes to the registry --
00292                 # the only oddity is that it might change the type of the tag, and thus
00293                 # the results from the capturing groups.
00294                 # http://www.iana.org/assignments/language-subtag-registry
00295 
00296                 $grandfathered = "en{$s}GB{$s}oed"
00297                         . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
00298                         . "|no{$s}(?:bok|nyn)"
00299                         . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
00300                         . "|zh{$s}min{$s}nan";
00301 
00302                 $variantList = "$variant(?:$s$variant)*";
00303                 $extensionList = "$extension(?:$s$extension)*";
00304 
00305                 $langtag = "(?:($language)"
00306                         . "(?:$s$script)?"
00307                         . "(?:$s$region)?"
00308                         . "(?:$s$variantList)?"
00309                         . "(?:$s$extensionList)?"
00310                         . "(?:$s$privateUse)?)";
00311 
00312                 # The final breakdown, with capturing groups for each of these components
00313                 # The variants, extensions, grandfathered, and private-use may have interior '-'
00314 
00315                 $root = "^(?:$langtag|$privateUse|$grandfathered)$";
00316 
00317                 return (bool)preg_match( "/$root/", strtolower( $code ) );
00318         }
00319 
00329         public static function isValidCode( $code ) {
00330                 return
00331                         // People think language codes are html safe, so enforce it.
00332                         // Ideally we should only allow a-zA-Z0-9-
00333                         // but, .+ and other chars are often used for {{int:}} hacks
00334                         // see bugs 37564, 37587, 36938
00335                         strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
00336                         && !preg_match( Title::getTitleInvalidRegex(), $code );
00337         }
00338 
00349         public static function isValidBuiltInCode( $code ) {
00350 
00351                 if ( !is_string( $code ) ) {
00352                         $type = gettype( $code );
00353                         if ( $type === 'object' ) {
00354                                 $addmsg = " of class " . get_class( $code );
00355                         } else {
00356                                 $addmsg = '';
00357                         }
00358                         throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
00359                 }
00360 
00361                 return (bool)preg_match( '/^[a-z0-9-]+$/i', $code );
00362         }
00363 
00372         public static function isKnownLanguageTag( $tag ) {
00373                 static $coreLanguageNames;
00374 
00375                 if ( $coreLanguageNames === null ) {
00376                         include( MWInit::compiledPath( 'languages/Names.php' ) );
00377                 }
00378 
00379                 if ( isset( $coreLanguageNames[$tag] )
00380                         || self::fetchLanguageName( $tag, $tag ) !== ''
00381                 ) {
00382                         return true;
00383                 }
00384 
00385                 return false;
00386         }
00387 
00392         public static function classFromCode( $code ) {
00393                 if ( $code == 'en' ) {
00394                         return 'Language';
00395                 } else {
00396                         return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
00397                 }
00398         }
00399 
00405         public static function preloadLanguageClass( $class ) {
00406                 global $IP;
00407 
00408                 if ( $class === 'Language' ) {
00409                         return;
00410                 }
00411 
00412                 if ( !defined( 'MW_COMPILED' ) ) {
00413                         if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
00414                                 include_once( "$IP/languages/classes/$class.php" );
00415                         }
00416                 }
00417         }
00418 
00424         public static function getLocalisationCache() {
00425                 if ( is_null( self::$dataCache ) ) {
00426                         global $wgLocalisationCacheConf;
00427                         $class = $wgLocalisationCacheConf['class'];
00428                         self::$dataCache = new $class( $wgLocalisationCacheConf );
00429                 }
00430                 return self::$dataCache;
00431         }
00432 
00433         function __construct() {
00434                 $this->mConverter = new FakeConverter( $this );
00435                 // Set the code to the name of the descendant
00436                 if ( get_class( $this ) == 'Language' ) {
00437                         $this->mCode = 'en';
00438                 } else {
00439                         $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
00440                 }
00441                 self::getLocalisationCache();
00442         }
00443 
00447         function __destruct() {
00448                 foreach ( $this as $name => $value ) {
00449                         unset( $this->$name );
00450                 }
00451         }
00452 
00457         function initContLang() { }
00458 
00464         function getFallbackLanguageCode() {
00465                 wfDeprecated( __METHOD__, '1.19' );
00466                 return self::getFallbackFor( $this->mCode );
00467         }
00468 
00473         function getFallbackLanguages() {
00474                 return self::getFallbacksFor( $this->mCode );
00475         }
00476 
00481         function getBookstoreList() {
00482                 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
00483         }
00484 
00488         public function getNamespaces() {
00489                 if ( is_null( $this->namespaceNames ) ) {
00490                         global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces;
00491 
00492                         $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
00493                         $validNamespaces = MWNamespace::getCanonicalNamespaces();
00494 
00495                         $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
00496 
00497                         $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
00498                         if ( $wgMetaNamespaceTalk ) {
00499                                 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
00500                         } else {
00501                                 $talk = $this->namespaceNames[NS_PROJECT_TALK];
00502                                 $this->namespaceNames[NS_PROJECT_TALK] =
00503                                         $this->fixVariableInNamespace( $talk );
00504                         }
00505 
00506                         # Sometimes a language will be localised but not actually exist on this wiki.
00507                         foreach ( $this->namespaceNames as $key => $text ) {
00508                                 if ( !isset( $validNamespaces[$key] ) ) {
00509                                         unset( $this->namespaceNames[$key] );
00510                                 }
00511                         }
00512 
00513                         # The above mixing may leave namespaces out of canonical order.
00514                         # Re-order by namespace ID number...
00515                         ksort( $this->namespaceNames );
00516 
00517                         wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) );
00518                 }
00519                 return $this->namespaceNames;
00520         }
00521 
00526         public function setNamespaces( array $namespaces ) {
00527                 $this->namespaceNames = $namespaces;
00528                 $this->mNamespaceIds = null;
00529         }
00530 
00534         public function resetNamespaces() {
00535                 $this->namespaceNames = null;
00536                 $this->mNamespaceIds = null;
00537                 $this->namespaceAliases = null;
00538         }
00539 
00548         function getFormattedNamespaces() {
00549                 $ns = $this->getNamespaces();
00550                 foreach ( $ns as $k => $v ) {
00551                         $ns[$k] = strtr( $v, '_', ' ' );
00552                 }
00553                 return $ns;
00554         }
00555 
00566         function getNsText( $index ) {
00567                 $ns = $this->getNamespaces();
00568                 return isset( $ns[$index] ) ? $ns[$index] : false;
00569         }
00570 
00584         function getFormattedNsText( $index ) {
00585                 $ns = $this->getNsText( $index );
00586                 return strtr( $ns, '_', ' ' );
00587         }
00588 
00596         function getGenderNsText( $index, $gender ) {
00597                 global $wgExtraGenderNamespaces;
00598 
00599                 $ns = $wgExtraGenderNamespaces + self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00600                 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
00601         }
00602 
00609         function needsGenderDistinction() {
00610                 global $wgExtraGenderNamespaces, $wgExtraNamespaces;
00611                 if ( count( $wgExtraGenderNamespaces ) > 0 ) {
00612                         // $wgExtraGenderNamespaces overrides everything
00613                         return true;
00614                 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
00616                         // $wgExtraNamespaces overrides any gender aliases specified in i18n files
00617                         return false;
00618                 } else {
00619                         // Check what is in i18n files
00620                         $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00621                         return count( $aliases ) > 0;
00622                 }
00623         }
00624 
00633         function getLocalNsIndex( $text ) {
00634                 $lctext = $this->lc( $text );
00635                 $ids = $this->getNamespaceIds();
00636                 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
00637         }
00638 
00642         function getNamespaceAliases() {
00643                 if ( is_null( $this->namespaceAliases ) ) {
00644                         $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
00645                         if ( !$aliases ) {
00646                                 $aliases = array();
00647                         } else {
00648                                 foreach ( $aliases as $name => $index ) {
00649                                         if ( $index === NS_PROJECT_TALK ) {
00650                                                 unset( $aliases[$name] );
00651                                                 $name = $this->fixVariableInNamespace( $name );
00652                                                 $aliases[$name] = $index;
00653                                         }
00654                                 }
00655                         }
00656 
00657                         global $wgExtraGenderNamespaces;
00658                         $genders = $wgExtraGenderNamespaces + (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00659                         foreach ( $genders as $index => $forms ) {
00660                                 foreach ( $forms as $alias ) {
00661                                         $aliases[$alias] = $index;
00662                                 }
00663                         }
00664 
00665                         $this->namespaceAliases = $aliases;
00666                 }
00667                 return $this->namespaceAliases;
00668         }
00669 
00673         function getNamespaceIds() {
00674                 if ( is_null( $this->mNamespaceIds ) ) {
00675                         global $wgNamespaceAliases;
00676                         # Put namespace names and aliases into a hashtable.
00677                         # If this is too slow, then we should arrange it so that it is done
00678                         # before caching. The catch is that at pre-cache time, the above
00679                         # class-specific fixup hasn't been done.
00680                         $this->mNamespaceIds = array();
00681                         foreach ( $this->getNamespaces() as $index => $name ) {
00682                                 $this->mNamespaceIds[$this->lc( $name )] = $index;
00683                         }
00684                         foreach ( $this->getNamespaceAliases() as $name => $index ) {
00685                                 $this->mNamespaceIds[$this->lc( $name )] = $index;
00686                         }
00687                         if ( $wgNamespaceAliases ) {
00688                                 foreach ( $wgNamespaceAliases as $name => $index ) {
00689                                         $this->mNamespaceIds[$this->lc( $name )] = $index;
00690                                 }
00691                         }
00692                 }
00693                 return $this->mNamespaceIds;
00694         }
00695 
00703         function getNsIndex( $text ) {
00704                 $lctext = $this->lc( $text );
00705                 $ns = MWNamespace::getCanonicalIndex( $lctext );
00706                 if ( $ns !== null ) {
00707                         return $ns;
00708                 }
00709                 $ids = $this->getNamespaceIds();
00710                 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
00711         }
00712 
00720         function getVariantname( $code, $usemsg = true ) {
00721                 $msg = "variantname-$code";
00722                 if ( $usemsg && wfMessage( $msg )->exists() ) {
00723                         return $this->getMessageFromDB( $msg );
00724                 }
00725                 $name = self::fetchLanguageName( $code );
00726                 if ( $name ) {
00727                         return $name; # if it's defined as a language name, show that
00728                 } else {
00729                         # otherwise, output the language code
00730                         return $code;
00731                 }
00732         }
00733 
00738         function specialPage( $name ) {
00739                 $aliases = $this->getSpecialPageAliases();
00740                 if ( isset( $aliases[$name][0] ) ) {
00741                         $name = $aliases[$name][0];
00742                 }
00743                 return $this->getNsText( NS_SPECIAL ) . ':' . $name;
00744         }
00745 
00749         function getQuickbarSettings() {
00750                 return array(
00751                         $this->getMessage( 'qbsettings-none' ),
00752                         $this->getMessage( 'qbsettings-fixedleft' ),
00753                         $this->getMessage( 'qbsettings-fixedright' ),
00754                         $this->getMessage( 'qbsettings-floatingleft' ),
00755                         $this->getMessage( 'qbsettings-floatingright' ),
00756                         $this->getMessage( 'qbsettings-directionality' )
00757                 );
00758         }
00759 
00763         function getDatePreferences() {
00764                 return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
00765         }
00766 
00770         function getDateFormats() {
00771                 return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
00772         }
00773 
00777         function getDefaultDateFormat() {
00778                 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
00779                 if ( $df === 'dmy or mdy' ) {
00780                         global $wgAmericanDates;
00781                         return $wgAmericanDates ? 'mdy' : 'dmy';
00782                 } else {
00783                         return $df;
00784                 }
00785         }
00786 
00790         function getDatePreferenceMigrationMap() {
00791                 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
00792         }
00793 
00798         function getImageFile( $image ) {
00799                 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
00800         }
00801 
00805         function getExtraUserToggles() {
00806                 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
00807         }
00808 
00813         function getUserToggle( $tog ) {
00814                 return $this->getMessageFromDB( "tog-$tog" );
00815         }
00816 
00827         public static function getLanguageNames( $customisedOnly = false ) {
00828                 return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' );
00829         }
00830 
00840         public static function getTranslatedLanguageNames( $code ) {
00841                 return self::fetchLanguageNames( $code, 'all' );
00842         }
00843 
00855         public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
00856                 global $wgExtraLanguageNames;
00857                 static $coreLanguageNames;
00858 
00859                 if ( $coreLanguageNames === null ) {
00860                         include( MWInit::compiledPath( 'languages/Names.php' ) );
00861                 }
00862 
00863                 $names = array();
00864 
00865                 if ( $inLanguage ) {
00866                         # TODO: also include when $inLanguage is null, when this code is more efficient
00867                         wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) );
00868                 }
00869 
00870                 $mwNames = $wgExtraLanguageNames + $coreLanguageNames;
00871                 foreach ( $mwNames as $mwCode => $mwName ) {
00872                         # - Prefer own MediaWiki native name when not using the hook
00873                         # - For other names just add if not added through the hook
00874                         if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
00875                                 $names[$mwCode] = $mwName;
00876                         }
00877                 }
00878 
00879                 if ( $include === 'all' ) {
00880                         return $names;
00881                 }
00882 
00883                 $returnMw = array();
00884                 $coreCodes = array_keys( $mwNames );
00885                 foreach ( $coreCodes as $coreCode ) {
00886                         $returnMw[$coreCode] = $names[$coreCode];
00887                 }
00888 
00889                 if ( $include === 'mwfile' ) {
00890                         $namesMwFile = array();
00891                         # We do this using a foreach over the codes instead of a directory
00892                         # loop so that messages files in extensions will work correctly.
00893                         foreach ( $returnMw as $code => $value ) {
00894                                 if ( is_readable( self::getMessagesFileName( $code ) ) ) {
00895                                         $namesMwFile[$code] = $names[$code];
00896                                 }
00897                         }
00898                         return $namesMwFile;
00899                 }
00900                 # 'mw' option; default if it's not one of the other two options (all/mwfile)
00901                 return $returnMw;
00902         }
00903 
00911         public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
00912                 $array = self::fetchLanguageNames( $inLanguage, $include );
00913                 return !array_key_exists( $code, $array ) ? '' : $array[$code];
00914         }
00915 
00922         function getMessageFromDB( $msg ) {
00923                 return wfMessage( $msg )->inLanguage( $this )->text();
00924         }
00925 
00933         function getLanguageName( $code ) {
00934                 return self::fetchLanguageName( $code );
00935         }
00936 
00941         function getMonthName( $key ) {
00942                 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
00943         }
00944 
00948         function getMonthNamesArray() {
00949                 $monthNames = array( '' );
00950                 for ( $i = 1; $i < 13; $i++ ) {
00951                         $monthNames[] = $this->getMonthName( $i );
00952                 }
00953                 return $monthNames;
00954         }
00955 
00960         function getMonthNameGen( $key ) {
00961                 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
00962         }
00963 
00968         function getMonthAbbreviation( $key ) {
00969                 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
00970         }
00971 
00975         function getMonthAbbreviationsArray() {
00976                 $monthNames = array( '' );
00977                 for ( $i = 1; $i < 13; $i++ ) {
00978                         $monthNames[] = $this->getMonthAbbreviation( $i );
00979                 }
00980                 return $monthNames;
00981         }
00982 
00987         function getWeekdayName( $key ) {
00988                 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
00989         }
00990 
00995         function getWeekdayAbbreviation( $key ) {
00996                 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
00997         }
00998 
01003         function getIranianCalendarMonthName( $key ) {
01004                 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
01005         }
01006 
01011         function getHebrewCalendarMonthName( $key ) {
01012                 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
01013         }
01014 
01019         function getHebrewCalendarMonthNameGen( $key ) {
01020                 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
01021         }
01022 
01027         function getHijriCalendarMonthName( $key ) {
01028                 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
01029         }
01030 
01093         function sprintfDate( $format, $ts ) {
01094                 $s = '';
01095                 $raw = false;
01096                 $roman = false;
01097                 $hebrewNum = false;
01098                 $unix = false;
01099                 $rawToggle = false;
01100                 $iranian = false;
01101                 $hebrew = false;
01102                 $hijri = false;
01103                 $thai = false;
01104                 $minguo = false;
01105                 $tenno = false;
01106                 for ( $p = 0; $p < strlen( $format ); $p++ ) {
01107                         $num = false;
01108                         $code = $format[$p];
01109                         if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
01110                                 $code .= $format[++$p];
01111                         }
01112 
01113                         if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' || $code == 'xo' || $code == 'xt' ) && $p < strlen( $format ) - 1 ) {
01114                                 $code .= $format[++$p];
01115                         }
01116 
01117                         switch ( $code ) {
01118                                 case 'xx':
01119                                         $s .= 'x';
01120                                         break;
01121                                 case 'xn':
01122                                         $raw = true;
01123                                         break;
01124                                 case 'xN':
01125                                         $rawToggle = !$rawToggle;
01126                                         break;
01127                                 case 'xr':
01128                                         $roman = true;
01129                                         break;
01130                                 case 'xh':
01131                                         $hebrewNum = true;
01132                                         break;
01133                                 case 'xg':
01134                                         $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
01135                                         break;
01136                                 case 'xjx':
01137                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
01138                                         $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
01139                                         break;
01140                                 case 'd':
01141                                         $num = substr( $ts, 6, 2 );
01142                                         break;
01143                                 case 'D':
01144                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
01145                                         $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) + 1 );
01146                                         break;
01147                                 case 'j':
01148                                         $num = intval( substr( $ts, 6, 2 ) );
01149                                         break;
01150                                 case 'xij':
01151                                         if ( !$iranian ) {
01152                                                 $iranian = self::tsToIranian( $ts );
01153                                         }
01154                                         $num = $iranian[2];
01155                                         break;
01156                                 case 'xmj':
01157                                         if ( !$hijri ) {
01158                                                 $hijri = self::tsToHijri( $ts );
01159                                         }
01160                                         $num = $hijri[2];
01161                                         break;
01162                                 case 'xjj':
01163                                         if ( !$hebrew ) {
01164                                                 $hebrew = self::tsToHebrew( $ts );
01165                                         }
01166                                         $num = $hebrew[2];
01167                                         break;
01168                                 case 'l':
01169                                         if ( !$unix ) {
01170                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01171                                         }
01172                                         $s .= $this->getWeekdayName( gmdate( 'w', $unix ) + 1 );
01173                                         break;
01174                                 case 'N':
01175                                         if ( !$unix ) {
01176                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01177                                         }
01178                                         $w = gmdate( 'w', $unix );
01179                                         $num = $w ? $w : 7;
01180                                         break;
01181                                 case 'w':
01182                                         if ( !$unix ) {
01183                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01184                                         }
01185                                         $num = gmdate( 'w', $unix );
01186                                         break;
01187                                 case 'z':
01188                                         if ( !$unix ) {
01189                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01190                                         }
01191                                         $num = gmdate( 'z', $unix );
01192                                         break;
01193                                 case 'W':
01194                                         if ( !$unix ) {
01195                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01196                                         }
01197                                         $num = gmdate( 'W', $unix );
01198                                         break;
01199                                 case 'F':
01200                                         $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
01201                                         break;
01202                                 case 'xiF':
01203                                         if ( !$iranian ) {
01204                                                 $iranian = self::tsToIranian( $ts );
01205                                         }
01206                                         $s .= $this->getIranianCalendarMonthName( $iranian[1] );
01207                                         break;
01208                                 case 'xmF':
01209                                         if ( !$hijri ) {
01210                                                 $hijri = self::tsToHijri( $ts );
01211                                         }
01212                                         $s .= $this->getHijriCalendarMonthName( $hijri[1] );
01213                                         break;
01214                                 case 'xjF':
01215                                         if ( !$hebrew ) {
01216                                                 $hebrew = self::tsToHebrew( $ts );
01217                                         }
01218                                         $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
01219                                         break;
01220                                 case 'm':
01221                                         $num = substr( $ts, 4, 2 );
01222                                         break;
01223                                 case 'M':
01224                                         $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
01225                                         break;
01226                                 case 'n':
01227                                         $num = intval( substr( $ts, 4, 2 ) );
01228                                         break;
01229                                 case 'xin':
01230                                         if ( !$iranian ) {
01231                                                 $iranian = self::tsToIranian( $ts );
01232                                         }
01233                                         $num = $iranian[1];
01234                                         break;
01235                                 case 'xmn':
01236                                         if ( !$hijri ) {
01237                                                 $hijri = self::tsToHijri ( $ts );
01238                                         }
01239                                         $num = $hijri[1];
01240                                         break;
01241                                 case 'xjn':
01242                                         if ( !$hebrew ) {
01243                                                 $hebrew = self::tsToHebrew( $ts );
01244                                         }
01245                                         $num = $hebrew[1];
01246                                         break;
01247                                 case 't':
01248                                         if ( !$unix ) {
01249                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01250                                         }
01251                                         $num = gmdate( 't', $unix );
01252                                         break;
01253                                 case 'xjt':
01254                                         if ( !$hebrew ) {
01255                                                 $hebrew = self::tsToHebrew( $ts );
01256                                         }
01257                                         $num = $hebrew[3];
01258                                         break;
01259                                 case 'L':
01260                                         if ( !$unix ) {
01261                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01262                                         }
01263                                         $num = gmdate( 'L', $unix );
01264                                         break;
01265                                 case 'o':
01266                                         if ( !$unix ) {
01267                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01268                                         }
01269                                         $num = gmdate( 'o', $unix );
01270                                         break;
01271                                 case 'Y':
01272                                         $num = substr( $ts, 0, 4 );
01273                                         break;
01274                                 case 'xiY':
01275                                         if ( !$iranian ) {
01276                                                 $iranian = self::tsToIranian( $ts );
01277                                         }
01278                                         $num = $iranian[0];
01279                                         break;
01280                                 case 'xmY':
01281                                         if ( !$hijri ) {
01282                                                 $hijri = self::tsToHijri( $ts );
01283                                         }
01284                                         $num = $hijri[0];
01285                                         break;
01286                                 case 'xjY':
01287                                         if ( !$hebrew ) {
01288                                                 $hebrew = self::tsToHebrew( $ts );
01289                                         }
01290                                         $num = $hebrew[0];
01291                                         break;
01292                                 case 'xkY':
01293                                         if ( !$thai ) {
01294                                                 $thai = self::tsToYear( $ts, 'thai' );
01295                                         }
01296                                         $num = $thai[0];
01297                                         break;
01298                                 case 'xoY':
01299                                         if ( !$minguo ) {
01300                                                 $minguo = self::tsToYear( $ts, 'minguo' );
01301                                         }
01302                                         $num = $minguo[0];
01303                                         break;
01304                                 case 'xtY':
01305                                         if ( !$tenno ) {
01306                                                 $tenno = self::tsToYear( $ts, 'tenno' );
01307                                         }
01308                                         $num = $tenno[0];
01309                                         break;
01310                                 case 'y':
01311                                         $num = substr( $ts, 2, 2 );
01312                                         break;
01313                                 case 'xiy':
01314                                         if ( !$iranian ) {
01315                                                 $iranian = self::tsToIranian( $ts );
01316                                         }
01317                                         $num = substr( $iranian[0], -2 );
01318                                         break;
01319                                 case 'a':
01320                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
01321                                         break;
01322                                 case 'A':
01323                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
01324                                         break;
01325                                 case 'g':
01326                                         $h = substr( $ts, 8, 2 );
01327                                         $num = $h % 12 ? $h % 12 : 12;
01328                                         break;
01329                                 case 'G':
01330                                         $num = intval( substr( $ts, 8, 2 ) );
01331                                         break;
01332                                 case 'h':
01333                                         $h = substr( $ts, 8, 2 );
01334                                         $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
01335                                         break;
01336                                 case 'H':
01337                                         $num = substr( $ts, 8, 2 );
01338                                         break;
01339                                 case 'i':
01340                                         $num = substr( $ts, 10, 2 );
01341                                         break;
01342                                 case 's':
01343                                         $num = substr( $ts, 12, 2 );
01344                                         break;
01345                                 case 'c':
01346                                         if ( !$unix ) {
01347                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01348                                         }
01349                                         $s .= gmdate( 'c', $unix );
01350                                         break;
01351                                 case 'r':
01352                                         if ( !$unix ) {
01353                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01354                                         }
01355                                         $s .= gmdate( 'r', $unix );
01356                                         break;
01357                                 case 'U':
01358                                         if ( !$unix ) {
01359                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01360                                         }
01361                                         $num = $unix;
01362                                         break;
01363                                 case '\\':
01364                                         # Backslash escaping
01365                                         if ( $p < strlen( $format ) - 1 ) {
01366                                                 $s .= $format[++$p];
01367                                         } else {
01368                                                 $s .= '\\';
01369                                         }
01370                                         break;
01371                                 case '"':
01372                                         # Quoted literal
01373                                         if ( $p < strlen( $format ) - 1 ) {
01374                                                 $endQuote = strpos( $format, '"', $p + 1 );
01375                                                 if ( $endQuote === false ) {
01376                                                         # No terminating quote, assume literal "
01377                                                         $s .= '"';
01378                                                 } else {
01379                                                         $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
01380                                                         $p = $endQuote;
01381                                                 }
01382                                         } else {
01383                                                 # Quote at end of string, assume literal "
01384                                                 $s .= '"';
01385                                         }
01386                                         break;
01387                                 default:
01388                                         $s .= $format[$p];
01389                         }
01390                         if ( $num !== false ) {
01391                                 if ( $rawToggle || $raw ) {
01392                                         $s .= $num;
01393                                         $raw = false;
01394                                 } elseif ( $roman ) {
01395                                         $s .= Language::romanNumeral( $num );
01396                                         $roman = false;
01397                                 } elseif ( $hebrewNum ) {
01398                                         $s .= self::hebrewNumeral( $num );
01399                                         $hebrewNum = false;
01400                                 } else {
01401                                         $s .= $this->formatNum( $num, true );
01402                                 }
01403                         }
01404                 }
01405                 return $s;
01406         }
01407 
01408         private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
01409         private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
01410 
01423         private static function tsToIranian( $ts ) {
01424                 $gy = substr( $ts, 0, 4 ) -1600;
01425                 $gm = substr( $ts, 4, 2 ) -1;
01426                 $gd = substr( $ts, 6, 2 ) -1;
01427 
01428                 # Days passed from the beginning (including leap years)
01429                 $gDayNo = 365 * $gy
01430                         + floor( ( $gy + 3 ) / 4 )
01431                         - floor( ( $gy + 99 ) / 100 )
01432                         + floor( ( $gy + 399 ) / 400 );
01433 
01434                 // Add days of the past months of this year
01435                 for ( $i = 0; $i < $gm; $i++ ) {
01436                         $gDayNo += self::$GREG_DAYS[$i];
01437                 }
01438 
01439                 // Leap years
01440                 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
01441                         $gDayNo++;
01442                 }
01443 
01444                 // Days passed in current month
01445                 $gDayNo += (int)$gd;
01446 
01447                 $jDayNo = $gDayNo - 79;
01448 
01449                 $jNp = floor( $jDayNo / 12053 );
01450                 $jDayNo %= 12053;
01451 
01452                 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
01453                 $jDayNo %= 1461;
01454 
01455                 if ( $jDayNo >= 366 ) {
01456                         $jy += floor( ( $jDayNo - 1 ) / 365 );
01457                         $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
01458                 }
01459 
01460                 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
01461                         $jDayNo -= self::$IRANIAN_DAYS[$i];
01462                 }
01463 
01464                 $jm = $i + 1;
01465                 $jd = $jDayNo + 1;
01466 
01467                 return array( $jy, $jm, $jd );
01468         }
01469 
01481         private static function tsToHijri( $ts ) {
01482                 $year = substr( $ts, 0, 4 );
01483                 $month = substr( $ts, 4, 2 );
01484                 $day = substr( $ts, 6, 2 );
01485 
01486                 $zyr = $year;
01487                 $zd = $day;
01488                 $zm = $month;
01489                 $zy = $zyr;
01490 
01491                 if (
01492                         ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
01493                         ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
01494                 )
01495                 {
01496                         $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
01497                                         (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
01498                                         (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
01499                                         $zd - 32075;
01500                 } else {
01501                         $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
01502                                                                 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
01503                 }
01504 
01505                 $zl = $zjd -1948440 + 10632;
01506                 $zn = (int)( ( $zl - 1 ) / 10631 );
01507                 $zl = $zl - 10631 * $zn + 354;
01508                 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) + ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
01509                 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) - ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
01510                 $zm = (int)( ( 24 * $zl ) / 709 );
01511                 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
01512                 $zy = 30 * $zn + $zj - 30;
01513 
01514                 return array( $zy, $zm, $zd );
01515         }
01516 
01532         private static function tsToHebrew( $ts ) {
01533                 # Parse date
01534                 $year = substr( $ts, 0, 4 );
01535                 $month = substr( $ts, 4, 2 );
01536                 $day = substr( $ts, 6, 2 );
01537 
01538                 # Calculate Hebrew year
01539                 $hebrewYear = $year + 3760;
01540 
01541                 # Month number when September = 1, August = 12
01542                 $month += 4;
01543                 if ( $month > 12 ) {
01544                         # Next year
01545                         $month -= 12;
01546                         $year++;
01547                         $hebrewYear++;
01548                 }
01549 
01550                 # Calculate day of year from 1 September
01551                 $dayOfYear = $day;
01552                 for ( $i = 1; $i < $month; $i++ ) {
01553                         if ( $i == 6 ) {
01554                                 # February
01555                                 $dayOfYear += 28;
01556                                 # Check if the year is leap
01557                                 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
01558                                         $dayOfYear++;
01559                                 }
01560                         } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
01561                                 $dayOfYear += 30;
01562                         } else {
01563                                 $dayOfYear += 31;
01564                         }
01565                 }
01566 
01567                 # Calculate the start of the Hebrew year
01568                 $start = self::hebrewYearStart( $hebrewYear );
01569 
01570                 # Calculate next year's start
01571                 if ( $dayOfYear <= $start ) {
01572                         # Day is before the start of the year - it is the previous year
01573                         # Next year's start
01574                         $nextStart = $start;
01575                         # Previous year
01576                         $year--;
01577                         $hebrewYear--;
01578                         # Add days since previous year's 1 September
01579                         $dayOfYear += 365;
01580                         if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
01581                                 # Leap year
01582                                 $dayOfYear++;
01583                         }
01584                         # Start of the new (previous) year
01585                         $start = self::hebrewYearStart( $hebrewYear );
01586                 } else {
01587                         # Next year's start
01588                         $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
01589                 }
01590 
01591                 # Calculate Hebrew day of year
01592                 $hebrewDayOfYear = $dayOfYear - $start;
01593 
01594                 # Difference between year's days
01595                 $diff = $nextStart - $start;
01596                 # Add 12 (or 13 for leap years) days to ignore the difference between
01597                 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
01598                 # difference is only about the year type
01599                 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
01600                         $diff += 13;
01601                 } else {
01602                         $diff += 12;
01603                 }
01604 
01605                 # Check the year pattern, and is leap year
01606                 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
01607                 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
01608                 # and non-leap years
01609                 $yearPattern = $diff % 30;
01610                 # Check if leap year
01611                 $isLeap = $diff >= 30;
01612 
01613                 # Calculate day in the month from number of day in the Hebrew year
01614                 # Don't check Adar - if the day is not in Adar, we will stop before;
01615                 # if it is in Adar, we will use it to check if it is Adar I or Adar II
01616                 $hebrewDay = $hebrewDayOfYear;
01617                 $hebrewMonth = 1;
01618                 $days = 0;
01619                 while ( $hebrewMonth <= 12 ) {
01620                         # Calculate days in this month
01621                         if ( $isLeap && $hebrewMonth == 6 ) {
01622                                 # Adar in a leap year
01623                                 if ( $isLeap ) {
01624                                         # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
01625                                         $days = 30;
01626                                         if ( $hebrewDay <= $days ) {
01627                                                 # Day in Adar I
01628                                                 $hebrewMonth = 13;
01629                                         } else {
01630                                                 # Subtract the days of Adar I
01631                                                 $hebrewDay -= $days;
01632                                                 # Try Adar II
01633                                                 $days = 29;
01634                                                 if ( $hebrewDay <= $days ) {
01635                                                         # Day in Adar II
01636                                                         $hebrewMonth = 14;
01637                                                 }
01638                                         }
01639                                 }
01640                         } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
01641                                 # Cheshvan in a complete year (otherwise as the rule below)
01642                                 $days = 30;
01643                         } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
01644                                 # Kislev in an incomplete year (otherwise as the rule below)
01645                                 $days = 29;
01646                         } else {
01647                                 # Odd months have 30 days, even have 29
01648                                 $days = 30 - ( $hebrewMonth - 1 ) % 2;
01649                         }
01650                         if ( $hebrewDay <= $days ) {
01651                                 # In the current month
01652                                 break;
01653                         } else {
01654                                 # Subtract the days of the current month
01655                                 $hebrewDay -= $days;
01656                                 # Try in the next month
01657                                 $hebrewMonth++;
01658                         }
01659                 }
01660 
01661                 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
01662         }
01663 
01673         private static function hebrewYearStart( $year ) {
01674                 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
01675                 $b = intval( ( $year - 1 ) % 4 );
01676                 $m = 32.044093161144 + 1.5542417966212 * $a +  $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
01677                 if ( $m < 0 ) {
01678                         $m--;
01679                 }
01680                 $Mar = intval( $m );
01681                 if ( $m < 0 ) {
01682                         $m++;
01683                 }
01684                 $m -= $Mar;
01685 
01686                 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
01687                 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
01688                         $Mar++;
01689                 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
01690                         $Mar += 2;
01691                 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
01692                         $Mar++;
01693                 }
01694 
01695                 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
01696                 return $Mar;
01697         }
01698 
01711         private static function tsToYear( $ts, $cName ) {
01712                 $gy = substr( $ts, 0, 4 );
01713                 $gm = substr( $ts, 4, 2 );
01714                 $gd = substr( $ts, 6, 2 );
01715 
01716                 if ( !strcmp( $cName, 'thai' ) ) {
01717                         # Thai solar dates
01718                         # Add 543 years to the Gregorian calendar
01719                         # Months and days are identical
01720                         $gy_offset = $gy + 543;
01721                 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
01722                         # Minguo dates
01723                         # Deduct 1911 years from the Gregorian calendar
01724                         # Months and days are identical
01725                         $gy_offset = $gy - 1911;
01726                 } elseif ( !strcmp( $cName, 'tenno' ) ) {
01727                         # Nengō dates up to Meiji period
01728                         # Deduct years from the Gregorian calendar
01729                         # depending on the nengo periods
01730                         # Months and days are identical
01731                         if ( ( $gy < 1912 ) || ( ( $gy == 1912 ) && ( $gm < 7 ) ) || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) ) ) {
01732                                 # Meiji period
01733                                 $gy_gannen = $gy - 1868 + 1;
01734                                 $gy_offset = $gy_gannen;
01735                                 if ( $gy_gannen == 1 ) {
01736                                         $gy_offset = '元';
01737                                 }
01738                                 $gy_offset = '明治' . $gy_offset;
01739                         } elseif (
01740                                 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
01741                                 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
01742                                 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
01743                                 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
01744                                 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
01745                         )
01746                         {
01747                                 # Taishō period
01748                                 $gy_gannen = $gy - 1912 + 1;
01749                                 $gy_offset = $gy_gannen;
01750                                 if ( $gy_gannen == 1 ) {
01751                                         $gy_offset = '元';
01752                                 }
01753                                 $gy_offset = '大正' . $gy_offset;
01754                         } elseif (
01755                                 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
01756                                 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
01757                                 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
01758                         )
01759                         {
01760                                 # Shōwa period
01761                                 $gy_gannen = $gy - 1926 + 1;
01762                                 $gy_offset = $gy_gannen;
01763                                 if ( $gy_gannen == 1 ) {
01764                                         $gy_offset = '元';
01765                                 }
01766                                 $gy_offset = '昭和' . $gy_offset;
01767                         } else {
01768                                 # Heisei period
01769                                 $gy_gannen = $gy - 1989 + 1;
01770                                 $gy_offset = $gy_gannen;
01771                                 if ( $gy_gannen == 1 ) {
01772                                         $gy_offset = '元';
01773                                 }
01774                                 $gy_offset = '平成' . $gy_offset;
01775                         }
01776                 } else {
01777                         $gy_offset = $gy;
01778                 }
01779 
01780                 return array( $gy_offset, $gm, $gd );
01781         }
01782 
01790         static function romanNumeral( $num ) {
01791                 static $table = array(
01792                         array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
01793                         array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
01794                         array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
01795                         array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM', 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' )
01796                 );
01797 
01798                 $num = intval( $num );
01799                 if ( $num > 10000 || $num <= 0 ) {
01800                         return $num;
01801                 }
01802 
01803                 $s = '';
01804                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
01805                         if ( $num >= $pow10 ) {
01806                                 $s .= $table[$i][(int)floor( $num / $pow10 )];
01807                         }
01808                         $num = $num % $pow10;
01809                 }
01810                 return $s;
01811         }
01812 
01820         static function hebrewNumeral( $num ) {
01821                 static $table = array(
01822                         array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
01823                         array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
01824                         array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
01825                         array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
01826                 );
01827 
01828                 $num = intval( $num );
01829                 if ( $num > 9999 || $num <= 0 ) {
01830                         return $num;
01831                 }
01832 
01833                 $s = '';
01834                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
01835                         if ( $num >= $pow10 ) {
01836                                 if ( $num == 15 || $num == 16 ) {
01837                                         $s .= $table[0][9] . $table[0][$num - 9];
01838                                         $num = 0;
01839                                 } else {
01840                                         $s .= $table[$i][intval( ( $num / $pow10 ) )];
01841                                         if ( $pow10 == 1000 ) {
01842                                                 $s .= "'";
01843                                         }
01844                                 }
01845                         }
01846                         $num = $num % $pow10;
01847                 }
01848                 if ( strlen( $s ) == 2 ) {
01849                         $str = $s . "'";
01850                 } else  {
01851                         $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
01852                         $str .= substr( $s, strlen( $s ) - 2, 2 );
01853                 }
01854                 $start = substr( $str, 0, strlen( $str ) - 2 );
01855                 $end = substr( $str, strlen( $str ) - 2 );
01856                 switch( $end ) {
01857                         case 'כ':
01858                                 $str = $start . 'ך';
01859                                 break;
01860                         case 'מ':
01861                                 $str = $start . 'ם';
01862                                 break;
01863                         case 'נ':
01864                                 $str = $start . 'ן';
01865                                 break;
01866                         case 'פ':
01867                                 $str = $start . 'ף';
01868                                 break;
01869                         case 'צ':
01870                                 $str = $start . 'ץ';
01871                                 break;
01872                 }
01873                 return $str;
01874         }
01875 
01884         function userAdjust( $ts, $tz = false ) {
01885                 global $wgUser, $wgLocalTZoffset;
01886 
01887                 if ( $tz === false ) {
01888                         $tz = $wgUser->getOption( 'timecorrection' );
01889                 }
01890 
01891                 $data = explode( '|', $tz, 3 );
01892 
01893                 if ( $data[0] == 'ZoneInfo' ) {
01894                         wfSuppressWarnings();
01895                         $userTZ = timezone_open( $data[2] );
01896                         wfRestoreWarnings();
01897                         if ( $userTZ !== false ) {
01898                                 $date = date_create( $ts, timezone_open( 'UTC' ) );
01899                                 date_timezone_set( $date, $userTZ );
01900                                 $date = date_format( $date, 'YmdHis' );
01901                                 return $date;
01902                         }
01903                         # Unrecognized timezone, default to 'Offset' with the stored offset.
01904                         $data[0] = 'Offset';
01905                 }
01906 
01907                 $minDiff = 0;
01908                 if ( $data[0] == 'System' || $tz == '' ) {
01909                         #  Global offset in minutes.
01910                         if ( isset( $wgLocalTZoffset ) ) {
01911                                 $minDiff = $wgLocalTZoffset;
01912                         }
01913                 } elseif ( $data[0] == 'Offset' ) {
01914                         $minDiff = intval( $data[1] );
01915                 } else {
01916                         $data = explode( ':', $tz );
01917                         if ( count( $data ) == 2 ) {
01918                                 $data[0] = intval( $data[0] );
01919                                 $data[1] = intval( $data[1] );
01920                                 $minDiff = abs( $data[0] ) * 60 + $data[1];
01921                                 if ( $data[0] < 0 ) {
01922                                         $minDiff = -$minDiff;
01923                                 }
01924                         } else {
01925                                 $minDiff = intval( $data[0] ) * 60;
01926                         }
01927                 }
01928 
01929                 # No difference ? Return time unchanged
01930                 if ( 0 == $minDiff ) {
01931                         return $ts;
01932                 }
01933 
01934                 wfSuppressWarnings(); // E_STRICT system time bitching
01935                 # Generate an adjusted date; take advantage of the fact that mktime
01936                 # will normalize out-of-range values so we don't have to split $minDiff
01937                 # into hours and minutes.
01938                 $t = mktime( (
01939                   (int)substr( $ts, 8, 2 ) ), # Hours
01940                   (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
01941                   (int)substr( $ts, 12, 2 ), # Seconds
01942                   (int)substr( $ts, 4, 2 ), # Month
01943                   (int)substr( $ts, 6, 2 ), # Day
01944                   (int)substr( $ts, 0, 4 ) ); # Year
01945 
01946                 $date = date( 'YmdHis', $t );
01947                 wfRestoreWarnings();
01948 
01949                 return $date;
01950         }
01951 
01969         function dateFormat( $usePrefs = true ) {
01970                 global $wgUser;
01971 
01972                 if ( is_bool( $usePrefs ) ) {
01973                         if ( $usePrefs ) {
01974                                 $datePreference = $wgUser->getDatePreference();
01975                         } else {
01976                                 $datePreference = (string)User::getDefaultOption( 'date' );
01977                         }
01978                 } else {
01979                         $datePreference = (string)$usePrefs;
01980                 }
01981 
01982                 // return int
01983                 if ( $datePreference == '' ) {
01984                         return 'default';
01985                 }
01986 
01987                 return $datePreference;
01988         }
01989 
01997         function getDateFormatString( $type, $pref ) {
01998                 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
01999                         if ( $pref == 'default' ) {
02000                                 $pref = $this->getDefaultDateFormat();
02001                                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
02002                         } else {
02003                                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
02004                                 if ( is_null( $df ) ) {
02005                                         $pref = $this->getDefaultDateFormat();
02006                                         $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
02007                                 }
02008                         }
02009                         $this->dateFormatStrings[$type][$pref] = $df;
02010                 }
02011                 return $this->dateFormatStrings[$type][$pref];
02012         }
02013 
02024         function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
02025                 $ts = wfTimestamp( TS_MW, $ts );
02026                 if ( $adj ) {
02027                         $ts = $this->userAdjust( $ts, $timecorrection );
02028                 }
02029                 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
02030                 return $this->sprintfDate( $df, $ts );
02031         }
02032 
02043         function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
02044                 $ts = wfTimestamp( TS_MW, $ts );
02045                 if ( $adj ) {
02046                         $ts = $this->userAdjust( $ts, $timecorrection );
02047                 }
02048                 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
02049                 return $this->sprintfDate( $df, $ts );
02050         }
02051 
02063         function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
02064                 $ts = wfTimestamp( TS_MW, $ts );
02065                 if ( $adj ) {
02066                         $ts = $this->userAdjust( $ts, $timecorrection );
02067                 }
02068                 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
02069                 return $this->sprintfDate( $df, $ts );
02070         }
02071 
02082         public function formatDuration( $seconds, array $chosenIntervals = array() ) {
02083                 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
02084 
02085                 $segments = array();
02086 
02087                 foreach ( $intervals as $intervalName => $intervalValue ) {
02088                         $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue );
02089                         $segments[] = $message->inLanguage( $this )->escaped();
02090                 }
02091 
02092                 return $this->listToText( $segments );
02093         }
02094 
02106         public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) {
02107                 if ( empty( $chosenIntervals ) ) {
02108                         $chosenIntervals = array( 'millennia', 'centuries', 'decades', 'years', 'days', 'hours', 'minutes', 'seconds' );
02109                 }
02110 
02111                 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
02112                 $sortedNames = array_keys( $intervals );
02113                 $smallestInterval = array_pop( $sortedNames );
02114 
02115                 $segments = array();
02116 
02117                 foreach ( $intervals as $name => $length ) {
02118                         $value = floor( $seconds / $length );
02119 
02120                         if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
02121                                 $seconds -= $value * $length;
02122                                 $segments[$name] = $value;
02123                         }
02124                 }
02125 
02126                 return $segments;
02127         }
02128 
02148         private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
02149                 $ts = wfTimestamp( TS_MW, $ts );
02150                 $options += array( 'timecorrection' => true, 'format' => true );
02151                 if ( $options['timecorrection'] !== false ) {
02152                         if ( $options['timecorrection'] === true ) {
02153                                 $offset = $user->getOption( 'timecorrection' );
02154                         } else {
02155                                 $offset = $options['timecorrection'];
02156                         }
02157                         $ts = $this->userAdjust( $ts, $offset );
02158                 }
02159                 if ( $options['format'] === true ) {
02160                         $format = $user->getDatePreference();
02161                 } else {
02162                         $format = $options['format'];
02163                 }
02164                 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
02165                 return $this->sprintfDate( $df, $ts );
02166         }
02167 
02187         public function userDate( $ts, User $user, array $options = array() ) {
02188                 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
02189         }
02190 
02210         public function userTime( $ts, User $user, array $options = array() ) {
02211                 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
02212         }
02213 
02233         public function userTimeAndDate( $ts, User $user, array $options = array() ) {
02234                 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
02235         }
02236 
02241         function getMessage( $key ) {
02242                 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
02243         }
02244 
02248         function getAllMessages() {
02249                 return self::$dataCache->getItem( $this->mCode, 'messages' );
02250         }
02251 
02258         function iconv( $in, $out, $string ) {
02259                 # This is a wrapper for iconv in all languages except esperanto,
02260                 # which does some nasty x-conversions beforehand
02261 
02262                 # Even with //IGNORE iconv can whine about illegal characters in
02263                 # *input* string. We just ignore those too.
02264                 # REF: http://bugs.php.net/bug.php?id=37166
02265                 # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885
02266                 wfSuppressWarnings();
02267                 $text = iconv( $in, $out . '//IGNORE', $string );
02268                 wfRestoreWarnings();
02269                 return $text;
02270         }
02271 
02272         // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
02273 
02278         function ucwordbreaksCallbackAscii( $matches ) {
02279                 return $this->ucfirst( $matches[1] );
02280         }
02281 
02286         function ucwordbreaksCallbackMB( $matches ) {
02287                 return mb_strtoupper( $matches[0] );
02288         }
02289 
02294         function ucCallback( $matches ) {
02295                 list( $wikiUpperChars ) = self::getCaseMaps();
02296                 return strtr( $matches[1], $wikiUpperChars );
02297         }
02298 
02303         function lcCallback( $matches ) {
02304                 list( , $wikiLowerChars ) = self::getCaseMaps();
02305                 return strtr( $matches[1], $wikiLowerChars );
02306         }
02307 
02312         function ucwordsCallbackMB( $matches ) {
02313                 return mb_strtoupper( $matches[0] );
02314         }
02315 
02320         function ucwordsCallbackWiki( $matches ) {
02321                 list( $wikiUpperChars ) = self::getCaseMaps();
02322                 return strtr( $matches[0], $wikiUpperChars );
02323         }
02324 
02332         function ucfirst( $str ) {
02333                 $o = ord( $str );
02334                 if ( $o < 96 ) { // if already uppercase...
02335                         return $str;
02336                 } elseif ( $o < 128 ) {
02337                         return ucfirst( $str ); // use PHP's ucfirst()
02338                 } else {
02339                         // fall back to more complex logic in case of multibyte strings
02340                         return $this->uc( $str, true );
02341                 }
02342         }
02343 
02352         function uc( $str, $first = false ) {
02353                 if ( function_exists( 'mb_strtoupper' ) ) {
02354                         if ( $first ) {
02355                                 if ( $this->isMultibyte( $str ) ) {
02356                                         return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
02357                                 } else {
02358                                         return ucfirst( $str );
02359                                 }
02360                         } else {
02361                                 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
02362                         }
02363                 } else {
02364                         if ( $this->isMultibyte( $str ) ) {
02365                                 $x = $first ? '^' : '';
02366                                 return preg_replace_callback(
02367                                         "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
02368                                         array( $this, 'ucCallback' ),
02369                                         $str
02370                                 );
02371                         } else {
02372                                 return $first ? ucfirst( $str ) : strtoupper( $str );
02373                         }
02374                 }
02375         }
02376 
02381         function lcfirst( $str ) {
02382                 $o = ord( $str );
02383                 if ( !$o ) {
02384                         return strval( $str );
02385                 } elseif ( $o >= 128 ) {
02386                         return $this->lc( $str, true );
02387                 } elseif ( $o > 96 ) {
02388                         return $str;
02389                 } else {
02390                         $str[0] = strtolower( $str[0] );
02391                         return $str;
02392                 }
02393         }
02394 
02400         function lc( $str, $first = false ) {
02401                 if ( function_exists( 'mb_strtolower' ) ) {
02402                         if ( $first ) {
02403                                 if ( $this->isMultibyte( $str ) ) {
02404                                         return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
02405                                 } else {
02406                                         return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
02407                                 }
02408                         } else {
02409                                 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
02410                         }
02411                 } else {
02412                         if ( $this->isMultibyte( $str ) ) {
02413                                 $x = $first ? '^' : '';
02414                                 return preg_replace_callback(
02415                                         "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
02416                                         array( $this, 'lcCallback' ),
02417                                         $str
02418                                 );
02419                         } else {
02420                                 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
02421                         }
02422                 }
02423         }
02424 
02429         function isMultibyte( $str ) {
02430                 return (bool)preg_match( '/[\x80-\xff]/', $str );
02431         }
02432 
02437         function ucwords( $str ) {
02438                 if ( $this->isMultibyte( $str ) ) {
02439                         $str = $this->lc( $str );
02440 
02441                         // regexp to find first letter in each word (i.e. after each space)
02442                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
02443 
02444                         // function to use to capitalize a single char
02445                         if ( function_exists( 'mb_strtoupper' ) ) {
02446                                 return preg_replace_callback(
02447                                         $replaceRegexp,
02448                                         array( $this, 'ucwordsCallbackMB' ),
02449                                         $str
02450                                 );
02451                         } else {
02452                                 return preg_replace_callback(
02453                                         $replaceRegexp,
02454                                         array( $this, 'ucwordsCallbackWiki' ),
02455                                         $str
02456                                 );
02457                         }
02458                 } else {
02459                         return ucwords( strtolower( $str ) );
02460                 }
02461         }
02462 
02469         function ucwordbreaks( $str ) {
02470                 if ( $this->isMultibyte( $str ) ) {
02471                         $str = $this->lc( $str );
02472 
02473                         // since \b doesn't work for UTF-8, we explicitely define word break chars
02474                         $breaks = "[ \-\(\)\}\{\.,\?!]";
02475 
02476                         // find first letter after word break
02477                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
02478 
02479                         if ( function_exists( 'mb_strtoupper' ) ) {
02480                                 return preg_replace_callback(
02481                                         $replaceRegexp,
02482                                         array( $this, 'ucwordbreaksCallbackMB' ),
02483                                         $str
02484                                 );
02485                         } else {
02486                                 return preg_replace_callback(
02487                                         $replaceRegexp,
02488                                         array( $this, 'ucwordsCallbackWiki' ),
02489                                         $str
02490                                 );
02491                         }
02492                 } else {
02493                         return preg_replace_callback(
02494                                 '/\b([\w\x80-\xff]+)\b/',
02495                                 array( $this, 'ucwordbreaksCallbackAscii' ),
02496                                 $str
02497                         );
02498                 }
02499         }
02500 
02516         function caseFold( $s ) {
02517                 return $this->uc( $s );
02518         }
02519 
02524         function checkTitleEncoding( $s ) {
02525                 if ( is_array( $s ) ) {
02526                         wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
02527                 }
02528                 if ( StringUtils::isUtf8( $s ) ) {
02529                         return $s;
02530                 }
02531 
02532                 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
02533         }
02534 
02538         function fallback8bitEncoding() {
02539                 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
02540         }
02541 
02550         function hasWordBreaks() {
02551                 return true;
02552         }
02553 
02561         function segmentByWord( $string ) {
02562                 return $string;
02563         }
02564 
02572         function normalizeForSearch( $string ) {
02573                 return self::convertDoubleWidth( $string );
02574         }
02575 
02584         protected static function convertDoubleWidth( $string ) {
02585                 static $full = null;
02586                 static $half = null;
02587 
02588                 if ( $full === null ) {
02589                         $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
02590                         $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
02591                         $full = str_split( $fullWidth, 3 );
02592                         $half = str_split( $halfWidth );
02593                 }
02594 
02595                 $string = str_replace( $full, $half, $string );
02596                 return $string;
02597         }
02598 
02604         protected static function insertSpace( $string, $pattern ) {
02605                 $string = preg_replace( $pattern, " $1 ", $string );
02606                 $string = preg_replace( '/ +/', ' ', $string );
02607                 return $string;
02608         }
02609 
02614         function convertForSearchResult( $termsArray ) {
02615                 # some languages, e.g. Chinese, need to do a conversion
02616                 # in order for search results to be displayed correctly
02617                 return $termsArray;
02618         }
02619 
02626         function firstChar( $s ) {
02627                 $matches = array();
02628                 preg_match(
02629                         '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
02630                                 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
02631                         $s,
02632                         $matches
02633                 );
02634 
02635                 if ( isset( $matches[1] ) ) {
02636                         if ( strlen( $matches[1] ) != 3 ) {
02637                                 return $matches[1];
02638                         }
02639 
02640                         // Break down Hangul syllables to grab the first jamo
02641                         $code = utf8ToCodepoint( $matches[1] );
02642                         if ( $code < 0xac00 || 0xd7a4 <= $code ) {
02643                                 return $matches[1];
02644                         } elseif ( $code < 0xb098 ) {
02645                                 return "\xe3\x84\xb1";
02646                         } elseif ( $code < 0xb2e4 ) {
02647                                 return "\xe3\x84\xb4";
02648                         } elseif ( $code < 0xb77c ) {
02649                                 return "\xe3\x84\xb7";
02650                         } elseif ( $code < 0xb9c8 ) {
02651                                 return "\xe3\x84\xb9";
02652                         } elseif ( $code < 0xbc14 ) {
02653                                 return "\xe3\x85\x81";
02654                         } elseif ( $code < 0xc0ac ) {
02655                                 return "\xe3\x85\x82";
02656                         } elseif ( $code < 0xc544 ) {
02657                                 return "\xe3\x85\x85";
02658                         } elseif ( $code < 0xc790 ) {
02659                                 return "\xe3\x85\x87";
02660                         } elseif ( $code < 0xcc28 ) {
02661                                 return "\xe3\x85\x88";
02662                         } elseif ( $code < 0xce74 ) {
02663                                 return "\xe3\x85\x8a";
02664                         } elseif ( $code < 0xd0c0 ) {
02665                                 return "\xe3\x85\x8b";
02666                         } elseif ( $code < 0xd30c ) {
02667                                 return "\xe3\x85\x8c";
02668                         } elseif ( $code < 0xd558 ) {
02669                                 return "\xe3\x85\x8d";
02670                         } else {
02671                                 return "\xe3\x85\x8e";
02672                         }
02673                 } else {
02674                         return '';
02675                 }
02676         }
02677 
02678         function initEncoding() {
02679                 # Some languages may have an alternate char encoding option
02680                 # (Esperanto X-coding, Japanese furigana conversion, etc)
02681                 # If this language is used as the primary content language,
02682                 # an override to the defaults can be set here on startup.
02683         }
02684 
02689         function recodeForEdit( $s ) {
02690                 # For some languages we'll want to explicitly specify
02691                 # which characters make it into the edit box raw
02692                 # or are converted in some way or another.
02693                 global $wgEditEncoding;
02694                 if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) {
02695                         return $s;
02696                 } else {
02697                         return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
02698                 }
02699         }
02700 
02705         function recodeInput( $s ) {
02706                 # Take the previous into account.
02707                 global $wgEditEncoding;
02708                 if ( $wgEditEncoding != '' ) {
02709                         $enc = $wgEditEncoding;
02710                 } else {
02711                         $enc = 'UTF-8';
02712                 }
02713                 if ( $enc == 'UTF-8' ) {
02714                         return $s;
02715                 } else {
02716                         return $this->iconv( $enc, 'UTF-8', $s );
02717                 }
02718         }
02719 
02731         function normalize( $s ) {
02732                 global $wgAllUnicodeFixes;
02733                 $s = UtfNormal::cleanUp( $s );
02734                 if ( $wgAllUnicodeFixes ) {
02735                         $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
02736                         $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
02737                 }
02738 
02739                 return $s;
02740         }
02741 
02756         function transformUsingPairFile( $file, $string ) {
02757                 if ( !isset( $this->transformData[$file] ) ) {
02758                         $data = wfGetPrecompiledData( $file );
02759                         if ( $data === false ) {
02760                                 throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
02761                         }
02762                         $this->transformData[$file] = new ReplacementArray( $data );
02763                 }
02764                 return $this->transformData[$file]->replace( $string );
02765         }
02766 
02772         function isRTL() {
02773                 return self::$dataCache->getItem( $this->mCode, 'rtl' );
02774         }
02775 
02780         function getDir() {
02781                 return $this->isRTL() ? 'rtl' : 'ltr';
02782         }
02783 
02792         function alignStart() {
02793                 return $this->isRTL() ? 'right' : 'left';
02794         }
02795 
02804         function alignEnd() {
02805                 return $this->isRTL() ? 'left' : 'right';
02806         }
02807 
02819         function getDirMarkEntity( $opposite = false ) {
02820                 if ( $opposite ) { return $this->isRTL() ? '&lrm;' : '&rlm;'; }
02821                 return $this->isRTL() ? '&rlm;' : '&lrm;';
02822         }
02823 
02834         function getDirMark( $opposite = false ) {
02835                 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
02836                 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
02837                 if ( $opposite ) { return $this->isRTL() ? $lrm : $rlm; }
02838                 return $this->isRTL() ? $rlm : $lrm;
02839         }
02840 
02844         function capitalizeAllNouns() {
02845                 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
02846         }
02847 
02854         function getArrow( $direction = 'forwards' ) {
02855                 switch ( $direction ) {
02856                 case 'forwards':
02857                         return $this->isRTL() ? '←' : '→';
02858                 case 'backwards':
02859                         return $this->isRTL() ? '→' : '←';
02860                 case 'left':
02861                         return '←';
02862                 case 'right':
02863                         return '→';
02864                 case 'up':
02865                         return '↑';
02866                 case 'down':
02867                         return '↓';
02868                 }
02869         }
02870 
02876         function linkPrefixExtension() {
02877                 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
02878         }
02879 
02883         function getMagicWords() {
02884                 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
02885         }
02886 
02887         protected function doMagicHook() {
02888                 if ( $this->mMagicHookDone ) {
02889                         return;
02890                 }
02891                 $this->mMagicHookDone = true;
02892                 wfProfileIn( 'LanguageGetMagic' );
02893                 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
02894                 wfProfileOut( 'LanguageGetMagic' );
02895         }
02896 
02902         function getMagic( $mw ) {
02903                 $this->doMagicHook();
02904 
02905                 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
02906                         $rawEntry = $this->mMagicExtensions[$mw->mId];
02907                 } else {
02908                         $magicWords = $this->getMagicWords();
02909                         if ( isset( $magicWords[$mw->mId] ) ) {
02910                                 $rawEntry = $magicWords[$mw->mId];
02911                         } else {
02912                                 $rawEntry = false;
02913                         }
02914                 }
02915 
02916                 if ( !is_array( $rawEntry ) ) {
02917                         error_log( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
02918                 } else {
02919                         $mw->mCaseSensitive = $rawEntry[0];
02920                         $mw->mSynonyms = array_slice( $rawEntry, 1 );
02921                 }
02922         }
02923 
02929         function addMagicWordsByLang( $newWords ) {
02930                 $fallbackChain = $this->getFallbackLanguages();
02931                 $fallbackChain = array_reverse( $fallbackChain );
02932                 foreach ( $fallbackChain as $code ) {
02933                         if ( isset( $newWords[$code] ) ) {
02934                                 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
02935                         }
02936                 }
02937         }
02938 
02943         function getSpecialPageAliases() {
02944                 // Cache aliases because it may be slow to load them
02945                 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
02946                         // Initialise array
02947                         $this->mExtendedSpecialPageAliases =
02948                                 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
02949                         wfRunHooks( 'LanguageGetSpecialPageAliases',
02950                                 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
02951                 }
02952 
02953                 return $this->mExtendedSpecialPageAliases;
02954         }
02955 
02962         function emphasize( $text ) {
02963                 return "<em>$text</em>";
02964         }
02965 
02990         public function formatNum( $number, $nocommafy = false ) {
02991                 global $wgTranslateNumerals;
02992                 if ( !$nocommafy ) {
02993                         $number = $this->commafy( $number );
02994                         $s = $this->separatorTransformTable();
02995                         if ( $s ) {
02996                                 $number = strtr( $number, $s );
02997                         }
02998                 }
02999 
03000                 if ( $wgTranslateNumerals ) {
03001                         $s = $this->digitTransformTable();
03002                         if ( $s ) {
03003                                 $number = strtr( $number, $s );
03004                         }
03005                 }
03006 
03007                 return $number;
03008         }
03009 
03018         public function formatNumNoSeparators( $number ) {
03019                 return $this->formatNum( $number, true );
03020         }
03021 
03026         function parseFormattedNumber( $number ) {
03027                 $s = $this->digitTransformTable();
03028                 if ( $s ) {
03029                         $number = strtr( $number, array_flip( $s ) );
03030                 }
03031 
03032                 $s = $this->separatorTransformTable();
03033                 if ( $s ) {
03034                         $number = strtr( $number, array_flip( $s ) );
03035                 }
03036 
03037                 $number = strtr( $number, array( ',' => '' ) );
03038                 return $number;
03039         }
03040 
03047         function commafy( $number ) {
03048                 $digitGroupingPattern = $this->digitGroupingPattern();
03049                 if ( $number === null ) {
03050                         return '';
03051                 }
03052 
03053                 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
03054                         // default grouping is at thousands,  use the same for ###,###,### pattern too.
03055                         return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) );
03056                 } else {
03057                         // Ref: http://cldr.unicode.org/translation/number-patterns
03058                         $sign = "";
03059                         if ( intval( $number ) < 0 ) {
03060                                 // For negative numbers apply the algorithm like positive number and add sign.
03061                                 $sign =  "-";
03062                                 $number = substr( $number, 1 );
03063                         }
03064                         $integerPart = array();
03065                         $decimalPart = array();
03066                         $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
03067                         preg_match( "/\d+/", $number, $integerPart );
03068                         preg_match( "/\.\d*/", $number, $decimalPart );
03069                         $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0]:"";
03070                         if ( $groupedNumber  === $number ) {
03071                                 // the string does not have any number part. Eg: .12345
03072                                 return $sign . $groupedNumber;
03073                         }
03074                         $start = $end = strlen( $integerPart[0] );
03075                         while ( $start > 0 ) {
03076                                 $match = $matches[0][$numMatches -1] ;
03077                                 $matchLen = strlen( $match );
03078                                 $start = $end - $matchLen;
03079                                 if ( $start < 0 ) {
03080                                         $start = 0;
03081                                 }
03082                                 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber ;
03083                                 $end = $start;
03084                                 if ( $numMatches > 1 ) {
03085                                         // use the last pattern for the rest of the number
03086                                         $numMatches--;
03087                                 }
03088                                 if ( $start > 0 ) {
03089                                         $groupedNumber = "," . $groupedNumber;
03090                                 }
03091                         }
03092                         return $sign . $groupedNumber;
03093                 }
03094         }
03095 
03099         function digitGroupingPattern() {
03100                 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
03101         }
03102 
03106         function digitTransformTable() {
03107                 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
03108         }
03109 
03113         function separatorTransformTable() {
03114                 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
03115         }
03116 
03126         function listToText( array $l ) {
03127                 $m = count( $l ) - 1;
03128                 if ( $m < 0 ) {
03129                         return '';
03130                 }
03131                 if ( $m > 0 ) {
03132                         $and = $this->getMessageFromDB( 'and' );
03133                         $space = $this->getMessageFromDB( 'word-separator' );
03134                         if ( $m > 1 ) {
03135                                 $comma = $this->getMessageFromDB( 'comma-separator' );
03136                         }
03137                 }
03138                 $s = $l[$m];
03139                 for ( $i = $m - 1; $i >= 0; $i-- ) {
03140                         if ( $i == $m - 1 ) {
03141                                 $s = $l[$i] . $and . $space . $s;
03142                         } else {
03143                                 $s = $l[$i] . $comma . $s;
03144                         }
03145                 }
03146                 return $s;
03147         }
03148 
03155         function commaList( array $list ) {
03156                 return implode(
03157                         wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
03158                         $list
03159                 );
03160         }
03161 
03168         function semicolonList( array $list ) {
03169                 return implode(
03170                         wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
03171                         $list
03172                 );
03173         }
03174 
03180         function pipeList( array $list ) {
03181                 return implode(
03182                         wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
03183                         $list
03184                 );
03185         }
03186 
03204         function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
03205                 # Use the localized ellipsis character
03206                 if ( $ellipsis == '...' ) {
03207                         $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
03208                 }
03209                 # Check if there is no need to truncate
03210                 if ( $length == 0 ) {
03211                         return $ellipsis; // convention
03212                 } elseif ( strlen( $string ) <= abs( $length ) ) {
03213                         return $string; // no need to truncate
03214                 }
03215                 $stringOriginal = $string;
03216                 # If ellipsis length is >= $length then we can't apply $adjustLength
03217                 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
03218                         $string = $ellipsis; // this can be slightly unexpected
03219                 # Otherwise, truncate and add ellipsis...
03220                 } else {
03221                         $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
03222                         if ( $length > 0 ) {
03223                                 $length -= $eLength;
03224                                 $string = substr( $string, 0, $length ); // xyz...
03225                                 $string = $this->removeBadCharLast( $string );
03226                                 $string = $string . $ellipsis;
03227                         } else {
03228                                 $length += $eLength;
03229                                 $string = substr( $string, $length ); // ...xyz
03230                                 $string = $this->removeBadCharFirst( $string );
03231                                 $string = $ellipsis . $string;
03232                         }
03233                 }
03234                 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
03235                 # This check is *not* redundant if $adjustLength, due to the single case where
03236                 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
03237                 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
03238                         return $string;
03239                 } else {
03240                         return $stringOriginal;
03241                 }
03242         }
03243 
03251         protected function removeBadCharLast( $string ) {
03252                 if ( $string != '' ) {
03253                         $char = ord( $string[strlen( $string ) - 1] );
03254                         $m = array();
03255                         if ( $char >= 0xc0 ) {
03256                                 # We got the first byte only of a multibyte char; remove it.
03257                                 $string = substr( $string, 0, -1 );
03258                         } elseif ( $char >= 0x80 &&
03259                                   preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
03260                                                           '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) )
03261                         {
03262                                 # We chopped in the middle of a character; remove it
03263                                 $string = $m[1];
03264                         }
03265                 }
03266                 return $string;
03267         }
03268 
03276         protected function removeBadCharFirst( $string ) {
03277                 if ( $string != '' ) {
03278                         $char = ord( $string[0] );
03279                         if ( $char >= 0x80 && $char < 0xc0 ) {
03280                                 # We chopped in the middle of a character; remove the whole thing
03281                                 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
03282                         }
03283                 }
03284                 return $string;
03285         }
03286 
03302         function truncateHtml( $text, $length, $ellipsis = '...' ) {
03303                 # Use the localized ellipsis character
03304                 if ( $ellipsis == '...' ) {
03305                         $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
03306                 }
03307                 # Check if there is clearly no need to truncate
03308                 if ( $length <= 0 ) {
03309                         return $ellipsis; // no text shown, nothing to format (convention)
03310                 } elseif ( strlen( $text ) <= $length ) {
03311                         return $text; // string short enough even *with* HTML (short-circuit)
03312                 }
03313 
03314                 $dispLen = 0; // innerHTML legth so far
03315                 $testingEllipsis = false; // checking if ellipses will make string longer/equal?
03316                 $tagType = 0; // 0-open, 1-close
03317                 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
03318                 $entityState = 0; // 0-not entity, 1-entity
03319                 $tag = $ret = ''; // accumulated tag name, accumulated result string
03320                 $openTags = array(); // open tag stack
03321                 $maybeState = null; // possible truncation state
03322 
03323                 $textLen = strlen( $text );
03324                 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
03325                 for ( $pos = 0; true; ++$pos ) {
03326                         # Consider truncation once the display length has reached the maximim.
03327                         # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
03328                         # Check that we're not in the middle of a bracket/entity...
03329                         if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
03330                                 if ( !$testingEllipsis ) {
03331                                         $testingEllipsis = true;
03332                                         # Save where we are; we will truncate here unless there turn out to
03333                                         # be so few remaining characters that truncation is not necessary.
03334                                         if ( !$maybeState ) { // already saved? ($neLength = 0 case)
03335                                                 $maybeState = array( $ret, $openTags ); // save state
03336                                         }
03337                                 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
03338                                         # String in fact does need truncation, the truncation point was OK.
03339                                         list( $ret, $openTags ) = $maybeState; // reload state
03340                                         $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
03341                                         $ret .= $ellipsis; // add ellipsis
03342                                         break;
03343                                 }
03344                         }
03345                         if ( $pos >= $textLen ) break; // extra iteration just for above checks
03346 
03347                         # Read the next char...
03348                         $ch = $text[$pos];
03349                         $lastCh = $pos ? $text[$pos - 1] : '';
03350                         $ret .= $ch; // add to result string
03351                         if ( $ch == '<' ) {
03352                                 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
03353                                 $entityState = 0; // for bad HTML
03354                                 $bracketState = 1; // tag started (checking for backslash)
03355                         } elseif ( $ch == '>' ) {
03356                                 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
03357                                 $entityState = 0; // for bad HTML
03358                                 $bracketState = 0; // out of brackets
03359                         } elseif ( $bracketState == 1 ) {
03360                                 if ( $ch == '/' ) {
03361                                         $tagType = 1; // close tag (e.g. "</span>")
03362                                 } else {
03363                                         $tagType = 0; // open tag (e.g. "<span>")
03364                                         $tag .= $ch;
03365                                 }
03366                                 $bracketState = 2; // building tag name
03367                         } elseif ( $bracketState == 2 ) {
03368                                 if ( $ch != ' ' ) {
03369                                         $tag .= $ch;
03370                                 } else {
03371                                         // Name found (e.g. "<a href=..."), add on tag attributes...
03372                                         $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
03373                                 }
03374                         } elseif ( $bracketState == 0 ) {
03375                                 if ( $entityState ) {
03376                                         if ( $ch == ';' ) {
03377                                                 $entityState = 0;
03378                                                 $dispLen++; // entity is one displayed char
03379                                         }
03380                                 } else {
03381                                         if ( $neLength == 0 && !$maybeState ) {
03382                                                 // Save state without $ch. We want to *hit* the first
03383                                                 // display char (to get tags) but not *use* it if truncating.
03384                                                 $maybeState = array( substr( $ret, 0, -1 ), $openTags );
03385                                         }
03386                                         if ( $ch == '&' ) {
03387                                                 $entityState = 1; // entity found, (e.g. "&#160;")
03388                                         } else {
03389                                                 $dispLen++; // this char is displayed
03390                                                 // Add the next $max display text chars after this in one swoop...
03391                                                 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
03392                                                 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
03393                                                 $dispLen += $skipped;
03394                                                 $pos += $skipped;
03395                                         }
03396                                 }
03397                         }
03398                 }
03399                 // Close the last tag if left unclosed by bad HTML
03400                 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
03401                 while ( count( $openTags ) > 0 ) {
03402                         $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
03403                 }
03404                 return $ret;
03405         }
03406 
03418         private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
03419                 if ( $len === null ) {
03420                         $len = -1; // -1 means "no limit" for strcspn
03421                 } elseif ( $len < 0 ) {
03422                         $len = 0; // sanity
03423                 }
03424                 $skipCount = 0;
03425                 if ( $start < strlen( $text ) ) {
03426                         $skipCount = strcspn( $text, $search, $start, $len );
03427                         $ret .= substr( $text, $start, $skipCount );
03428                 }
03429                 return $skipCount;
03430         }
03431 
03441         private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
03442                 $tag = ltrim( $tag );
03443                 if ( $tag != '' ) {
03444                         if ( $tagType == 0 && $lastCh != '/' ) {
03445                                 $openTags[] = $tag; // tag opened (didn't close itself)
03446                         } elseif ( $tagType == 1 ) {
03447                                 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
03448                                         array_pop( $openTags ); // tag closed
03449                                 }
03450                         }
03451                         $tag = '';
03452                 }
03453         }
03454 
03463         function convertGrammar( $word, $case ) {
03464                 global $wgGrammarForms;
03465                 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
03466                         return $wgGrammarForms[$this->getCode()][$case][$word];
03467                 }
03468                 return $word;
03469         }
03475         function getGrammarForms() {
03476                 global $wgGrammarForms;
03477                 if ( isset( $wgGrammarForms[$this->getCode()] ) && is_array( $wgGrammarForms[$this->getCode()] ) ) {
03478                          return $wgGrammarForms[$this->getCode()];
03479                 }
03480                 return array();
03481         }
03501         function gender( $gender, $forms ) {
03502                 if ( !count( $forms ) ) {
03503                         return '';
03504                 }
03505                 $forms = $this->preConvertPlural( $forms, 2 );
03506                 if ( $gender === 'male' ) {
03507                         return $forms[0];
03508                 }
03509                 if ( $gender === 'female' ) {
03510                         return $forms[1];
03511                 }
03512                 return isset( $forms[2] ) ? $forms[2] : $forms[0];
03513         }
03514 
03530         function convertPlural( $count, $forms ) {
03531                 if ( !count( $forms ) ) {
03532                         return '';
03533                 }
03534 
03535                 // Handle explicit n=pluralform cases
03536                 foreach ( $forms as $index => $form ) {
03537                         if ( preg_match( '/\d+=/i', $form ) ) {
03538                                 $pos = strpos( $form, '=' );
03539                                 if ( substr( $form, 0, $pos ) === (string) $count ) {
03540                                         return substr( $form, $pos + 1 );
03541                                 }
03542                                 unset( $forms[$index] );
03543                         }
03544                 }
03545                 $forms = array_values( $forms );
03546 
03547                 $pluralForm = $this->getPluralForm( $count );
03548                 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
03549                 return $forms[$pluralForm];
03550         }
03551 
03560         protected function preConvertPlural( /* Array */ $forms, $count ) {
03561                 while ( count( $forms ) < $count ) {
03562                         $forms[] = $forms[count( $forms ) - 1];
03563                 }
03564                 return $forms;
03565         }
03566 
03578         function translateBlockExpiry( $str ) {
03579                 $duration = SpecialBlock::getSuggestedDurations( $this );
03580                 foreach ( $duration as $show => $value ) {
03581                         if ( strcmp( $str, $value ) == 0 ) {
03582                                 return htmlspecialchars( trim( $show ) );
03583                         }
03584                 }
03585 
03586                 // Since usually only infinite or indefinite is only on list, so try
03587                 // equivalents if still here.
03588                 $indefs = array( 'infinite', 'infinity', 'indefinite' );
03589                 if ( in_array( $str, $indefs ) ) {
03590                         foreach ( $indefs as $val ) {
03591                                 $show = array_search( $val, $duration, true );
03592                                 if ( $show !== false ) {
03593                                         return htmlspecialchars( trim( $show ) );
03594                                 }
03595                         }
03596                 }
03597 
03598                 // If all else fails, return a standard duration or timestamp description.
03599                 $time = strtotime( $str, 0 );
03600                 if ( $time === false ) { // Unknown format. Return it as-is in case.
03601                         return $str;
03602                 } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp.
03603                         // $time is relative to 0 so it's a duration length.
03604                         return $this->formatDuration( $time );
03605                 } else { // It's an absolute timestamp.
03606                         if ( $time === 0 ) {
03607                                 // wfTimestamp() handles 0 as current time instead of epoch.
03608                                 return $this->timeanddate( '19700101000000' );
03609                         } else {
03610                                 return $this->timeanddate( $time );
03611                         }
03612                 }
03613         }
03614 
03622         public function segmentForDiff( $text ) {
03623                 return $text;
03624         }
03625 
03632         public function unsegmentForDiff( $text ) {
03633                 return $text;
03634         }
03635 
03642         public function getConverter() {
03643                 return $this->mConverter;
03644         }
03645 
03652         public function autoConvertToAllVariants( $text ) {
03653                 return $this->mConverter->autoConvertToAllVariants( $text );
03654         }
03655 
03662         public function convert( $text ) {
03663                 return $this->mConverter->convert( $text );
03664         }
03665 
03672         public function convertTitle( $title ) {
03673                 return $this->mConverter->convertTitle( $title );
03674         }
03675 
03682         public function convertNamespace( $ns ) {
03683                 return $this->mConverter->convertNamespace( $ns );
03684         }
03685 
03691         public function hasVariants() {
03692                 return count( $this->getVariants() ) > 1;
03693         }
03694 
03702         public function hasVariant( $variant ) {
03703                 return (bool)$this->mConverter->validateVariant( $variant );
03704         }
03705 
03712         public function armourMath( $text ) {
03713                 return $this->mConverter->armourMath( $text );
03714         }
03715 
03723         public function convertHtml( $text, $isTitle = false ) {
03724                 return htmlspecialchars( $this->convert( $text, $isTitle ) );
03725         }
03726 
03731         public function convertCategoryKey( $key ) {
03732                 return $this->mConverter->convertCategoryKey( $key );
03733         }
03734 
03741         public function getVariants() {
03742                 return $this->mConverter->getVariants();
03743         }
03744 
03748         public function getPreferredVariant() {
03749                 return $this->mConverter->getPreferredVariant();
03750         }
03751 
03755         public function getDefaultVariant() {
03756                 return $this->mConverter->getDefaultVariant();
03757         }
03758 
03762         public function getURLVariant() {
03763                 return $this->mConverter->getURLVariant();
03764         }
03765 
03778         public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
03779                 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
03780         }
03781 
03793         public function convertLinkToAllVariants( $text ) {
03794                 return $this->mConverter->convertLinkToAllVariants( $text );
03795         }
03796 
03803         function getExtraHashOptions() {
03804                 return $this->mConverter->getExtraHashOptions();
03805         }
03806 
03814         public function getParsedTitle() {
03815                 return $this->mConverter->getParsedTitle();
03816         }
03817 
03830         public function markNoConversion( $text, $noParse = false ) {
03831                 // Excluding protocal-relative URLs may avoid many false positives.
03832                 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) {
03833                         return $this->mConverter->markNoConversion( $text );
03834                 } else {
03835                         return $text;
03836                 }
03837         }
03838 
03845         public function linkTrail() {
03846                 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
03847         }
03848 
03852         function getLangObj() {
03853                 return $this;
03854         }
03855 
03864         public function getCode() {
03865                 return $this->mCode;
03866         }
03867 
03878         public function getHtmlCode() {
03879                 if ( is_null( $this->mHtmlCode ) ) {
03880                         $this->mHtmlCode = wfBCP47( $this->getCode() );
03881                 }
03882                 return $this->mHtmlCode;
03883         }
03884 
03888         public function setCode( $code ) {
03889                 $this->mCode = $code;
03890                 // Ensure we don't leave an incorrect html code lying around
03891                 $this->mHtmlCode = null;
03892         }
03893 
03902         public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
03903                 // Protect against path traversal
03904                 if ( !Language::isValidCode( $code )
03905                         || strcspn( $code, ":/\\\000" ) !== strlen( $code ) )
03906                 {
03907                         throw new MWException( "Invalid language code \"$code\"" );
03908                 }
03909 
03910                 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
03911         }
03912 
03920         public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
03921                 $m = null;
03922                 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
03923                         preg_quote( $suffix, '/' ) . '/', $filename, $m );
03924                 if ( !count( $m ) ) {
03925                         return false;
03926                 }
03927                 return str_replace( '_', '-', strtolower( $m[1] ) );
03928         }
03929 
03934         public static function getMessagesFileName( $code ) {
03935                 global $IP;
03936                 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
03937                 wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) );
03938                 return $file;
03939         }
03940 
03945         public static function getClassFileName( $code ) {
03946                 global $IP;
03947                 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
03948         }
03949 
03957         public static function getFallbackFor( $code ) {
03958                 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
03959                         return false;
03960                 } else {
03961                         $fallbacks = self::getFallbacksFor( $code );
03962                         $first = array_shift( $fallbacks );
03963                         return $first;
03964                 }
03965         }
03966 
03974         public static function getFallbacksFor( $code ) {
03975                 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
03976                         return array();
03977                 } else {
03978                         $v = self::getLocalisationCache()->getItem( $code, 'fallback' );
03979                         $v = array_map( 'trim', explode( ',', $v ) );
03980                         if ( $v[count( $v ) - 1] !== 'en' ) {
03981                                 $v[] = 'en';
03982                         }
03983                         return $v;
03984                 }
03985         }
03986 
03996         public static function getMessagesFor( $code ) {
03997                 return self::getLocalisationCache()->getItem( $code, 'messages' );
03998         }
03999 
04008         public static function getMessageFor( $key, $code ) {
04009                 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
04010         }
04011 
04020         public static function getMessageKeysFor( $code ) {
04021                 return self::getLocalisationCache()->getSubItemList( $code, 'messages' );
04022         }
04023 
04028         function fixVariableInNamespace( $talk ) {
04029                 if ( strpos( $talk, '$1' ) === false ) {
04030                         return $talk;
04031                 }
04032 
04033                 global $wgMetaNamespace;
04034                 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
04035 
04036                 # Allow grammar transformations
04037                 # Allowing full message-style parsing would make simple requests
04038                 # such as action=raw much more expensive than they need to be.
04039                 # This will hopefully cover most cases.
04040                 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
04041                         array( &$this, 'replaceGrammarInNamespace' ), $talk );
04042                 return str_replace( ' ', '_', $talk );
04043         }
04044 
04049         function replaceGrammarInNamespace( $m ) {
04050                 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
04051         }
04052 
04057         static function getCaseMaps() {
04058                 static $wikiUpperChars, $wikiLowerChars;
04059                 if ( isset( $wikiUpperChars ) ) {
04060                         return array( $wikiUpperChars, $wikiLowerChars );
04061                 }
04062 
04063                 wfProfileIn( __METHOD__ );
04064                 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
04065                 if ( $arr === false ) {
04066                         throw new MWException(
04067                                 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
04068                 }
04069                 $wikiUpperChars = $arr['wikiUpperChars'];
04070                 $wikiLowerChars = $arr['wikiLowerChars'];
04071                 wfProfileOut( __METHOD__ );
04072                 return array( $wikiUpperChars, $wikiLowerChars );
04073         }
04074 
04086         public function formatExpiry( $expiry, $format = true ) {
04087                 static $infinity, $infinityMsg;
04088                 if ( $infinity === null ) {
04089                         $infinityMsg = wfMessage( 'infiniteblock' );
04090                         $infinity = wfGetDB( DB_SLAVE )->getInfinity();
04091                 }
04092 
04093                 if ( $expiry == '' || $expiry == $infinity ) {
04094                         return $format === true
04095                                 ? $infinityMsg
04096                                 : $infinity;
04097                 } else {
04098                         return $format === true
04099                                 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
04100                                 : wfTimestamp( $format, $expiry );
04101                 }
04102         }
04103 
04114         function formatTimePeriod( $seconds, $format = array() ) {
04115                 if ( !is_array( $format ) ) {
04116                         $format = array( 'avoid' => $format ); // For backwards compatibility
04117                 }
04118                 if ( !isset( $format['avoid'] ) ) {
04119                         $format['avoid'] = false;
04120                 }
04121                 if ( !isset( $format['noabbrevs' ] ) ) {
04122                         $format['noabbrevs'] = false;
04123                 }
04124                 $secondsMsg = wfMessage(
04125                         $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
04126                 $minutesMsg = wfMessage(
04127                         $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
04128                 $hoursMsg = wfMessage(
04129                         $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
04130                 $daysMsg = wfMessage(
04131                         $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
04132 
04133                 if ( round( $seconds * 10 ) < 100 ) {
04134                         $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
04135                         $s = $secondsMsg->params( $s )->text();
04136                 } elseif ( round( $seconds ) < 60 ) {
04137                         $s = $this->formatNum( round( $seconds ) );
04138                         $s = $secondsMsg->params( $s )->text();
04139                 } elseif ( round( $seconds ) < 3600 ) {
04140                         $minutes = floor( $seconds / 60 );
04141                         $secondsPart = round( fmod( $seconds, 60 ) );
04142                         if ( $secondsPart == 60 ) {
04143                                 $secondsPart = 0;
04144                                 $minutes++;
04145                         }
04146                         $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04147                         $s .= ' ';
04148                         $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
04149                 } elseif ( round( $seconds ) <= 2 * 86400 ) {
04150                         $hours = floor( $seconds / 3600 );
04151                         $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
04152                         $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
04153                         if ( $secondsPart == 60 ) {
04154                                 $secondsPart = 0;
04155                                 $minutes++;
04156                         }
04157                         if ( $minutes == 60 ) {
04158                                 $minutes = 0;
04159                                 $hours++;
04160                         }
04161                         $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
04162                         $s .= ' ';
04163                         $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04164                         if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) {
04165                                 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
04166                         }
04167                 } else {
04168                         $days = floor( $seconds / 86400 );
04169                         if ( $format['avoid'] === 'avoidminutes' ) {
04170                                 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
04171                                 if ( $hours == 24 ) {
04172                                         $hours = 0;
04173                                         $days++;
04174                                 }
04175                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04176                                 $s .= ' ';
04177                                 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
04178                         } elseif ( $format['avoid'] === 'avoidseconds' ) {
04179                                 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
04180                                 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
04181                                 if ( $minutes == 60 ) {
04182                                         $minutes = 0;
04183                                         $hours++;
04184                                 }
04185                                 if ( $hours == 24 ) {
04186                                         $hours = 0;
04187                                         $days++;
04188                                 }
04189                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04190                                 $s .= ' ';
04191                                 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
04192                                 $s .= ' ';
04193                                 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04194                         } else {
04195                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04196                                 $s .= ' ';
04197                                 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
04198                         }
04199                 }
04200                 return $s;
04201         }
04202 
04213         function formatBitrate( $bps ) {
04214                 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
04215         }
04216 
04223         function formatComputingNumbers( $size, $boundary, $messageKey ) {
04224                 if ( $size <= 0 ) {
04225                         return str_replace( '$1', $this->formatNum( $size ),
04226                                 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
04227                         );
04228                 }
04229                 $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' );
04230                 $index = 0;
04231 
04232                 $maxIndex = count( $sizes ) - 1;
04233                 while ( $size >= $boundary && $index < $maxIndex ) {
04234                         $index++;
04235                         $size /= $boundary;
04236                 }
04237 
04238                 // For small sizes no decimal places necessary
04239                 $round = 0;
04240                 if ( $index > 1 ) {
04241                         // For MB and bigger two decimal places are smarter
04242                         $round = 2;
04243                 }
04244                 $msg = str_replace( '$1', $sizes[$index], $messageKey );
04245 
04246                 $size = round( $size, $round );
04247                 $text = $this->getMessageFromDB( $msg );
04248                 return str_replace( '$1', $this->formatNum( $size ), $text );
04249         }
04250 
04261         function formatSize( $size ) {
04262                 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
04263         }
04264 
04274         function specialList( $page, $details, $oppositedm = true ) {
04275                 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) .
04276                         $this->getDirMark();
04277                 $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) .
04278                         wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : '';
04279                 return $page . $details;
04280         }
04281 
04292         public function viewPrevNext( Title $title, $offset, $limit, array $query = array(), $atend = false ) {
04293                 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
04294 
04295                 # Make 'previous' link
04296                 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04297                 if ( $offset > 0 ) {
04298                         $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
04299                                 $query, $prev, 'prevn-title', 'mw-prevlink' );
04300                 } else {
04301                         $plink = htmlspecialchars( $prev );
04302                 }
04303 
04304                 # Make 'next' link
04305                 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04306                 if ( $atend ) {
04307                         $nlink = htmlspecialchars( $next );
04308                 } else {
04309                         $nlink = $this->numLink( $title, $offset + $limit, $limit,
04310                                 $query, $next, 'prevn-title', 'mw-nextlink' );
04311                 }
04312 
04313                 # Make links to set number of items per page
04314                 $numLinks = array();
04315                 foreach ( array( 20, 50, 100, 250, 500 ) as $num ) {
04316                         $numLinks[] = $this->numLink( $title, $offset, $num,
04317                                 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
04318                 }
04319 
04320                 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
04321                         )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
04322         }
04323 
04336         private function numLink( Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class ) {
04337                 $query = array( 'limit' => $limit, 'offset' => $offset ) + $query;
04338                 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04339                 return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ),
04340                         'title' => $tooltip, 'class' => $class ), $link );
04341         }
04342 
04348         public function getConvRuleTitle() {
04349                 return $this->mConverter->getConvRuleTitle();
04350         }
04351 
04357         public function getCompiledPluralRules() {
04358                 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
04359                 $fallbacks = Language::getFallbacksFor( $this->mCode );
04360                 if ( !$pluralRules ) {
04361                         foreach ( $fallbacks as $fallbackCode ) {
04362                                 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
04363                                 if ( $pluralRules ) {
04364                                         break;
04365                                 }
04366                         }
04367                 }
04368                 return $pluralRules;
04369         }
04370 
04376         public function getPluralRules() {
04377                 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
04378                 $fallbacks = Language::getFallbacksFor( $this->mCode );
04379                 if ( !$pluralRules ) {
04380                         foreach ( $fallbacks as $fallbackCode ) {
04381                                 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
04382                                 if ( $pluralRules ) {
04383                                         break;
04384                                 }
04385                         }
04386                 }
04387                 return $pluralRules;
04388         }
04389 
04395         private function getPluralForm( $number ) {
04396                 $pluralRules = $this->getCompiledPluralRules();
04397                 $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules );
04398                 return $form;
04399         }
04400 }