MediaWiki  master
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' => 31557600000,
00164                 'centuries' => 3155760000,
00165                 'decades' => 315576000,
00166                 'years' => 31557600, // 86400 * 365.25
00167                 'weeks' => 604800,
00168                 'days' => 86400,
00169                 'hours' => 3600,
00170                 'minutes' => 60,
00171                 'seconds' => 1,
00172         );
00173 
00179         static function factory( $code ) {
00180                 if ( !isset( self::$mLangObjCache[$code] ) ) {
00181                         if ( count( self::$mLangObjCache ) > 10 ) {
00182                                 // Don't keep a billion objects around, that's stupid.
00183                                 self::$mLangObjCache = array();
00184                         }
00185                         self::$mLangObjCache[$code] = self::newFromCode( $code );
00186                 }
00187                 return self::$mLangObjCache[$code];
00188         }
00189 
00196         protected static function newFromCode( $code ) {
00197                 // Protect against path traversal below
00198                 if ( !Language::isValidCode( $code )
00199                         || strcspn( $code, ":/\\\000" ) !== strlen( $code ) )
00200                 {
00201                         throw new MWException( "Invalid language code \"$code\"" );
00202                 }
00203 
00204                 if ( !Language::isValidBuiltInCode( $code ) ) {
00205                         // It's not possible to customise this code with class files, so
00206                         // just return a Language object. This is to support uselang= hacks.
00207                         $lang = new Language;
00208                         $lang->setCode( $code );
00209                         return $lang;
00210                 }
00211 
00212                 // Check if there is a language class for the code
00213                 $class = self::classFromCode( $code );
00214                 self::preloadLanguageClass( $class );
00215                 if ( MWInit::classExists( $class ) ) {
00216                         $lang = new $class;
00217                         return $lang;
00218                 }
00219 
00220                 // Keep trying the fallback list until we find an existing class
00221                 $fallbacks = Language::getFallbacksFor( $code );
00222                 foreach ( $fallbacks as $fallbackCode ) {
00223                         if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
00224                                 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
00225                         }
00226 
00227                         $class = self::classFromCode( $fallbackCode );
00228                         self::preloadLanguageClass( $class );
00229                         if ( MWInit::classExists( $class ) ) {
00230                                 $lang = Language::newFromCode( $fallbackCode );
00231                                 $lang->setCode( $code );
00232                                 return $lang;
00233                         }
00234                 }
00235 
00236                 throw new MWException( "Invalid fallback sequence for language '$code'" );
00237         }
00238 
00248         public static function isValidCode( $code ) {
00249                 return
00250                         // People think language codes are html safe, so enforce it.
00251                         // Ideally we should only allow a-zA-Z0-9-
00252                         // but, .+ and other chars are often used for {{int:}} hacks
00253                         // see bugs 37564, 37587, 36938
00254                         strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
00255                         && !preg_match( Title::getTitleInvalidRegex(), $code );
00256         }
00257 
00268         public static function isValidBuiltInCode( $code ) {
00269 
00270                 if ( !is_string( $code ) ) {
00271                         $type = gettype( $code );
00272                         if ( $type === 'object' ) {
00273                                 $addmsg = " of class " . get_class( $code );
00274                         } else {
00275                                 $addmsg = '';
00276                         }
00277                         throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
00278                 }
00279 
00280                 return preg_match( '/^[a-z0-9-]+$/i', $code );
00281         }
00282 
00287         public static function classFromCode( $code ) {
00288                 if ( $code == 'en' ) {
00289                         return 'Language';
00290                 } else {
00291                         return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
00292                 }
00293         }
00294 
00300         public static function preloadLanguageClass( $class ) {
00301                 global $IP;
00302 
00303                 if ( $class === 'Language' ) {
00304                         return;
00305                 }
00306 
00307                 if ( !defined( 'MW_COMPILED' ) ) {
00308                         if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
00309                                 include_once( "$IP/languages/classes/$class.php" );
00310                         }
00311                 }
00312         }
00313 
00319         public static function getLocalisationCache() {
00320                 if ( is_null( self::$dataCache ) ) {
00321                         global $wgLocalisationCacheConf;
00322                         $class = $wgLocalisationCacheConf['class'];
00323                         self::$dataCache = new $class( $wgLocalisationCacheConf );
00324                 }
00325                 return self::$dataCache;
00326         }
00327 
00328         function __construct() {
00329                 $this->mConverter = new FakeConverter( $this );
00330                 // Set the code to the name of the descendant
00331                 if ( get_class( $this ) == 'Language' ) {
00332                         $this->mCode = 'en';
00333                 } else {
00334                         $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
00335                 }
00336                 self::getLocalisationCache();
00337         }
00338 
00342         function __destruct() {
00343                 foreach ( $this as $name => $value ) {
00344                         unset( $this->$name );
00345                 }
00346         }
00347 
00352         function initContLang() { }
00353 
00359         function getFallbackLanguageCode() {
00360                 wfDeprecated( __METHOD__, '1.19' );
00361                 return self::getFallbackFor( $this->mCode );
00362         }
00363 
00368         function getFallbackLanguages() {
00369                 return self::getFallbacksFor( $this->mCode );
00370         }
00371 
00376         function getBookstoreList() {
00377                 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
00378         }
00379 
00383         public function getNamespaces() {
00384                 if ( is_null( $this->namespaceNames ) ) {
00385                         global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces;
00386 
00387                         $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
00388                         $validNamespaces = MWNamespace::getCanonicalNamespaces();
00389 
00390                         $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
00391 
00392                         $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
00393                         if ( $wgMetaNamespaceTalk ) {
00394                                 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
00395                         } else {
00396                                 $talk = $this->namespaceNames[NS_PROJECT_TALK];
00397                                 $this->namespaceNames[NS_PROJECT_TALK] =
00398                                         $this->fixVariableInNamespace( $talk );
00399                         }
00400 
00401                         # Sometimes a language will be localised but not actually exist on this wiki.
00402                         foreach ( $this->namespaceNames as $key => $text ) {
00403                                 if ( !isset( $validNamespaces[$key] ) ) {
00404                                         unset( $this->namespaceNames[$key] );
00405                                 }
00406                         }
00407 
00408                         # The above mixing may leave namespaces out of canonical order.
00409                         # Re-order by namespace ID number...
00410                         ksort( $this->namespaceNames );
00411 
00412                         wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) );
00413                 }
00414                 return $this->namespaceNames;
00415         }
00416 
00421         public function setNamespaces( array $namespaces ) {
00422                 $this->namespaceNames = $namespaces;
00423                 $this->mNamespaceIds = null;
00424         }
00425 
00429         public function resetNamespaces( ) {
00430                 $this->namespaceNames = null;
00431                 $this->mNamespaceIds = null;
00432                 $this->namespaceAliases = null;
00433         }
00434 
00443         function getFormattedNamespaces() {
00444                 $ns = $this->getNamespaces();
00445                 foreach ( $ns as $k => $v ) {
00446                         $ns[$k] = strtr( $v, '_', ' ' );
00447                 }
00448                 return $ns;
00449         }
00450 
00461         function getNsText( $index ) {
00462                 $ns = $this->getNamespaces();
00463                 return isset( $ns[$index] ) ? $ns[$index] : false;
00464         }
00465 
00475         function getFormattedNsText( $index ) {
00476                 $ns = $this->getNsText( $index );
00477                 return strtr( $ns, '_', ' ' );
00478         }
00479 
00487         function getGenderNsText( $index, $gender ) {
00488                 global $wgExtraGenderNamespaces;
00489 
00490                 $ns = $wgExtraGenderNamespaces + self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00491                 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
00492         }
00493 
00500         function needsGenderDistinction() {
00501                 global $wgExtraGenderNamespaces, $wgExtraNamespaces;
00502                 if ( count( $wgExtraGenderNamespaces ) > 0 ) {
00503                         // $wgExtraGenderNamespaces overrides everything
00504                         return true;
00505                 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
00507                         // $wgExtraNamespaces overrides any gender aliases specified in i18n files
00508                         return false;
00509                 } else {
00510                         // Check what is in i18n files
00511                         $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00512                         return count( $aliases ) > 0;
00513                 }
00514         }
00515 
00524         function getLocalNsIndex( $text ) {
00525                 $lctext = $this->lc( $text );
00526                 $ids = $this->getNamespaceIds();
00527                 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
00528         }
00529 
00533         function getNamespaceAliases() {
00534                 if ( is_null( $this->namespaceAliases ) ) {
00535                         $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
00536                         if ( !$aliases ) {
00537                                 $aliases = array();
00538                         } else {
00539                                 foreach ( $aliases as $name => $index ) {
00540                                         if ( $index === NS_PROJECT_TALK ) {
00541                                                 unset( $aliases[$name] );
00542                                                 $name = $this->fixVariableInNamespace( $name );
00543                                                 $aliases[$name] = $index;
00544                                         }
00545                                 }
00546                         }
00547 
00548                         global $wgExtraGenderNamespaces;
00549                         $genders = $wgExtraGenderNamespaces + (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00550                         foreach ( $genders as $index => $forms ) {
00551                                 foreach ( $forms as $alias ) {
00552                                         $aliases[$alias] = $index;
00553                                 }
00554                         }
00555 
00556                         $this->namespaceAliases = $aliases;
00557                 }
00558                 return $this->namespaceAliases;
00559         }
00560 
00564         function getNamespaceIds() {
00565                 if ( is_null( $this->mNamespaceIds ) ) {
00566                         global $wgNamespaceAliases;
00567                         # Put namespace names and aliases into a hashtable.
00568                         # If this is too slow, then we should arrange it so that it is done
00569                         # before caching. The catch is that at pre-cache time, the above
00570                         # class-specific fixup hasn't been done.
00571                         $this->mNamespaceIds = array();
00572                         foreach ( $this->getNamespaces() as $index => $name ) {
00573                                 $this->mNamespaceIds[$this->lc( $name )] = $index;
00574                         }
00575                         foreach ( $this->getNamespaceAliases() as $name => $index ) {
00576                                 $this->mNamespaceIds[$this->lc( $name )] = $index;
00577                         }
00578                         if ( $wgNamespaceAliases ) {
00579                                 foreach ( $wgNamespaceAliases as $name => $index ) {
00580                                         $this->mNamespaceIds[$this->lc( $name )] = $index;
00581                                 }
00582                         }
00583                 }
00584                 return $this->mNamespaceIds;
00585         }
00586 
00594         function getNsIndex( $text ) {
00595                 $lctext = $this->lc( $text );
00596                 $ns = MWNamespace::getCanonicalIndex( $lctext );
00597                 if ( $ns !== null ) {
00598                         return $ns;
00599                 }
00600                 $ids = $this->getNamespaceIds();
00601                 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
00602         }
00603 
00611         function getVariantname( $code, $usemsg = true ) {
00612                 $msg = "variantname-$code";
00613                 if ( $usemsg && wfMessage( $msg )->exists() ) {
00614                         return $this->getMessageFromDB( $msg );
00615                 }
00616                 $name = self::fetchLanguageName( $code );
00617                 if ( $name ) {
00618                         return $name; # if it's defined as a language name, show that
00619                 } else {
00620                         # otherwise, output the language code
00621                         return $code;
00622                 }
00623         }
00624 
00629         function specialPage( $name ) {
00630                 $aliases = $this->getSpecialPageAliases();
00631                 if ( isset( $aliases[$name][0] ) ) {
00632                         $name = $aliases[$name][0];
00633                 }
00634                 return $this->getNsText( NS_SPECIAL ) . ':' . $name;
00635         }
00636 
00640         function getQuickbarSettings() {
00641                 return array(
00642                         $this->getMessage( 'qbsettings-none' ),
00643                         $this->getMessage( 'qbsettings-fixedleft' ),
00644                         $this->getMessage( 'qbsettings-fixedright' ),
00645                         $this->getMessage( 'qbsettings-floatingleft' ),
00646                         $this->getMessage( 'qbsettings-floatingright' ),
00647                         $this->getMessage( 'qbsettings-directionality' )
00648                 );
00649         }
00650 
00654         function getDatePreferences() {
00655                 return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
00656         }
00657 
00661         function getDateFormats() {
00662                 return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
00663         }
00664 
00668         function getDefaultDateFormat() {
00669                 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
00670                 if ( $df === 'dmy or mdy' ) {
00671                         global $wgAmericanDates;
00672                         return $wgAmericanDates ? 'mdy' : 'dmy';
00673                 } else {
00674                         return $df;
00675                 }
00676         }
00677 
00681         function getDatePreferenceMigrationMap() {
00682                 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
00683         }
00684 
00689         function getImageFile( $image ) {
00690                 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
00691         }
00692 
00696         function getExtraUserToggles() {
00697                 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
00698         }
00699 
00704         function getUserToggle( $tog ) {
00705                 return $this->getMessageFromDB( "tog-$tog" );
00706         }
00707 
00718         public static function getLanguageNames( $customisedOnly = false ) {
00719                 return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' );
00720         }
00721 
00731         public static function getTranslatedLanguageNames( $code ) {
00732                 return self::fetchLanguageNames( $code, 'all' );
00733         }
00734 
00746         public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
00747                 global $wgExtraLanguageNames;
00748                 static $coreLanguageNames;
00749 
00750                 if ( $coreLanguageNames === null ) {
00751                         include( MWInit::compiledPath( 'languages/Names.php' ) );
00752                 }
00753 
00754                 $names = array();
00755 
00756                 if ( $inLanguage ) {
00757                         # TODO: also include when $inLanguage is null, when this code is more efficient
00758                         wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) );
00759                 }
00760 
00761                 $mwNames = $wgExtraLanguageNames + $coreLanguageNames;
00762                 foreach ( $mwNames as $mwCode => $mwName ) {
00763                         # - Prefer own MediaWiki native name when not using the hook
00764                         # - For other names just add if not added through the hook
00765                         if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
00766                                 $names[$mwCode] = $mwName;
00767                         }
00768                 }
00769 
00770                 if ( $include === 'all' ) {
00771                         return $names;
00772                 }
00773 
00774                 $returnMw = array();
00775                 $coreCodes = array_keys( $mwNames );
00776                 foreach ( $coreCodes as $coreCode ) {
00777                         $returnMw[$coreCode] = $names[$coreCode];
00778                 }
00779 
00780                 if ( $include === 'mwfile' ) {
00781                         $namesMwFile = array();
00782                         # We do this using a foreach over the codes instead of a directory
00783                         # loop so that messages files in extensions will work correctly.
00784                         foreach ( $returnMw as $code => $value ) {
00785                                 if ( is_readable( self::getMessagesFileName( $code ) ) ) {
00786                                         $namesMwFile[$code] = $names[$code];
00787                                 }
00788                         }
00789                         return $namesMwFile;
00790                 }
00791                 # 'mw' option; default if it's not one of the other two options (all/mwfile)
00792                 return $returnMw;
00793         }
00794 
00802         public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
00803                 $array = self::fetchLanguageNames( $inLanguage, $include );
00804                 return !array_key_exists( $code, $array ) ? '' : $array[$code];
00805         }
00806 
00813         function getMessageFromDB( $msg ) {
00814                 return wfMessage( $msg )->inLanguage( $this )->text();
00815         }
00816 
00824         function getLanguageName( $code ) {
00825                 return self::fetchLanguageName( $code );
00826         }
00827 
00832         function getMonthName( $key ) {
00833                 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
00834         }
00835 
00839         function getMonthNamesArray() {
00840                 $monthNames = array( '' );
00841                 for ( $i = 1; $i < 13; $i++ ) {
00842                         $monthNames[] = $this->getMonthName( $i );
00843                 }
00844                 return $monthNames;
00845         }
00846 
00851         function getMonthNameGen( $key ) {
00852                 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
00853         }
00854 
00859         function getMonthAbbreviation( $key ) {
00860                 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
00861         }
00862 
00866         function getMonthAbbreviationsArray() {
00867                 $monthNames = array( '' );
00868                 for ( $i = 1; $i < 13; $i++ ) {
00869                         $monthNames[] = $this->getMonthAbbreviation( $i );
00870                 }
00871                 return $monthNames;
00872         }
00873 
00878         function getWeekdayName( $key ) {
00879                 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
00880         }
00881 
00886         function getWeekdayAbbreviation( $key ) {
00887                 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
00888         }
00889 
00894         function getIranianCalendarMonthName( $key ) {
00895                 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
00896         }
00897 
00902         function getHebrewCalendarMonthName( $key ) {
00903                 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
00904         }
00905 
00910         function getHebrewCalendarMonthNameGen( $key ) {
00911                 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
00912         }
00913 
00918         function getHijriCalendarMonthName( $key ) {
00919                 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
00920         }
00921 
00984         function sprintfDate( $format, $ts ) {
00985                 $s = '';
00986                 $raw = false;
00987                 $roman = false;
00988                 $hebrewNum = false;
00989                 $unix = false;
00990                 $rawToggle = false;
00991                 $iranian = false;
00992                 $hebrew = false;
00993                 $hijri = false;
00994                 $thai = false;
00995                 $minguo = false;
00996                 $tenno = false;
00997                 for ( $p = 0; $p < strlen( $format ); $p++ ) {
00998                         $num = false;
00999                         $code = $format[$p];
01000                         if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
01001                                 $code .= $format[++$p];
01002                         }
01003 
01004                         if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' || $code == 'xo' || $code == 'xt' ) && $p < strlen( $format ) - 1 ) {
01005                                 $code .= $format[++$p];
01006                         }
01007 
01008                         switch ( $code ) {
01009                                 case 'xx':
01010                                         $s .= 'x';
01011                                         break;
01012                                 case 'xn':
01013                                         $raw = true;
01014                                         break;
01015                                 case 'xN':
01016                                         $rawToggle = !$rawToggle;
01017                                         break;
01018                                 case 'xr':
01019                                         $roman = true;
01020                                         break;
01021                                 case 'xh':
01022                                         $hebrewNum = true;
01023                                         break;
01024                                 case 'xg':
01025                                         $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
01026                                         break;
01027                                 case 'xjx':
01028                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
01029                                         $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
01030                                         break;
01031                                 case 'd':
01032                                         $num = substr( $ts, 6, 2 );
01033                                         break;
01034                                 case 'D':
01035                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
01036                                         $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) + 1 );
01037                                         break;
01038                                 case 'j':
01039                                         $num = intval( substr( $ts, 6, 2 ) );
01040                                         break;
01041                                 case 'xij':
01042                                         if ( !$iranian ) {
01043                                                 $iranian = self::tsToIranian( $ts );
01044                                         }
01045                                         $num = $iranian[2];
01046                                         break;
01047                                 case 'xmj':
01048                                         if ( !$hijri ) {
01049                                                 $hijri = self::tsToHijri( $ts );
01050                                         }
01051                                         $num = $hijri[2];
01052                                         break;
01053                                 case 'xjj':
01054                                         if ( !$hebrew ) {
01055                                                 $hebrew = self::tsToHebrew( $ts );
01056                                         }
01057                                         $num = $hebrew[2];
01058                                         break;
01059                                 case 'l':
01060                                         if ( !$unix ) {
01061                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01062                                         }
01063                                         $s .= $this->getWeekdayName( gmdate( 'w', $unix ) + 1 );
01064                                         break;
01065                                 case 'N':
01066                                         if ( !$unix ) {
01067                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01068                                         }
01069                                         $w = gmdate( 'w', $unix );
01070                                         $num = $w ? $w : 7;
01071                                         break;
01072                                 case 'w':
01073                                         if ( !$unix ) {
01074                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01075                                         }
01076                                         $num = gmdate( 'w', $unix );
01077                                         break;
01078                                 case 'z':
01079                                         if ( !$unix ) {
01080                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01081                                         }
01082                                         $num = gmdate( 'z', $unix );
01083                                         break;
01084                                 case 'W':
01085                                         if ( !$unix ) {
01086                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01087                                         }
01088                                         $num = gmdate( 'W', $unix );
01089                                         break;
01090                                 case 'F':
01091                                         $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
01092                                         break;
01093                                 case 'xiF':
01094                                         if ( !$iranian ) {
01095                                                 $iranian = self::tsToIranian( $ts );
01096                                         }
01097                                         $s .= $this->getIranianCalendarMonthName( $iranian[1] );
01098                                         break;
01099                                 case 'xmF':
01100                                         if ( !$hijri ) {
01101                                                 $hijri = self::tsToHijri( $ts );
01102                                         }
01103                                         $s .= $this->getHijriCalendarMonthName( $hijri[1] );
01104                                         break;
01105                                 case 'xjF':
01106                                         if ( !$hebrew ) {
01107                                                 $hebrew = self::tsToHebrew( $ts );
01108                                         }
01109                                         $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
01110                                         break;
01111                                 case 'm':
01112                                         $num = substr( $ts, 4, 2 );
01113                                         break;
01114                                 case 'M':
01115                                         $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
01116                                         break;
01117                                 case 'n':
01118                                         $num = intval( substr( $ts, 4, 2 ) );
01119                                         break;
01120                                 case 'xin':
01121                                         if ( !$iranian ) {
01122                                                 $iranian = self::tsToIranian( $ts );
01123                                         }
01124                                         $num = $iranian[1];
01125                                         break;
01126                                 case 'xmn':
01127                                         if ( !$hijri ) {
01128                                                 $hijri = self::tsToHijri ( $ts );
01129                                         }
01130                                         $num = $hijri[1];
01131                                         break;
01132                                 case 'xjn':
01133                                         if ( !$hebrew ) {
01134                                                 $hebrew = self::tsToHebrew( $ts );
01135                                         }
01136                                         $num = $hebrew[1];
01137                                         break;
01138                                 case 't':
01139                                         if ( !$unix ) {
01140                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01141                                         }
01142                                         $num = gmdate( 't', $unix );
01143                                         break;
01144                                 case 'xjt':
01145                                         if ( !$hebrew ) {
01146                                                 $hebrew = self::tsToHebrew( $ts );
01147                                         }
01148                                         $num = $hebrew[3];
01149                                         break;
01150                                 case 'L':
01151                                         if ( !$unix ) {
01152                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01153                                         }
01154                                         $num = gmdate( 'L', $unix );
01155                                         break;
01156                                 case 'o':
01157                                         if ( !$unix ) {
01158                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01159                                         }
01160                                         $num = gmdate( 'o', $unix );
01161                                         break;
01162                                 case 'Y':
01163                                         $num = substr( $ts, 0, 4 );
01164                                         break;
01165                                 case 'xiY':
01166                                         if ( !$iranian ) {
01167                                                 $iranian = self::tsToIranian( $ts );
01168                                         }
01169                                         $num = $iranian[0];
01170                                         break;
01171                                 case 'xmY':
01172                                         if ( !$hijri ) {
01173                                                 $hijri = self::tsToHijri( $ts );
01174                                         }
01175                                         $num = $hijri[0];
01176                                         break;
01177                                 case 'xjY':
01178                                         if ( !$hebrew ) {
01179                                                 $hebrew = self::tsToHebrew( $ts );
01180                                         }
01181                                         $num = $hebrew[0];
01182                                         break;
01183                                 case 'xkY':
01184                                         if ( !$thai ) {
01185                                                 $thai = self::tsToYear( $ts, 'thai' );
01186                                         }
01187                                         $num = $thai[0];
01188                                         break;
01189                                 case 'xoY':
01190                                         if ( !$minguo ) {
01191                                                 $minguo = self::tsToYear( $ts, 'minguo' );
01192                                         }
01193                                         $num = $minguo[0];
01194                                         break;
01195                                 case 'xtY':
01196                                         if ( !$tenno ) {
01197                                                 $tenno = self::tsToYear( $ts, 'tenno' );
01198                                         }
01199                                         $num = $tenno[0];
01200                                         break;
01201                                 case 'y':
01202                                         $num = substr( $ts, 2, 2 );
01203                                         break;
01204                                 case 'xiy':
01205                                         if ( !$iranian ) {
01206                                                 $iranian = self::tsToIranian( $ts );
01207                                         }
01208                                         $num = substr( $iranian[0], -2 );
01209                                         break;
01210                                 case 'a':
01211                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
01212                                         break;
01213                                 case 'A':
01214                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
01215                                         break;
01216                                 case 'g':
01217                                         $h = substr( $ts, 8, 2 );
01218                                         $num = $h % 12 ? $h % 12 : 12;
01219                                         break;
01220                                 case 'G':
01221                                         $num = intval( substr( $ts, 8, 2 ) );
01222                                         break;
01223                                 case 'h':
01224                                         $h = substr( $ts, 8, 2 );
01225                                         $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
01226                                         break;
01227                                 case 'H':
01228                                         $num = substr( $ts, 8, 2 );
01229                                         break;
01230                                 case 'i':
01231                                         $num = substr( $ts, 10, 2 );
01232                                         break;
01233                                 case 's':
01234                                         $num = substr( $ts, 12, 2 );
01235                                         break;
01236                                 case 'c':
01237                                         if ( !$unix ) {
01238                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01239                                         }
01240                                         $s .= gmdate( 'c', $unix );
01241                                         break;
01242                                 case 'r':
01243                                         if ( !$unix ) {
01244                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01245                                         }
01246                                         $s .= gmdate( 'r', $unix );
01247                                         break;
01248                                 case 'U':
01249                                         if ( !$unix ) {
01250                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01251                                         }
01252                                         $num = $unix;
01253                                         break;
01254                                 case '\\':
01255                                         # Backslash escaping
01256                                         if ( $p < strlen( $format ) - 1 ) {
01257                                                 $s .= $format[++$p];
01258                                         } else {
01259                                                 $s .= '\\';
01260                                         }
01261                                         break;
01262                                 case '"':
01263                                         # Quoted literal
01264                                         if ( $p < strlen( $format ) - 1 ) {
01265                                                 $endQuote = strpos( $format, '"', $p + 1 );
01266                                                 if ( $endQuote === false ) {
01267                                                         # No terminating quote, assume literal "
01268                                                         $s .= '"';
01269                                                 } else {
01270                                                         $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
01271                                                         $p = $endQuote;
01272                                                 }
01273                                         } else {
01274                                                 # Quote at end of string, assume literal "
01275                                                 $s .= '"';
01276                                         }
01277                                         break;
01278                                 default:
01279                                         $s .= $format[$p];
01280                         }
01281                         if ( $num !== false ) {
01282                                 if ( $rawToggle || $raw ) {
01283                                         $s .= $num;
01284                                         $raw = false;
01285                                 } elseif ( $roman ) {
01286                                         $s .= Language::romanNumeral( $num );
01287                                         $roman = false;
01288                                 } elseif ( $hebrewNum ) {
01289                                         $s .= self::hebrewNumeral( $num );
01290                                         $hebrewNum = false;
01291                                 } else {
01292                                         $s .= $this->formatNum( $num, true );
01293                                 }
01294                         }
01295                 }
01296                 return $s;
01297         }
01298 
01299         private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
01300         private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
01301 
01314         private static function tsToIranian( $ts ) {
01315                 $gy = substr( $ts, 0, 4 ) -1600;
01316                 $gm = substr( $ts, 4, 2 ) -1;
01317                 $gd = substr( $ts, 6, 2 ) -1;
01318 
01319                 # Days passed from the beginning (including leap years)
01320                 $gDayNo = 365 * $gy
01321                         + floor( ( $gy + 3 ) / 4 )
01322                         - floor( ( $gy + 99 ) / 100 )
01323                         + floor( ( $gy + 399 ) / 400 );
01324 
01325                 // Add days of the past months of this year
01326                 for ( $i = 0; $i < $gm; $i++ ) {
01327                         $gDayNo += self::$GREG_DAYS[$i];
01328                 }
01329 
01330                 // Leap years
01331                 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
01332                         $gDayNo++;
01333                 }
01334 
01335                 // Days passed in current month
01336                 $gDayNo += (int)$gd;
01337 
01338                 $jDayNo = $gDayNo - 79;
01339 
01340                 $jNp = floor( $jDayNo / 12053 );
01341                 $jDayNo %= 12053;
01342 
01343                 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
01344                 $jDayNo %= 1461;
01345 
01346                 if ( $jDayNo >= 366 ) {
01347                         $jy += floor( ( $jDayNo - 1 ) / 365 );
01348                         $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
01349                 }
01350 
01351                 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
01352                         $jDayNo -= self::$IRANIAN_DAYS[$i];
01353                 }
01354 
01355                 $jm = $i + 1;
01356                 $jd = $jDayNo + 1;
01357 
01358                 return array( $jy, $jm, $jd );
01359         }
01360 
01372         private static function tsToHijri( $ts ) {
01373                 $year = substr( $ts, 0, 4 );
01374                 $month = substr( $ts, 4, 2 );
01375                 $day = substr( $ts, 6, 2 );
01376 
01377                 $zyr = $year;
01378                 $zd = $day;
01379                 $zm = $month;
01380                 $zy = $zyr;
01381 
01382                 if (
01383                         ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
01384                         ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
01385                 )
01386                 {
01387                         $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
01388                                         (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
01389                                         (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
01390                                         $zd - 32075;
01391                 } else {
01392                         $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
01393                                                                 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
01394                 }
01395 
01396                 $zl = $zjd -1948440 + 10632;
01397                 $zn = (int)( ( $zl - 1 ) / 10631 );
01398                 $zl = $zl - 10631 * $zn + 354;
01399                 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) + ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
01400                 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) - ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
01401                 $zm = (int)( ( 24 * $zl ) / 709 );
01402                 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
01403                 $zy = 30 * $zn + $zj - 30;
01404 
01405                 return array( $zy, $zm, $zd );
01406         }
01407 
01423         private static function tsToHebrew( $ts ) {
01424                 # Parse date
01425                 $year = substr( $ts, 0, 4 );
01426                 $month = substr( $ts, 4, 2 );
01427                 $day = substr( $ts, 6, 2 );
01428 
01429                 # Calculate Hebrew year
01430                 $hebrewYear = $year + 3760;
01431 
01432                 # Month number when September = 1, August = 12
01433                 $month += 4;
01434                 if ( $month > 12 ) {
01435                         # Next year
01436                         $month -= 12;
01437                         $year++;
01438                         $hebrewYear++;
01439                 }
01440 
01441                 # Calculate day of year from 1 September
01442                 $dayOfYear = $day;
01443                 for ( $i = 1; $i < $month; $i++ ) {
01444                         if ( $i == 6 ) {
01445                                 # February
01446                                 $dayOfYear += 28;
01447                                 # Check if the year is leap
01448                                 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
01449                                         $dayOfYear++;
01450                                 }
01451                         } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
01452                                 $dayOfYear += 30;
01453                         } else {
01454                                 $dayOfYear += 31;
01455                         }
01456                 }
01457 
01458                 # Calculate the start of the Hebrew year
01459                 $start = self::hebrewYearStart( $hebrewYear );
01460 
01461                 # Calculate next year's start
01462                 if ( $dayOfYear <= $start ) {
01463                         # Day is before the start of the year - it is the previous year
01464                         # Next year's start
01465                         $nextStart = $start;
01466                         # Previous year
01467                         $year--;
01468                         $hebrewYear--;
01469                         # Add days since previous year's 1 September
01470                         $dayOfYear += 365;
01471                         if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
01472                                 # Leap year
01473                                 $dayOfYear++;
01474                         }
01475                         # Start of the new (previous) year
01476                         $start = self::hebrewYearStart( $hebrewYear );
01477                 } else {
01478                         # Next year's start
01479                         $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
01480                 }
01481 
01482                 # Calculate Hebrew day of year
01483                 $hebrewDayOfYear = $dayOfYear - $start;
01484 
01485                 # Difference between year's days
01486                 $diff = $nextStart - $start;
01487                 # Add 12 (or 13 for leap years) days to ignore the difference between
01488                 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
01489                 # difference is only about the year type
01490                 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
01491                         $diff += 13;
01492                 } else {
01493                         $diff += 12;
01494                 }
01495 
01496                 # Check the year pattern, and is leap year
01497                 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
01498                 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
01499                 # and non-leap years
01500                 $yearPattern = $diff % 30;
01501                 # Check if leap year
01502                 $isLeap = $diff >= 30;
01503 
01504                 # Calculate day in the month from number of day in the Hebrew year
01505                 # Don't check Adar - if the day is not in Adar, we will stop before;
01506                 # if it is in Adar, we will use it to check if it is Adar I or Adar II
01507                 $hebrewDay = $hebrewDayOfYear;
01508                 $hebrewMonth = 1;
01509                 $days = 0;
01510                 while ( $hebrewMonth <= 12 ) {
01511                         # Calculate days in this month
01512                         if ( $isLeap && $hebrewMonth == 6 ) {
01513                                 # Adar in a leap year
01514                                 if ( $isLeap ) {
01515                                         # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
01516                                         $days = 30;
01517                                         if ( $hebrewDay <= $days ) {
01518                                                 # Day in Adar I
01519                                                 $hebrewMonth = 13;
01520                                         } else {
01521                                                 # Subtract the days of Adar I
01522                                                 $hebrewDay -= $days;
01523                                                 # Try Adar II
01524                                                 $days = 29;
01525                                                 if ( $hebrewDay <= $days ) {
01526                                                         # Day in Adar II
01527                                                         $hebrewMonth = 14;
01528                                                 }
01529                                         }
01530                                 }
01531                         } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
01532                                 # Cheshvan in a complete year (otherwise as the rule below)
01533                                 $days = 30;
01534                         } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
01535                                 # Kislev in an incomplete year (otherwise as the rule below)
01536                                 $days = 29;
01537                         } else {
01538                                 # Odd months have 30 days, even have 29
01539                                 $days = 30 - ( $hebrewMonth - 1 ) % 2;
01540                         }
01541                         if ( $hebrewDay <= $days ) {
01542                                 # In the current month
01543                                 break;
01544                         } else {
01545                                 # Subtract the days of the current month
01546                                 $hebrewDay -= $days;
01547                                 # Try in the next month
01548                                 $hebrewMonth++;
01549                         }
01550                 }
01551 
01552                 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
01553         }
01554 
01564         private static function hebrewYearStart( $year ) {
01565                 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
01566                 $b = intval( ( $year - 1 ) % 4 );
01567                 $m = 32.044093161144 + 1.5542417966212 * $a +  $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
01568                 if ( $m < 0 ) {
01569                         $m--;
01570                 }
01571                 $Mar = intval( $m );
01572                 if ( $m < 0 ) {
01573                         $m++;
01574                 }
01575                 $m -= $Mar;
01576 
01577                 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
01578                 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
01579                         $Mar++;
01580                 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
01581                         $Mar += 2;
01582                 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
01583                         $Mar++;
01584                 }
01585 
01586                 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
01587                 return $Mar;
01588         }
01589 
01602         private static function tsToYear( $ts, $cName ) {
01603                 $gy = substr( $ts, 0, 4 );
01604                 $gm = substr( $ts, 4, 2 );
01605                 $gd = substr( $ts, 6, 2 );
01606 
01607                 if ( !strcmp( $cName, 'thai' ) ) {
01608                         # Thai solar dates
01609                         # Add 543 years to the Gregorian calendar
01610                         # Months and days are identical
01611                         $gy_offset = $gy + 543;
01612                 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
01613                         # Minguo dates
01614                         # Deduct 1911 years from the Gregorian calendar
01615                         # Months and days are identical
01616                         $gy_offset = $gy - 1911;
01617                 } elseif ( !strcmp( $cName, 'tenno' ) ) {
01618                         # Nengō dates up to Meiji period
01619                         # Deduct years from the Gregorian calendar
01620                         # depending on the nengo periods
01621                         # Months and days are identical
01622                         if ( ( $gy < 1912 ) || ( ( $gy == 1912 ) && ( $gm < 7 ) ) || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) ) ) {
01623                                 # Meiji period
01624                                 $gy_gannen = $gy - 1868 + 1;
01625                                 $gy_offset = $gy_gannen;
01626                                 if ( $gy_gannen == 1 ) {
01627                                         $gy_offset = '元';
01628                                 }
01629                                 $gy_offset = '明治' . $gy_offset;
01630                         } elseif (
01631                                 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
01632                                 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
01633                                 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
01634                                 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
01635                                 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
01636                         )
01637                         {
01638                                 # Taishō period
01639                                 $gy_gannen = $gy - 1912 + 1;
01640                                 $gy_offset = $gy_gannen;
01641                                 if ( $gy_gannen == 1 ) {
01642                                         $gy_offset = '元';
01643                                 }
01644                                 $gy_offset = '大正' . $gy_offset;
01645                         } elseif (
01646                                 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
01647                                 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
01648                                 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
01649                         )
01650                         {
01651                                 # Shōwa period
01652                                 $gy_gannen = $gy - 1926 + 1;
01653                                 $gy_offset = $gy_gannen;
01654                                 if ( $gy_gannen == 1 ) {
01655                                         $gy_offset = '元';
01656                                 }
01657                                 $gy_offset = '昭和' . $gy_offset;
01658                         } else {
01659                                 # Heisei period
01660                                 $gy_gannen = $gy - 1989 + 1;
01661                                 $gy_offset = $gy_gannen;
01662                                 if ( $gy_gannen == 1 ) {
01663                                         $gy_offset = '元';
01664                                 }
01665                                 $gy_offset = '平成' . $gy_offset;
01666                         }
01667                 } else {
01668                         $gy_offset = $gy;
01669                 }
01670 
01671                 return array( $gy_offset, $gm, $gd );
01672         }
01673 
01681         static function romanNumeral( $num ) {
01682                 static $table = array(
01683                         array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
01684                         array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
01685                         array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
01686                         array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM', 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' )
01687                 );
01688 
01689                 $num = intval( $num );
01690                 if ( $num > 10000 || $num <= 0 ) {
01691                         return $num;
01692                 }
01693 
01694                 $s = '';
01695                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
01696                         if ( $num >= $pow10 ) {
01697                                 $s .= $table[$i][(int)floor( $num / $pow10 )];
01698                         }
01699                         $num = $num % $pow10;
01700                 }
01701                 return $s;
01702         }
01703 
01711         static function hebrewNumeral( $num ) {
01712                 static $table = array(
01713                         array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
01714                         array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
01715                         array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
01716                         array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
01717                 );
01718 
01719                 $num = intval( $num );
01720                 if ( $num > 9999 || $num <= 0 ) {
01721                         return $num;
01722                 }
01723 
01724                 $s = '';
01725                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
01726                         if ( $num >= $pow10 ) {
01727                                 if ( $num == 15 || $num == 16 ) {
01728                                         $s .= $table[0][9] . $table[0][$num - 9];
01729                                         $num = 0;
01730                                 } else {
01731                                         $s .= $table[$i][intval( ( $num / $pow10 ) )];
01732                                         if ( $pow10 == 1000 ) {
01733                                                 $s .= "'";
01734                                         }
01735                                 }
01736                         }
01737                         $num = $num % $pow10;
01738                 }
01739                 if ( strlen( $s ) == 2 ) {
01740                         $str = $s . "'";
01741                 } else  {
01742                         $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
01743                         $str .= substr( $s, strlen( $s ) - 2, 2 );
01744                 }
01745                 $start = substr( $str, 0, strlen( $str ) - 2 );
01746                 $end = substr( $str, strlen( $str ) - 2 );
01747                 switch( $end ) {
01748                         case 'כ':
01749                                 $str = $start . 'ך';
01750                                 break;
01751                         case 'מ':
01752                                 $str = $start . 'ם';
01753                                 break;
01754                         case 'נ':
01755                                 $str = $start . 'ן';
01756                                 break;
01757                         case 'פ':
01758                                 $str = $start . 'ף';
01759                                 break;
01760                         case 'צ':
01761                                 $str = $start . 'ץ';
01762                                 break;
01763                 }
01764                 return $str;
01765         }
01766 
01775         function userAdjust( $ts, $tz = false ) {
01776                 global $wgUser, $wgLocalTZoffset;
01777 
01778                 if ( $tz === false ) {
01779                         $tz = $wgUser->getOption( 'timecorrection' );
01780                 }
01781 
01782                 $data = explode( '|', $tz, 3 );
01783 
01784                 if ( $data[0] == 'ZoneInfo' ) {
01785                         wfSuppressWarnings();
01786                         $userTZ = timezone_open( $data[2] );
01787                         wfRestoreWarnings();
01788                         if ( $userTZ !== false ) {
01789                                 $date = date_create( $ts, timezone_open( 'UTC' ) );
01790                                 date_timezone_set( $date, $userTZ );
01791                                 $date = date_format( $date, 'YmdHis' );
01792                                 return $date;
01793                         }
01794                         # Unrecognized timezone, default to 'Offset' with the stored offset.
01795                         $data[0] = 'Offset';
01796                 }
01797 
01798                 $minDiff = 0;
01799                 if ( $data[0] == 'System' || $tz == '' ) {
01800                         #  Global offset in minutes.
01801                         if ( isset( $wgLocalTZoffset ) ) {
01802                                 $minDiff = $wgLocalTZoffset;
01803                         }
01804                 } elseif ( $data[0] == 'Offset' ) {
01805                         $minDiff = intval( $data[1] );
01806                 } else {
01807                         $data = explode( ':', $tz );
01808                         if ( count( $data ) == 2 ) {
01809                                 $data[0] = intval( $data[0] );
01810                                 $data[1] = intval( $data[1] );
01811                                 $minDiff = abs( $data[0] ) * 60 + $data[1];
01812                                 if ( $data[0] < 0 ) {
01813                                         $minDiff = -$minDiff;
01814                                 }
01815                         } else {
01816                                 $minDiff = intval( $data[0] ) * 60;
01817                         }
01818                 }
01819 
01820                 # No difference ? Return time unchanged
01821                 if ( 0 == $minDiff ) {
01822                         return $ts;
01823                 }
01824 
01825                 wfSuppressWarnings(); // E_STRICT system time bitching
01826                 # Generate an adjusted date; take advantage of the fact that mktime
01827                 # will normalize out-of-range values so we don't have to split $minDiff
01828                 # into hours and minutes.
01829                 $t = mktime( (
01830                   (int)substr( $ts, 8, 2 ) ), # Hours
01831                   (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
01832                   (int)substr( $ts, 12, 2 ), # Seconds
01833                   (int)substr( $ts, 4, 2 ), # Month
01834                   (int)substr( $ts, 6, 2 ), # Day
01835                   (int)substr( $ts, 0, 4 ) ); # Year
01836 
01837                 $date = date( 'YmdHis', $t );
01838                 wfRestoreWarnings();
01839 
01840                 return $date;
01841         }
01842 
01860         function dateFormat( $usePrefs = true ) {
01861                 global $wgUser;
01862 
01863                 if ( is_bool( $usePrefs ) ) {
01864                         if ( $usePrefs ) {
01865                                 $datePreference = $wgUser->getDatePreference();
01866                         } else {
01867                                 $datePreference = (string)User::getDefaultOption( 'date' );
01868                         }
01869                 } else {
01870                         $datePreference = (string)$usePrefs;
01871                 }
01872 
01873                 // return int
01874                 if ( $datePreference == '' ) {
01875                         return 'default';
01876                 }
01877 
01878                 return $datePreference;
01879         }
01880 
01888         function getDateFormatString( $type, $pref ) {
01889                 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
01890                         if ( $pref == 'default' ) {
01891                                 $pref = $this->getDefaultDateFormat();
01892                                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
01893                         } else {
01894                                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
01895                                 if ( is_null( $df ) ) {
01896                                         $pref = $this->getDefaultDateFormat();
01897                                         $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
01898                                 }
01899                         }
01900                         $this->dateFormatStrings[$type][$pref] = $df;
01901                 }
01902                 return $this->dateFormatStrings[$type][$pref];
01903         }
01904 
01915         function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
01916                 $ts = wfTimestamp( TS_MW, $ts );
01917                 if ( $adj ) {
01918                         $ts = $this->userAdjust( $ts, $timecorrection );
01919                 }
01920                 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
01921                 return $this->sprintfDate( $df, $ts );
01922         }
01923 
01934         function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
01935                 $ts = wfTimestamp( TS_MW, $ts );
01936                 if ( $adj ) {
01937                         $ts = $this->userAdjust( $ts, $timecorrection );
01938                 }
01939                 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
01940                 return $this->sprintfDate( $df, $ts );
01941         }
01942 
01954         function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
01955                 $ts = wfTimestamp( TS_MW, $ts );
01956                 if ( $adj ) {
01957                         $ts = $this->userAdjust( $ts, $timecorrection );
01958                 }
01959                 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
01960                 return $this->sprintfDate( $df, $ts );
01961         }
01962 
01973         public function formatDuration( $seconds, array $chosenIntervals = array() ) {
01974                 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
01975 
01976                 $segments = array();
01977 
01978                 foreach ( $intervals as $intervalName => $intervalValue ) {
01979                         $message = new Message( 'duration-' . $intervalName, array( $intervalValue ) );
01980                         $segments[] = $message->inLanguage( $this )->escaped();
01981                 }
01982 
01983                 return $this->listToText( $segments );
01984         }
01985 
01997         public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) {
01998                 if ( empty( $chosenIntervals ) ) {
01999                         $chosenIntervals = array( 'millennia', 'centuries', 'decades', 'years', 'days', 'hours', 'minutes', 'seconds' );
02000                 }
02001 
02002                 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
02003                 $sortedNames = array_keys( $intervals );
02004                 $smallestInterval = array_pop( $sortedNames );
02005 
02006                 $segments = array();
02007 
02008                 foreach ( $intervals as $name => $length ) {
02009                         $value = floor( $seconds / $length );
02010 
02011                         if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
02012                                 $seconds -= $value * $length;
02013                                 $segments[$name] = $value;
02014                         }
02015                 }
02016 
02017                 return $segments;
02018         }
02019 
02039         private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
02040                 $ts = wfTimestamp( TS_MW, $ts );
02041                 $options += array( 'timecorrection' => true, 'format' => true );
02042                 if ( $options['timecorrection'] !== false ) {
02043                         if ( $options['timecorrection'] === true ) {
02044                                 $offset = $user->getOption( 'timecorrection' );
02045                         } else {
02046                                 $offset = $options['timecorrection'];
02047                         }
02048                         $ts = $this->userAdjust( $ts, $offset );
02049                 }
02050                 if ( $options['format'] === true ) {
02051                         $format = $user->getDatePreference();
02052                 } else {
02053                         $format = $options['format'];
02054                 }
02055                 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
02056                 return $this->sprintfDate( $df, $ts );
02057         }
02058 
02078         public function userDate( $ts, User $user, array $options = array() ) {
02079                 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
02080         }
02081 
02101         public function userTime( $ts, User $user, array $options = array() ) {
02102                 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
02103         }
02104 
02124         public function userTimeAndDate( $ts, User $user, array $options = array() ) {
02125                 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
02126         }
02127 
02132         function getMessage( $key ) {
02133                 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
02134         }
02135 
02139         function getAllMessages() {
02140                 return self::$dataCache->getItem( $this->mCode, 'messages' );
02141         }
02142 
02149         function iconv( $in, $out, $string ) {
02150                 # This is a wrapper for iconv in all languages except esperanto,
02151                 # which does some nasty x-conversions beforehand
02152 
02153                 # Even with //IGNORE iconv can whine about illegal characters in
02154                 # *input* string. We just ignore those too.
02155                 # REF: http://bugs.php.net/bug.php?id=37166
02156                 # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885
02157                 wfSuppressWarnings();
02158                 $text = iconv( $in, $out . '//IGNORE', $string );
02159                 wfRestoreWarnings();
02160                 return $text;
02161         }
02162 
02163         // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
02164 
02169         function ucwordbreaksCallbackAscii( $matches ) {
02170                 return $this->ucfirst( $matches[1] );
02171         }
02172 
02177         function ucwordbreaksCallbackMB( $matches ) {
02178                 return mb_strtoupper( $matches[0] );
02179         }
02180 
02185         function ucCallback( $matches ) {
02186                 list( $wikiUpperChars ) = self::getCaseMaps();
02187                 return strtr( $matches[1], $wikiUpperChars );
02188         }
02189 
02194         function lcCallback( $matches ) {
02195                 list( , $wikiLowerChars ) = self::getCaseMaps();
02196                 return strtr( $matches[1], $wikiLowerChars );
02197         }
02198 
02203         function ucwordsCallbackMB( $matches ) {
02204                 return mb_strtoupper( $matches[0] );
02205         }
02206 
02211         function ucwordsCallbackWiki( $matches ) {
02212                 list( $wikiUpperChars ) = self::getCaseMaps();
02213                 return strtr( $matches[0], $wikiUpperChars );
02214         }
02215 
02223         function ucfirst( $str ) {
02224                 $o = ord( $str );
02225                 if ( $o < 96 ) { // if already uppercase...
02226                         return $str;
02227                 } elseif ( $o < 128 ) {
02228                         return ucfirst( $str ); // use PHP's ucfirst()
02229                 } else {
02230                         // fall back to more complex logic in case of multibyte strings
02231                         return $this->uc( $str, true );
02232                 }
02233         }
02234 
02243         function uc( $str, $first = false ) {
02244                 if ( function_exists( 'mb_strtoupper' ) ) {
02245                         if ( $first ) {
02246                                 if ( $this->isMultibyte( $str ) ) {
02247                                         return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
02248                                 } else {
02249                                         return ucfirst( $str );
02250                                 }
02251                         } else {
02252                                 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
02253                         }
02254                 } else {
02255                         if ( $this->isMultibyte( $str ) ) {
02256                                 $x = $first ? '^' : '';
02257                                 return preg_replace_callback(
02258                                         "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
02259                                         array( $this, 'ucCallback' ),
02260                                         $str
02261                                 );
02262                         } else {
02263                                 return $first ? ucfirst( $str ) : strtoupper( $str );
02264                         }
02265                 }
02266         }
02267 
02272         function lcfirst( $str ) {
02273                 $o = ord( $str );
02274                 if ( !$o ) {
02275                         return strval( $str );
02276                 } elseif ( $o >= 128 ) {
02277                         return $this->lc( $str, true );
02278                 } elseif ( $o > 96 ) {
02279                         return $str;
02280                 } else {
02281                         $str[0] = strtolower( $str[0] );
02282                         return $str;
02283                 }
02284         }
02285 
02291         function lc( $str, $first = false ) {
02292                 if ( function_exists( 'mb_strtolower' ) ) {
02293                         if ( $first ) {
02294                                 if ( $this->isMultibyte( $str ) ) {
02295                                         return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
02296                                 } else {
02297                                         return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
02298                                 }
02299                         } else {
02300                                 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
02301                         }
02302                 } else {
02303                         if ( $this->isMultibyte( $str ) ) {
02304                                 $x = $first ? '^' : '';
02305                                 return preg_replace_callback(
02306                                         "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
02307                                         array( $this, 'lcCallback' ),
02308                                         $str
02309                                 );
02310                         } else {
02311                                 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
02312                         }
02313                 }
02314         }
02315 
02320         function isMultibyte( $str ) {
02321                 return (bool)preg_match( '/[\x80-\xff]/', $str );
02322         }
02323 
02328         function ucwords( $str ) {
02329                 if ( $this->isMultibyte( $str ) ) {
02330                         $str = $this->lc( $str );
02331 
02332                         // regexp to find first letter in each word (i.e. after each space)
02333                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
02334 
02335                         // function to use to capitalize a single char
02336                         if ( function_exists( 'mb_strtoupper' ) ) {
02337                                 return preg_replace_callback(
02338                                         $replaceRegexp,
02339                                         array( $this, 'ucwordsCallbackMB' ),
02340                                         $str
02341                                 );
02342                         } else {
02343                                 return preg_replace_callback(
02344                                         $replaceRegexp,
02345                                         array( $this, 'ucwordsCallbackWiki' ),
02346                                         $str
02347                                 );
02348                         }
02349                 } else {
02350                         return ucwords( strtolower( $str ) );
02351                 }
02352         }
02353 
02360         function ucwordbreaks( $str ) {
02361                 if ( $this->isMultibyte( $str ) ) {
02362                         $str = $this->lc( $str );
02363 
02364                         // since \b doesn't work for UTF-8, we explicitely define word break chars
02365                         $breaks = "[ \-\(\)\}\{\.,\?!]";
02366 
02367                         // find first letter after word break
02368                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
02369 
02370                         if ( function_exists( 'mb_strtoupper' ) ) {
02371                                 return preg_replace_callback(
02372                                         $replaceRegexp,
02373                                         array( $this, 'ucwordbreaksCallbackMB' ),
02374                                         $str
02375                                 );
02376                         } else {
02377                                 return preg_replace_callback(
02378                                         $replaceRegexp,
02379                                         array( $this, 'ucwordsCallbackWiki' ),
02380                                         $str
02381                                 );
02382                         }
02383                 } else {
02384                         return preg_replace_callback(
02385                                 '/\b([\w\x80-\xff]+)\b/',
02386                                 array( $this, 'ucwordbreaksCallbackAscii' ),
02387                                 $str
02388                         );
02389                 }
02390         }
02391 
02407         function caseFold( $s ) {
02408                 return $this->uc( $s );
02409         }
02410 
02415         function checkTitleEncoding( $s ) {
02416                 if ( is_array( $s ) ) {
02417                         wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
02418                 }
02419                 # Check for non-UTF-8 URLs
02420                 $ishigh = preg_match( '/[\x80-\xff]/', $s );
02421                 if ( !$ishigh ) {
02422                         return $s;
02423                 }
02424 
02425                 if ( function_exists( 'mb_check_encoding' ) ) {
02426                         $isutf8 = mb_check_encoding( $s, 'UTF-8' );
02427                 } else {
02428                         $isutf8 = preg_match( '/^(?>[\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
02429                                         '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
02430                 }
02431                 if ( $isutf8 ) {
02432                         return $s;
02433                 }
02434 
02435                 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
02436         }
02437 
02441         function fallback8bitEncoding() {
02442                 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
02443         }
02444 
02453         function hasWordBreaks() {
02454                 return true;
02455         }
02456 
02464         function segmentByWord( $string ) {
02465                 return $string;
02466         }
02467 
02475         function normalizeForSearch( $string ) {
02476                 return self::convertDoubleWidth( $string );
02477         }
02478 
02487         protected static function convertDoubleWidth( $string ) {
02488                 static $full = null;
02489                 static $half = null;
02490 
02491                 if ( $full === null ) {
02492                         $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
02493                         $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
02494                         $full = str_split( $fullWidth, 3 );
02495                         $half = str_split( $halfWidth );
02496                 }
02497 
02498                 $string = str_replace( $full, $half, $string );
02499                 return $string;
02500         }
02501 
02507         protected static function insertSpace( $string, $pattern ) {
02508                 $string = preg_replace( $pattern, " $1 ", $string );
02509                 $string = preg_replace( '/ +/', ' ', $string );
02510                 return $string;
02511         }
02512 
02517         function convertForSearchResult( $termsArray ) {
02518                 # some languages, e.g. Chinese, need to do a conversion
02519                 # in order for search results to be displayed correctly
02520                 return $termsArray;
02521         }
02522 
02529         function firstChar( $s ) {
02530                 $matches = array();
02531                 preg_match(
02532                         '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
02533                                 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
02534                         $s,
02535                         $matches
02536                 );
02537 
02538                 if ( isset( $matches[1] ) ) {
02539                         if ( strlen( $matches[1] ) != 3 ) {
02540                                 return $matches[1];
02541                         }
02542 
02543                         // Break down Hangul syllables to grab the first jamo
02544                         $code = utf8ToCodepoint( $matches[1] );
02545                         if ( $code < 0xac00 || 0xd7a4 <= $code ) {
02546                                 return $matches[1];
02547                         } elseif ( $code < 0xb098 ) {
02548                                 return "\xe3\x84\xb1";
02549                         } elseif ( $code < 0xb2e4 ) {
02550                                 return "\xe3\x84\xb4";
02551                         } elseif ( $code < 0xb77c ) {
02552                                 return "\xe3\x84\xb7";
02553                         } elseif ( $code < 0xb9c8 ) {
02554                                 return "\xe3\x84\xb9";
02555                         } elseif ( $code < 0xbc14 ) {
02556                                 return "\xe3\x85\x81";
02557                         } elseif ( $code < 0xc0ac ) {
02558                                 return "\xe3\x85\x82";
02559                         } elseif ( $code < 0xc544 ) {
02560                                 return "\xe3\x85\x85";
02561                         } elseif ( $code < 0xc790 ) {
02562                                 return "\xe3\x85\x87";
02563                         } elseif ( $code < 0xcc28 ) {
02564                                 return "\xe3\x85\x88";
02565                         } elseif ( $code < 0xce74 ) {
02566                                 return "\xe3\x85\x8a";
02567                         } elseif ( $code < 0xd0c0 ) {
02568                                 return "\xe3\x85\x8b";
02569                         } elseif ( $code < 0xd30c ) {
02570                                 return "\xe3\x85\x8c";
02571                         } elseif ( $code < 0xd558 ) {
02572                                 return "\xe3\x85\x8d";
02573                         } else {
02574                                 return "\xe3\x85\x8e";
02575                         }
02576                 } else {
02577                         return '';
02578                 }
02579         }
02580 
02581         function initEncoding() {
02582                 # Some languages may have an alternate char encoding option
02583                 # (Esperanto X-coding, Japanese furigana conversion, etc)
02584                 # If this language is used as the primary content language,
02585                 # an override to the defaults can be set here on startup.
02586         }
02587 
02592         function recodeForEdit( $s ) {
02593                 # For some languages we'll want to explicitly specify
02594                 # which characters make it into the edit box raw
02595                 # or are converted in some way or another.
02596                 global $wgEditEncoding;
02597                 if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) {
02598                         return $s;
02599                 } else {
02600                         return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
02601                 }
02602         }
02603 
02608         function recodeInput( $s ) {
02609                 # Take the previous into account.
02610                 global $wgEditEncoding;
02611                 if ( $wgEditEncoding != '' ) {
02612                         $enc = $wgEditEncoding;
02613                 } else {
02614                         $enc = 'UTF-8';
02615                 }
02616                 if ( $enc == 'UTF-8' ) {
02617                         return $s;
02618                 } else {
02619                         return $this->iconv( $enc, 'UTF-8', $s );
02620                 }
02621         }
02622 
02634         function normalize( $s ) {
02635                 global $wgAllUnicodeFixes;
02636                 $s = UtfNormal::cleanUp( $s );
02637                 if ( $wgAllUnicodeFixes ) {
02638                         $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
02639                         $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
02640                 }
02641 
02642                 return $s;
02643         }
02644 
02659         function transformUsingPairFile( $file, $string ) {
02660                 if ( !isset( $this->transformData[$file] ) ) {
02661                         $data = wfGetPrecompiledData( $file );
02662                         if ( $data === false ) {
02663                                 throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
02664                         }
02665                         $this->transformData[$file] = new ReplacementArray( $data );
02666                 }
02667                 return $this->transformData[$file]->replace( $string );
02668         }
02669 
02675         function isRTL() {
02676                 return self::$dataCache->getItem( $this->mCode, 'rtl' );
02677         }
02678 
02683         function getDir() {
02684                 return $this->isRTL() ? 'rtl' : 'ltr';
02685         }
02686 
02695         function alignStart() {
02696                 return $this->isRTL() ? 'right' : 'left';
02697         }
02698 
02707         function alignEnd() {
02708                 return $this->isRTL() ? 'left' : 'right';
02709         }
02710 
02722         function getDirMarkEntity( $opposite = false ) {
02723                 if ( $opposite ) { return $this->isRTL() ? '&lrm;' : '&rlm;'; }
02724                 return $this->isRTL() ? '&rlm;' : '&lrm;';
02725         }
02726 
02737         function getDirMark( $opposite = false ) {
02738                 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
02739                 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
02740                 if ( $opposite ) { return $this->isRTL() ? $lrm : $rlm; }
02741                 return $this->isRTL() ? $rlm : $lrm;
02742         }
02743 
02747         function capitalizeAllNouns() {
02748                 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
02749         }
02750 
02757         function getArrow( $direction = 'forwards' ) {
02758                 switch ( $direction ) {
02759                 case 'forwards':
02760                         return $this->isRTL() ? '←' : '→';
02761                 case 'backwards':
02762                         return $this->isRTL() ? '→' : '←';
02763                 case 'left':
02764                         return '←';
02765                 case 'right':
02766                         return '→';
02767                 case 'up':
02768                         return '↑';
02769                 case 'down':
02770                         return '↓';
02771                 }
02772         }
02773 
02779         function linkPrefixExtension() {
02780                 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
02781         }
02782 
02786         function getMagicWords() {
02787                 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
02788         }
02789 
02790         protected function doMagicHook() {
02791                 if ( $this->mMagicHookDone ) {
02792                         return;
02793                 }
02794                 $this->mMagicHookDone = true;
02795                 wfProfileIn( 'LanguageGetMagic' );
02796                 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
02797                 wfProfileOut( 'LanguageGetMagic' );
02798         }
02799 
02805         function getMagic( $mw ) {
02806                 $this->doMagicHook();
02807 
02808                 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
02809                         $rawEntry = $this->mMagicExtensions[$mw->mId];
02810                 } else {
02811                         $magicWords = $this->getMagicWords();
02812                         if ( isset( $magicWords[$mw->mId] ) ) {
02813                                 $rawEntry = $magicWords[$mw->mId];
02814                         } else {
02815                                 $rawEntry = false;
02816                         }
02817                 }
02818 
02819                 if ( !is_array( $rawEntry ) ) {
02820                         error_log( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
02821                 } else {
02822                         $mw->mCaseSensitive = $rawEntry[0];
02823                         $mw->mSynonyms = array_slice( $rawEntry, 1 );
02824                 }
02825         }
02826 
02832         function addMagicWordsByLang( $newWords ) {
02833                 $fallbackChain = $this->getFallbackLanguages();
02834                 $fallbackChain = array_reverse( $fallbackChain );
02835                 foreach ( $fallbackChain as $code ) {
02836                         if ( isset( $newWords[$code] ) ) {
02837                                 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
02838                         }
02839                 }
02840         }
02841 
02846         function getSpecialPageAliases() {
02847                 // Cache aliases because it may be slow to load them
02848                 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
02849                         // Initialise array
02850                         $this->mExtendedSpecialPageAliases =
02851                                 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
02852                         wfRunHooks( 'LanguageGetSpecialPageAliases',
02853                                 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
02854                 }
02855 
02856                 return $this->mExtendedSpecialPageAliases;
02857         }
02858 
02865         function emphasize( $text ) {
02866                 return "<em>$text</em>";
02867         }
02868 
02893         public function formatNum( $number, $nocommafy = false ) {
02894                 global $wgTranslateNumerals;
02895                 if ( !$nocommafy ) {
02896                         $number = $this->commafy( $number );
02897                         $s = $this->separatorTransformTable();
02898                         if ( $s ) {
02899                                 $number = strtr( $number, $s );
02900                         }
02901                 }
02902 
02903                 if ( $wgTranslateNumerals ) {
02904                         $s = $this->digitTransformTable();
02905                         if ( $s ) {
02906                                 $number = strtr( $number, $s );
02907                         }
02908                 }
02909 
02910                 return $number;
02911         }
02912 
02917         function parseFormattedNumber( $number ) {
02918                 $s = $this->digitTransformTable();
02919                 if ( $s ) {
02920                         $number = strtr( $number, array_flip( $s ) );
02921                 }
02922 
02923                 $s = $this->separatorTransformTable();
02924                 if ( $s ) {
02925                         $number = strtr( $number, array_flip( $s ) );
02926                 }
02927 
02928                 $number = strtr( $number, array( ',' => '' ) );
02929                 return $number;
02930         }
02931 
02938         function commafy( $_ ) {
02939                 $digitGroupingPattern = $this->digitGroupingPattern();
02940                 if ( $_ === null ) {
02941                         return '';
02942                 }
02943 
02944                 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
02945                         // default grouping is at thousands,  use the same for ###,###,### pattern too.
02946                         return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $_ ) ) );
02947                 } else {
02948                         // Ref: http://cldr.unicode.org/translation/number-patterns
02949                         $sign = "";
02950                         if ( intval( $_ ) < 0 ) {
02951                                 // For negative numbers apply the algorithm like positive number and add sign.
02952                                 $sign =  "-";
02953                                 $_ = substr( $_, 1 );
02954                         }
02955                         $numberpart = array();
02956                         $decimalpart = array();
02957                         $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
02958                         preg_match( "/\d+/", $_, $numberpart );
02959                         preg_match( "/\.\d*/", $_, $decimalpart );
02960                         $groupedNumber = ( count( $decimalpart ) > 0 ) ? $decimalpart[0]:"";
02961                         if ( $groupedNumber  === $_ ) {
02962                                 // the string does not have any number part. Eg: .12345
02963                                 return $sign . $groupedNumber;
02964                         }
02965                         $start = $end = strlen( $numberpart[0] );
02966                         while ( $start > 0 ) {
02967                                 $match = $matches[0][$numMatches -1] ;
02968                                 $matchLen = strlen( $match );
02969                                 $start = $end - $matchLen;
02970                                 if ( $start < 0 ) {
02971                                         $start = 0;
02972                                 }
02973                                 $groupedNumber = substr( $_ , $start, $end -$start ) . $groupedNumber ;
02974                                 $end = $start;
02975                                 if ( $numMatches > 1 ) {
02976                                         // use the last pattern for the rest of the number
02977                                         $numMatches--;
02978                                 }
02979                                 if ( $start > 0 ) {
02980                                         $groupedNumber = "," . $groupedNumber;
02981                                 }
02982                         }
02983                         return $sign . $groupedNumber;
02984                 }
02985         }
02989         function digitGroupingPattern() {
02990                 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
02991         }
02992 
02996         function digitTransformTable() {
02997                 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
02998         }
02999 
03003         function separatorTransformTable() {
03004                 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
03005         }
03006 
03016         function listToText( array $l ) {
03017                 $s = '';
03018                 $m = count( $l ) - 1;
03019 
03020                 if ( $m === 0 ) {
03021                         return $l[0];
03022                 } elseif ( $m === 1 ) {
03023                         return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
03024                 } else {
03025                         for ( $i = $m; $i >= 0; $i-- ) {
03026                                 if ( $i == $m ) {
03027                                         $s = $l[$i];
03028                                 } elseif ( $i == $m - 1 ) {
03029                                         $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
03030                                 } else {
03031                                         $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
03032                                 }
03033                         }
03034                         return $s;
03035                 }
03036         }
03037 
03044         function commaList( array $list ) {
03045                 return implode(
03046                         wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
03047                         $list
03048                 );
03049         }
03050 
03057         function semicolonList( array $list ) {
03058                 return implode(
03059                         wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
03060                         $list
03061                 );
03062         }
03063 
03069         function pipeList( array $list ) {
03070                 return implode(
03071                         wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
03072                         $list
03073                 );
03074         }
03075 
03093         function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
03094                 # Use the localized ellipsis character
03095                 if ( $ellipsis == '...' ) {
03096                         $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
03097                 }
03098                 # Check if there is no need to truncate
03099                 if ( $length == 0 ) {
03100                         return $ellipsis; // convention
03101                 } elseif ( strlen( $string ) <= abs( $length ) ) {
03102                         return $string; // no need to truncate
03103                 }
03104                 $stringOriginal = $string;
03105                 # If ellipsis length is >= $length then we can't apply $adjustLength
03106                 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
03107                         $string = $ellipsis; // this can be slightly unexpected
03108                 # Otherwise, truncate and add ellipsis...
03109                 } else {
03110                         $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
03111                         if ( $length > 0 ) {
03112                                 $length -= $eLength;
03113                                 $string = substr( $string, 0, $length ); // xyz...
03114                                 $string = $this->removeBadCharLast( $string );
03115                                 $string = $string . $ellipsis;
03116                         } else {
03117                                 $length += $eLength;
03118                                 $string = substr( $string, $length ); // ...xyz
03119                                 $string = $this->removeBadCharFirst( $string );
03120                                 $string = $ellipsis . $string;
03121                         }
03122                 }
03123                 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
03124                 # This check is *not* redundant if $adjustLength, due to the single case where
03125                 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
03126                 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
03127                         return $string;
03128                 } else {
03129                         return $stringOriginal;
03130                 }
03131         }
03132 
03140         protected function removeBadCharLast( $string ) {
03141                 if ( $string != '' ) {
03142                         $char = ord( $string[strlen( $string ) - 1] );
03143                         $m = array();
03144                         if ( $char >= 0xc0 ) {
03145                                 # We got the first byte only of a multibyte char; remove it.
03146                                 $string = substr( $string, 0, -1 );
03147                         } elseif ( $char >= 0x80 &&
03148                                   preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
03149                                                           '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) )
03150                         {
03151                                 # We chopped in the middle of a character; remove it
03152                                 $string = $m[1];
03153                         }
03154                 }
03155                 return $string;
03156         }
03157 
03165         protected function removeBadCharFirst( $string ) {
03166                 if ( $string != '' ) {
03167                         $char = ord( $string[0] );
03168                         if ( $char >= 0x80 && $char < 0xc0 ) {
03169                                 # We chopped in the middle of a character; remove the whole thing
03170                                 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
03171                         }
03172                 }
03173                 return $string;
03174         }
03175 
03191         function truncateHtml( $text, $length, $ellipsis = '...' ) {
03192                 # Use the localized ellipsis character
03193                 if ( $ellipsis == '...' ) {
03194                         $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
03195                 }
03196                 # Check if there is clearly no need to truncate
03197                 if ( $length <= 0 ) {
03198                         return $ellipsis; // no text shown, nothing to format (convention)
03199                 } elseif ( strlen( $text ) <= $length ) {
03200                         return $text; // string short enough even *with* HTML (short-circuit)
03201                 }
03202 
03203                 $dispLen = 0; // innerHTML legth so far
03204                 $testingEllipsis = false; // checking if ellipses will make string longer/equal?
03205                 $tagType = 0; // 0-open, 1-close
03206                 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
03207                 $entityState = 0; // 0-not entity, 1-entity
03208                 $tag = $ret = ''; // accumulated tag name, accumulated result string
03209                 $openTags = array(); // open tag stack
03210                 $maybeState = null; // possible truncation state
03211 
03212                 $textLen = strlen( $text );
03213                 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
03214                 for ( $pos = 0; true; ++$pos ) {
03215                         # Consider truncation once the display length has reached the maximim.
03216                         # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
03217                         # Check that we're not in the middle of a bracket/entity...
03218                         if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
03219                                 if ( !$testingEllipsis ) {
03220                                         $testingEllipsis = true;
03221                                         # Save where we are; we will truncate here unless there turn out to
03222                                         # be so few remaining characters that truncation is not necessary.
03223                                         if ( !$maybeState ) { // already saved? ($neLength = 0 case)
03224                                                 $maybeState = array( $ret, $openTags ); // save state
03225                                         }
03226                                 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
03227                                         # String in fact does need truncation, the truncation point was OK.
03228                                         list( $ret, $openTags ) = $maybeState; // reload state
03229                                         $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
03230                                         $ret .= $ellipsis; // add ellipsis
03231                                         break;
03232                                 }
03233                         }
03234                         if ( $pos >= $textLen ) break; // extra iteration just for above checks
03235 
03236                         # Read the next char...
03237                         $ch = $text[$pos];
03238                         $lastCh = $pos ? $text[$pos - 1] : '';
03239                         $ret .= $ch; // add to result string
03240                         if ( $ch == '<' ) {
03241                                 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
03242                                 $entityState = 0; // for bad HTML
03243                                 $bracketState = 1; // tag started (checking for backslash)
03244                         } elseif ( $ch == '>' ) {
03245                                 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
03246                                 $entityState = 0; // for bad HTML
03247                                 $bracketState = 0; // out of brackets
03248                         } elseif ( $bracketState == 1 ) {
03249                                 if ( $ch == '/' ) {
03250                                         $tagType = 1; // close tag (e.g. "</span>")
03251                                 } else {
03252                                         $tagType = 0; // open tag (e.g. "<span>")
03253                                         $tag .= $ch;
03254                                 }
03255                                 $bracketState = 2; // building tag name
03256                         } elseif ( $bracketState == 2 ) {
03257                                 if ( $ch != ' ' ) {
03258                                         $tag .= $ch;
03259                                 } else {
03260                                         // Name found (e.g. "<a href=..."), add on tag attributes...
03261                                         $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
03262                                 }
03263                         } elseif ( $bracketState == 0 ) {
03264                                 if ( $entityState ) {
03265                                         if ( $ch == ';' ) {
03266                                                 $entityState = 0;
03267                                                 $dispLen++; // entity is one displayed char
03268                                         }
03269                                 } else {
03270                                         if ( $neLength == 0 && !$maybeState ) {
03271                                                 // Save state without $ch. We want to *hit* the first
03272                                                 // display char (to get tags) but not *use* it if truncating.
03273                                                 $maybeState = array( substr( $ret, 0, -1 ), $openTags );
03274                                         }
03275                                         if ( $ch == '&' ) {
03276                                                 $entityState = 1; // entity found, (e.g. "&#160;")
03277                                         } else {
03278                                                 $dispLen++; // this char is displayed
03279                                                 // Add the next $max display text chars after this in one swoop...
03280                                                 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
03281                                                 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
03282                                                 $dispLen += $skipped;
03283                                                 $pos += $skipped;
03284                                         }
03285                                 }
03286                         }
03287                 }
03288                 // Close the last tag if left unclosed by bad HTML
03289                 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
03290                 while ( count( $openTags ) > 0 ) {
03291                         $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
03292                 }
03293                 return $ret;
03294         }
03295 
03307         private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
03308                 if ( $len === null ) {
03309                         $len = -1; // -1 means "no limit" for strcspn
03310                 } elseif ( $len < 0 ) {
03311                         $len = 0; // sanity
03312                 }
03313                 $skipCount = 0;
03314                 if ( $start < strlen( $text ) ) {
03315                         $skipCount = strcspn( $text, $search, $start, $len );
03316                         $ret .= substr( $text, $start, $skipCount );
03317                 }
03318                 return $skipCount;
03319         }
03320 
03330         private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
03331                 $tag = ltrim( $tag );
03332                 if ( $tag != '' ) {
03333                         if ( $tagType == 0 && $lastCh != '/' ) {
03334                                 $openTags[] = $tag; // tag opened (didn't close itself)
03335                         } elseif ( $tagType == 1 ) {
03336                                 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
03337                                         array_pop( $openTags ); // tag closed
03338                                 }
03339                         }
03340                         $tag = '';
03341                 }
03342         }
03343 
03352         function convertGrammar( $word, $case ) {
03353                 global $wgGrammarForms;
03354                 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
03355                         return $wgGrammarForms[$this->getCode()][$case][$word];
03356                 }
03357                 return $word;
03358         }
03364         function getGrammarForms() {
03365                 global $wgGrammarForms;
03366                 if ( isset( $wgGrammarForms[$this->getCode()] ) && is_array( $wgGrammarForms[$this->getCode()] ) ) {
03367                          return $wgGrammarForms[$this->getCode()];
03368                 }
03369                 return array();
03370         }
03390         function gender( $gender, $forms ) {
03391                 if ( !count( $forms ) ) {
03392                         return '';
03393                 }
03394                 $forms = $this->preConvertPlural( $forms, 2 );
03395                 if ( $gender === 'male' ) {
03396                         return $forms[0];
03397                 }
03398                 if ( $gender === 'female' ) {
03399                         return $forms[1];
03400                 }
03401                 return isset( $forms[2] ) ? $forms[2] : $forms[0];
03402         }
03403 
03419         function convertPlural( $count, $forms ) {
03420                 if ( !count( $forms ) ) {
03421                         return '';
03422                 }
03423 
03424                 // Handle explicit 0= and 1= forms
03425                 foreach ( $forms as $index => $form ) {
03426                         if ( isset( $form[1] ) && $form[1] === '=' ) {
03427                                 if ( $form[0] === (string) $count ) {
03428                                         return substr( $form, 2 );
03429                                 }
03430                                 unset( $forms[$index] );
03431                         }
03432                 }
03433                 $forms = array_values( $forms );
03434 
03435                 $pluralForm = $this->getPluralForm( $count );
03436                 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
03437                 return $forms[$pluralForm];
03438         }
03439 
03448         protected function preConvertPlural( /* Array */ $forms, $count ) {
03449                 while ( count( $forms ) < $count ) {
03450                         $forms[] = $forms[count( $forms ) - 1];
03451                 }
03452                 return $forms;
03453         }
03454 
03466         function translateBlockExpiry( $str ) {
03467                 $duration = SpecialBlock::getSuggestedDurations( $this );
03468                 foreach ( $duration as $show => $value ) {
03469                         if ( strcmp( $str, $value ) == 0 ) {
03470                                 return htmlspecialchars( trim( $show ) );
03471                         }
03472                 }
03473 
03474                 // Since usually only infinite or indefinite is only on list, so try
03475                 // equivalents if still here.
03476                 $indefs = array( 'infinite', 'infinity', 'indefinite' );
03477                 if ( in_array( $str, $indefs ) ) {
03478                         foreach ( $indefs as $val ) {
03479                                 $show = array_search( $val, $duration, true );
03480                                 if ( $show !== false ) {
03481                                         return htmlspecialchars( trim( $show ) );
03482                                 }
03483                         }
03484                 }
03485                 // If all else fails, return the original string.
03486                 return $str;
03487         }
03488 
03496         public function segmentForDiff( $text ) {
03497                 return $text;
03498         }
03499 
03506         public function unsegmentForDiff( $text ) {
03507                 return $text;
03508         }
03509 
03516         public function getConverter() {
03517                 return $this->mConverter;
03518         }
03519 
03526         public function autoConvertToAllVariants( $text ) {
03527                 return $this->mConverter->autoConvertToAllVariants( $text );
03528         }
03529 
03536         public function convert( $text ) {
03537                 return $this->mConverter->convert( $text );
03538         }
03539 
03546         public function convertTitle( $title ) {
03547                 return $this->mConverter->convertTitle( $title );
03548         }
03549 
03556         public function convertNamespace( $ns ) {
03557                 return $this->mConverter->convertNamespace( $ns );
03558         }
03559 
03565         public function hasVariants() {
03566                 return sizeof( $this->getVariants() ) > 1;
03567         }
03568 
03576         public function hasVariant( $variant ) {
03577                 return (bool)$this->mConverter->validateVariant( $variant );
03578         }
03579 
03586         public function armourMath( $text ) {
03587                 return $this->mConverter->armourMath( $text );
03588         }
03589 
03597         public function convertHtml( $text, $isTitle = false ) {
03598                 return htmlspecialchars( $this->convert( $text, $isTitle ) );
03599         }
03600 
03605         public function convertCategoryKey( $key ) {
03606                 return $this->mConverter->convertCategoryKey( $key );
03607         }
03608 
03615         public function getVariants() {
03616                 return $this->mConverter->getVariants();
03617         }
03618 
03622         public function getPreferredVariant() {
03623                 return $this->mConverter->getPreferredVariant();
03624         }
03625 
03629         public function getDefaultVariant() {
03630                 return $this->mConverter->getDefaultVariant();
03631         }
03632 
03636         public function getURLVariant() {
03637                 return $this->mConverter->getURLVariant();
03638         }
03639 
03652         public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
03653                 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
03654         }
03655 
03667         public function convertLinkToAllVariants( $text ) {
03668                 return $this->mConverter->convertLinkToAllVariants( $text );
03669         }
03670 
03677         function getExtraHashOptions() {
03678                 return $this->mConverter->getExtraHashOptions();
03679         }
03680 
03688         public function getParsedTitle() {
03689                 return $this->mConverter->getParsedTitle();
03690         }
03691 
03700         public function markNoConversion( $text, $noParse = false ) {
03701                 return $this->mConverter->markNoConversion( $text, $noParse );
03702         }
03703 
03710         public function linkTrail() {
03711                 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
03712         }
03713 
03717         function getLangObj() {
03718                 return $this;
03719         }
03720 
03729         public function getCode() {
03730                 return $this->mCode;
03731         }
03732 
03743         public function getHtmlCode() {
03744                 if ( is_null( $this->mHtmlCode ) ) {
03745                         $this->mHtmlCode = wfBCP47( $this->getCode() );
03746                 }
03747                 return $this->mHtmlCode;
03748         }
03749 
03753         public function setCode( $code ) {
03754                 $this->mCode = $code;
03755                 // Ensure we don't leave an incorrect html code lying around
03756                 $this->mHtmlCode = null;
03757         }
03758 
03767         public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
03768                 // Protect against path traversal
03769                 if ( !Language::isValidCode( $code )
03770                         || strcspn( $code, ":/\\\000" ) !== strlen( $code ) )
03771                 {
03772                         throw new MWException( "Invalid language code \"$code\"" );
03773                 }
03774 
03775                 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
03776         }
03777 
03785         public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
03786                 $m = null;
03787                 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
03788                         preg_quote( $suffix, '/' ) . '/', $filename, $m );
03789                 if ( !count( $m ) ) {
03790                         return false;
03791                 }
03792                 return str_replace( '_', '-', strtolower( $m[1] ) );
03793         }
03794 
03799         public static function getMessagesFileName( $code ) {
03800                 global $IP;
03801                 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
03802                 wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) );
03803                 return $file;
03804         }
03805 
03810         public static function getClassFileName( $code ) {
03811                 global $IP;
03812                 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
03813         }
03814 
03822         public static function getFallbackFor( $code ) {
03823                 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
03824                         return false;
03825                 } else {
03826                         $fallbacks = self::getFallbacksFor( $code );
03827                         $first = array_shift( $fallbacks );
03828                         return $first;
03829                 }
03830         }
03831 
03839         public static function getFallbacksFor( $code ) {
03840                 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
03841                         return array();
03842                 } else {
03843                         $v = self::getLocalisationCache()->getItem( $code, 'fallback' );
03844                         $v = array_map( 'trim', explode( ',', $v ) );
03845                         if ( $v[count( $v ) - 1] !== 'en' ) {
03846                                 $v[] = 'en';
03847                         }
03848                         return $v;
03849                 }
03850         }
03851 
03861         public static function getMessagesFor( $code ) {
03862                 return self::getLocalisationCache()->getItem( $code, 'messages' );
03863         }
03864 
03873         public static function getMessageFor( $key, $code ) {
03874                 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
03875         }
03876 
03885         public static function getMessageKeysFor( $code ) {
03886                 return self::getLocalisationCache()->getSubItemList( $code, 'messages' );
03887         }
03888 
03893         function fixVariableInNamespace( $talk ) {
03894                 if ( strpos( $talk, '$1' ) === false ) {
03895                         return $talk;
03896                 }
03897 
03898                 global $wgMetaNamespace;
03899                 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
03900 
03901                 # Allow grammar transformations
03902                 # Allowing full message-style parsing would make simple requests
03903                 # such as action=raw much more expensive than they need to be.
03904                 # This will hopefully cover most cases.
03905                 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
03906                         array( &$this, 'replaceGrammarInNamespace' ), $talk );
03907                 return str_replace( ' ', '_', $talk );
03908         }
03909 
03914         function replaceGrammarInNamespace( $m ) {
03915                 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
03916         }
03917 
03922         static function getCaseMaps() {
03923                 static $wikiUpperChars, $wikiLowerChars;
03924                 if ( isset( $wikiUpperChars ) ) {
03925                         return array( $wikiUpperChars, $wikiLowerChars );
03926                 }
03927 
03928                 wfProfileIn( __METHOD__ );
03929                 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
03930                 if ( $arr === false ) {
03931                         throw new MWException(
03932                                 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
03933                 }
03934                 $wikiUpperChars = $arr['wikiUpperChars'];
03935                 $wikiLowerChars = $arr['wikiLowerChars'];
03936                 wfProfileOut( __METHOD__ );
03937                 return array( $wikiUpperChars, $wikiLowerChars );
03938         }
03939 
03951         public function formatExpiry( $expiry, $format = true ) {
03952                 static $infinity, $infinityMsg;
03953                 if ( $infinity === null ) {
03954                         $infinityMsg = wfMessage( 'infiniteblock' );
03955                         $infinity = wfGetDB( DB_SLAVE )->getInfinity();
03956                 }
03957 
03958                 if ( $expiry == '' || $expiry == $infinity ) {
03959                         return $format === true
03960                                 ? $infinityMsg
03961                                 : $infinity;
03962                 } else {
03963                         return $format === true
03964                                 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
03965                                 : wfTimestamp( $format, $expiry );
03966                 }
03967         }
03968 
03979         function formatTimePeriod( $seconds, $format = array() ) {
03980                 if ( !is_array( $format ) ) {
03981                         $format = array( 'avoid' => $format ); // For backwards compatibility
03982                 }
03983                 if ( !isset( $format['avoid'] ) ) {
03984                         $format['avoid'] = false;
03985                 }
03986                 if ( !isset( $format['noabbrevs' ] ) ) {
03987                         $format['noabbrevs'] = false;
03988                 }
03989                 $secondsMsg = wfMessage(
03990                         $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
03991                 $minutesMsg = wfMessage(
03992                         $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
03993                 $hoursMsg = wfMessage(
03994                         $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
03995                 $daysMsg = wfMessage(
03996                         $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
03997 
03998                 if ( round( $seconds * 10 ) < 100 ) {
03999                         $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
04000                         $s = $secondsMsg->params( $s )->text();
04001                 } elseif ( round( $seconds ) < 60 ) {
04002                         $s = $this->formatNum( round( $seconds ) );
04003                         $s = $secondsMsg->params( $s )->text();
04004                 } elseif ( round( $seconds ) < 3600 ) {
04005                         $minutes = floor( $seconds / 60 );
04006                         $secondsPart = round( fmod( $seconds, 60 ) );
04007                         if ( $secondsPart == 60 ) {
04008                                 $secondsPart = 0;
04009                                 $minutes++;
04010                         }
04011                         $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04012                         $s .= ' ';
04013                         $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
04014                 } elseif ( round( $seconds ) <= 2 * 86400 ) {
04015                         $hours = floor( $seconds / 3600 );
04016                         $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
04017                         $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
04018                         if ( $secondsPart == 60 ) {
04019                                 $secondsPart = 0;
04020                                 $minutes++;
04021                         }
04022                         if ( $minutes == 60 ) {
04023                                 $minutes = 0;
04024                                 $hours++;
04025                         }
04026                         $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
04027                         $s .= ' ';
04028                         $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04029                         if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) {
04030                                 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
04031                         }
04032                 } else {
04033                         $days = floor( $seconds / 86400 );
04034                         if ( $format['avoid'] === 'avoidminutes' ) {
04035                                 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
04036                                 if ( $hours == 24 ) {
04037                                         $hours = 0;
04038                                         $days++;
04039                                 }
04040                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04041                                 $s .= ' ';
04042                                 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
04043                         } elseif ( $format['avoid'] === 'avoidseconds' ) {
04044                                 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
04045                                 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
04046                                 if ( $minutes == 60 ) {
04047                                         $minutes = 0;
04048                                         $hours++;
04049                                 }
04050                                 if ( $hours == 24 ) {
04051                                         $hours = 0;
04052                                         $days++;
04053                                 }
04054                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04055                                 $s .= ' ';
04056                                 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
04057                                 $s .= ' ';
04058                                 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04059                         } else {
04060                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04061                                 $s .= ' ';
04062                                 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
04063                         }
04064                 }
04065                 return $s;
04066         }
04067 
04078         function formatBitrate( $bps ) {
04079                 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
04080         }
04081 
04088         function formatComputingNumbers( $size, $boundary, $messageKey ) {
04089                 if ( $size <= 0 ) {
04090                         return str_replace( '$1', $this->formatNum( $size ),
04091                                 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
04092                         );
04093                 }
04094                 $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' );
04095                 $index = 0;
04096 
04097                 $maxIndex = count( $sizes ) - 1;
04098                 while ( $size >= $boundary && $index < $maxIndex ) {
04099                         $index++;
04100                         $size /= $boundary;
04101                 }
04102 
04103                 // For small sizes no decimal places necessary
04104                 $round = 0;
04105                 if ( $index > 1 ) {
04106                         // For MB and bigger two decimal places are smarter
04107                         $round = 2;
04108                 }
04109                 $msg = str_replace( '$1', $sizes[$index], $messageKey );
04110 
04111                 $size = round( $size, $round );
04112                 $text = $this->getMessageFromDB( $msg );
04113                 return str_replace( '$1', $this->formatNum( $size ), $text );
04114         }
04115 
04126         function formatSize( $size ) {
04127                 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
04128         }
04129 
04139         function specialList( $page, $details, $oppositedm = true ) {
04140                 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) .
04141                         $this->getDirMark();
04142                 $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) .
04143                         wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : '';
04144                 return $page . $details;
04145         }
04146 
04157         public function viewPrevNext( Title $title, $offset, $limit, array $query = array(), $atend = false ) {
04158                 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
04159 
04160                 # Make 'previous' link
04161                 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04162                 if ( $offset > 0 ) {
04163                         $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
04164                                 $query, $prev, 'prevn-title', 'mw-prevlink' );
04165                 } else {
04166                         $plink = htmlspecialchars( $prev );
04167                 }
04168 
04169                 # Make 'next' link
04170                 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04171                 if ( $atend ) {
04172                         $nlink = htmlspecialchars( $next );
04173                 } else {
04174                         $nlink = $this->numLink( $title, $offset + $limit, $limit,
04175                                 $query, $next, 'prevn-title', 'mw-nextlink' );
04176                 }
04177 
04178                 # Make links to set number of items per page
04179                 $numLinks = array();
04180                 foreach ( array( 20, 50, 100, 250, 500 ) as $num ) {
04181                         $numLinks[] = $this->numLink( $title, $offset, $num,
04182                                 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
04183                 }
04184 
04185                 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
04186                         )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
04187         }
04188 
04201         private function numLink( Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class ) {
04202                 $query = array( 'limit' => $limit, 'offset' => $offset ) + $query;
04203                 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04204                 return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ),
04205                         'title' => $tooltip, 'class' => $class ), $link );
04206         }
04207 
04213         public function getConvRuleTitle() {
04214                 return $this->mConverter->getConvRuleTitle();
04215         }
04216 
04222         public function getCompiledPluralRules() {
04223                 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
04224                 $fallbacks = Language::getFallbacksFor( $this->mCode );
04225                 if ( !$pluralRules ) {
04226                         foreach ( $fallbacks as $fallbackCode ) {
04227                                 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
04228                                 if ( $pluralRules ) {
04229                                         break;
04230                                 }
04231                         }
04232                 }
04233                 return $pluralRules;
04234         }
04235 
04241         public function getPluralRules() {
04242                 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
04243                 $fallbacks = Language::getFallbacksFor( $this->mCode );
04244                 if ( !$pluralRules ) {
04245                         foreach ( $fallbacks as $fallbackCode ) {
04246                                 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
04247                                 if ( $pluralRules ) {
04248                                         break;
04249                                 }
04250                         }
04251                 }
04252                 return $pluralRules;
04253         }
04254 
04260         private function getPluralForm( $number ) {
04261                 $pluralRules = $this->getCompiledPluralRules();
04262                 $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules );
04263                 return $form;
04264         }
04265 
04266 }