MediaWiki  REL1_20
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         var $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 getVariants() { return array( $this->mLang->getCode() ); }
00058         function getPreferredVariant() { return $this->mLang->getCode(); }
00059         function getDefaultVariant() { return $this->mLang->getCode(); }
00060         function getURLVariant() { return ''; }
00061         function getConvRuleTitle() { return false; }
00062         function findVariantLink( &$l, &$n, $ignoreOtherCond = false ) { }
00063         function getExtraHashOptions() { return ''; }
00064         function getParsedTitle() { return ''; }
00065         function markNoConversion( $text, $noParse = false ) { return $text; }
00066         function convertCategoryKey( $key ) { return $key; }
00067         function convertLinkToAllVariants( $text ) { return $this->autoConvertToAllVariants( $text ); }
00068         function armourMath( $text ) { return $text; }
00069 }
00070 
00075 class Language {
00076 
00080         var $mConverter;
00081 
00082         var $mVariants, $mCode, $mLoaded = false;
00083         var $mMagicExtensions = array(), $mMagicHookDone = false;
00084         private $mHtmlCode = null;
00085 
00086         var $dateFormatStrings = array();
00087         var $mExtendedSpecialPageAliases;
00088 
00089         protected $namespaceNames, $mNamespaceIds, $namespaceAliases;
00090 
00094         var $transformData = array();
00095 
00099         static public $dataCache;
00100 
00101         static public $mLangObjCache = array();
00102 
00103         static public $mWeekdayMsgs = array(
00104                 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
00105                 'friday', 'saturday'
00106         );
00107 
00108         static public $mWeekdayAbbrevMsgs = array(
00109                 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
00110         );
00111 
00112         static public $mMonthMsgs = array(
00113                 'january', 'february', 'march', 'april', 'may_long', 'june',
00114                 'july', 'august', 'september', 'october', 'november',
00115                 'december'
00116         );
00117         static public $mMonthGenMsgs = array(
00118                 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
00119                 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
00120                 'december-gen'
00121         );
00122         static public $mMonthAbbrevMsgs = array(
00123                 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
00124                 'sep', 'oct', 'nov', 'dec'
00125         );
00126 
00127         static public $mIranianCalendarMonthMsgs = array(
00128                 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
00129                 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
00130                 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
00131                 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
00132         );
00133 
00134         static public $mHebrewCalendarMonthMsgs = array(
00135                 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
00136                 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
00137                 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
00138                 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
00139                 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
00140         );
00141 
00142         static public $mHebrewCalendarMonthGenMsgs = array(
00143                 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
00144                 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
00145                 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
00146                 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
00147                 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
00148         );
00149 
00150         static public $mHijriCalendarMonthMsgs = array(
00151                 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
00152                 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
00153                 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
00154                 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
00155         );
00156 
00161         static public $durationIntervals = array(
00162                 'millennia' => 31557600000,
00163                 'centuries' => 3155760000,
00164                 'decades' => 315576000,
00165                 'years' => 31557600, // 86400 * 365.25
00166                 'weeks' => 604800,
00167                 'days' => 86400,
00168                 'hours' => 3600,
00169                 'minutes' => 60,
00170                 'seconds' => 1,
00171         );
00172 
00178         static function factory( $code ) {
00179                 if ( !isset( self::$mLangObjCache[$code] ) ) {
00180                         if ( count( self::$mLangObjCache ) > 10 ) {
00181                                 // Don't keep a billion objects around, that's stupid.
00182                                 self::$mLangObjCache = array();
00183                         }
00184                         self::$mLangObjCache[$code] = self::newFromCode( $code );
00185                 }
00186                 return self::$mLangObjCache[$code];
00187         }
00188 
00195         protected static function newFromCode( $code ) {
00196                 // Protect against path traversal below
00197                 if ( !Language::isValidCode( $code )
00198                         || strcspn( $code, ":/\\\000" ) !== strlen( $code ) )
00199                 {
00200                         throw new MWException( "Invalid language code \"$code\"" );
00201                 }
00202 
00203                 if ( !Language::isValidBuiltInCode( $code ) ) {
00204                         // It's not possible to customise this code with class files, so
00205                         // just return a Language object. This is to support uselang= hacks.
00206                         $lang = new Language;
00207                         $lang->setCode( $code );
00208                         return $lang;
00209                 }
00210 
00211                 // Check if there is a language class for the code
00212                 $class = self::classFromCode( $code );
00213                 self::preloadLanguageClass( $class );
00214                 if ( MWInit::classExists( $class ) ) {
00215                         $lang = new $class;
00216                         return $lang;
00217                 }
00218 
00219                 // Keep trying the fallback list until we find an existing class
00220                 $fallbacks = Language::getFallbacksFor( $code );
00221                 foreach ( $fallbacks as $fallbackCode ) {
00222                         if ( !Language::isValidBuiltInCode( $fallbackCode ) ) {
00223                                 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
00224                         }
00225 
00226                         $class = self::classFromCode( $fallbackCode );
00227                         self::preloadLanguageClass( $class );
00228                         if ( MWInit::classExists( $class ) ) {
00229                                 $lang = Language::newFromCode( $fallbackCode );
00230                                 $lang->setCode( $code );
00231                                 return $lang;
00232                         }
00233                 }
00234 
00235                 throw new MWException( "Invalid fallback sequence for language '$code'" );
00236         }
00237 
00247         public static function isValidCode( $code ) {
00248                 return
00249                         // People think language codes are html safe, so enforce it.
00250                         // Ideally we should only allow a-zA-Z0-9-
00251                         // but, .+ and other chars are often used for {{int:}} hacks
00252                         // see bugs 37564, 37587, 36938
00253                         strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
00254                         && !preg_match( Title::getTitleInvalidRegex(), $code );
00255         }
00256 
00267         public static function isValidBuiltInCode( $code ) {
00268 
00269                 if ( !is_string( $code ) ) {
00270                         $type = gettype( $code );
00271                         if ( $type === 'object' ) {
00272                                 $addmsg = " of class " . get_class( $code );
00273                         } else {
00274                                 $addmsg = '';
00275                         }
00276                         throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" );
00277                 }
00278 
00279                 return preg_match( '/^[a-z0-9-]+$/i', $code );
00280         }
00281 
00286         public static function classFromCode( $code ) {
00287                 if ( $code == 'en' ) {
00288                         return 'Language';
00289                 } else {
00290                         return 'Language' . str_replace( '-', '_', ucfirst( $code ) );
00291                 }
00292         }
00293 
00299         public static function preloadLanguageClass( $class ) {
00300                 global $IP;
00301 
00302                 if ( $class === 'Language' ) {
00303                         return;
00304                 }
00305 
00306                 if ( !defined( 'MW_COMPILED' ) ) {
00307                         if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
00308                                 include_once( "$IP/languages/classes/$class.php" );
00309                         }
00310                 }
00311         }
00312 
00318         public static function getLocalisationCache() {
00319                 if ( is_null( self::$dataCache ) ) {
00320                         global $wgLocalisationCacheConf;
00321                         $class = $wgLocalisationCacheConf['class'];
00322                         self::$dataCache = new $class( $wgLocalisationCacheConf );
00323                 }
00324                 return self::$dataCache;
00325         }
00326 
00327         function __construct() {
00328                 $this->mConverter = new FakeConverter( $this );
00329                 // Set the code to the name of the descendant
00330                 if ( get_class( $this ) == 'Language' ) {
00331                         $this->mCode = 'en';
00332                 } else {
00333                         $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
00334                 }
00335                 self::getLocalisationCache();
00336         }
00337 
00341         function __destruct() {
00342                 foreach ( $this as $name => $value ) {
00343                         unset( $this->$name );
00344                 }
00345         }
00346 
00351         function initContLang() { }
00352 
00358         function getFallbackLanguageCode() {
00359                 wfDeprecated( __METHOD__ );
00360                 return self::getFallbackFor( $this->mCode );
00361         }
00362 
00367         function getFallbackLanguages() {
00368                 return self::getFallbacksFor( $this->mCode );
00369         }
00370 
00375         function getBookstoreList() {
00376                 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
00377         }
00378 
00382         public function getNamespaces() {
00383                 if ( is_null( $this->namespaceNames ) ) {
00384                         global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces;
00385 
00386                         $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
00387                         $validNamespaces = MWNamespace::getCanonicalNamespaces();
00388 
00389                         $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
00390 
00391                         $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
00392                         if ( $wgMetaNamespaceTalk ) {
00393                                 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
00394                         } else {
00395                                 $talk = $this->namespaceNames[NS_PROJECT_TALK];
00396                                 $this->namespaceNames[NS_PROJECT_TALK] =
00397                                         $this->fixVariableInNamespace( $talk );
00398                         }
00399 
00400                         # Sometimes a language will be localised but not actually exist on this wiki.
00401                         foreach ( $this->namespaceNames as $key => $text ) {
00402                                 if ( !isset( $validNamespaces[$key] ) ) {
00403                                         unset( $this->namespaceNames[$key] );
00404                                 }
00405                         }
00406 
00407                         # The above mixing may leave namespaces out of canonical order.
00408                         # Re-order by namespace ID number...
00409                         ksort( $this->namespaceNames );
00410 
00411                         wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) );
00412                 }
00413                 return $this->namespaceNames;
00414         }
00415 
00420         public function setNamespaces( array $namespaces ) {
00421                 $this->namespaceNames = $namespaces;
00422         }
00423 
00432         function getFormattedNamespaces() {
00433                 $ns = $this->getNamespaces();
00434                 foreach ( $ns as $k => $v ) {
00435                         $ns[$k] = strtr( $v, '_', ' ' );
00436                 }
00437                 return $ns;
00438         }
00439 
00450         function getNsText( $index ) {
00451                 $ns = $this->getNamespaces();
00452                 return isset( $ns[$index] ) ? $ns[$index] : false;
00453         }
00454 
00464         function getFormattedNsText( $index ) {
00465                 $ns = $this->getNsText( $index );
00466                 return strtr( $ns, '_', ' ' );
00467         }
00468 
00476         function getGenderNsText( $index, $gender ) {
00477                 global $wgExtraGenderNamespaces;
00478 
00479                 $ns = $wgExtraGenderNamespaces + self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00480                 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index );
00481         }
00482 
00489         function needsGenderDistinction() {
00490                 global $wgExtraGenderNamespaces, $wgExtraNamespaces;
00491                 if ( count( $wgExtraGenderNamespaces ) > 0 ) {
00492                         // $wgExtraGenderNamespaces overrides everything
00493                         return true;
00494                 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) {
00496                         // $wgExtraNamespaces overrides any gender aliases specified in i18n files
00497                         return false;
00498                 } else {
00499                         // Check what is in i18n files
00500                         $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00501                         return count( $aliases ) > 0;
00502                 }
00503         }
00504 
00513         function getLocalNsIndex( $text ) {
00514                 $lctext = $this->lc( $text );
00515                 $ids = $this->getNamespaceIds();
00516                 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
00517         }
00518 
00522         function getNamespaceAliases() {
00523                 if ( is_null( $this->namespaceAliases ) ) {
00524                         $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
00525                         if ( !$aliases ) {
00526                                 $aliases = array();
00527                         } else {
00528                                 foreach ( $aliases as $name => $index ) {
00529                                         if ( $index === NS_PROJECT_TALK ) {
00530                                                 unset( $aliases[$name] );
00531                                                 $name = $this->fixVariableInNamespace( $name );
00532                                                 $aliases[$name] = $index;
00533                                         }
00534                                 }
00535                         }
00536 
00537                         global $wgExtraGenderNamespaces;
00538                         $genders = $wgExtraGenderNamespaces + (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' );
00539                         foreach ( $genders as $index => $forms ) {
00540                                 foreach ( $forms as $alias ) {
00541                                         $aliases[$alias] = $index;
00542                                 }
00543                         }
00544 
00545                         $this->namespaceAliases = $aliases;
00546                 }
00547                 return $this->namespaceAliases;
00548         }
00549 
00553         function getNamespaceIds() {
00554                 if ( is_null( $this->mNamespaceIds ) ) {
00555                         global $wgNamespaceAliases;
00556                         # Put namespace names and aliases into a hashtable.
00557                         # If this is too slow, then we should arrange it so that it is done
00558                         # before caching. The catch is that at pre-cache time, the above
00559                         # class-specific fixup hasn't been done.
00560                         $this->mNamespaceIds = array();
00561                         foreach ( $this->getNamespaces() as $index => $name ) {
00562                                 $this->mNamespaceIds[$this->lc( $name )] = $index;
00563                         }
00564                         foreach ( $this->getNamespaceAliases() as $name => $index ) {
00565                                 $this->mNamespaceIds[$this->lc( $name )] = $index;
00566                         }
00567                         if ( $wgNamespaceAliases ) {
00568                                 foreach ( $wgNamespaceAliases as $name => $index ) {
00569                                         $this->mNamespaceIds[$this->lc( $name )] = $index;
00570                                 }
00571                         }
00572                 }
00573                 return $this->mNamespaceIds;
00574         }
00575 
00583         function getNsIndex( $text ) {
00584                 $lctext = $this->lc( $text );
00585                 $ns = MWNamespace::getCanonicalIndex( $lctext );
00586                 if ( $ns !== null ) {
00587                         return $ns;
00588                 }
00589                 $ids = $this->getNamespaceIds();
00590                 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
00591         }
00592 
00600         function getVariantname( $code, $usemsg = true ) {
00601                 $msg = "variantname-$code";
00602                 if ( $usemsg && wfMessage( $msg )->exists() ) {
00603                         return $this->getMessageFromDB( $msg );
00604                 }
00605                 $name = self::fetchLanguageName( $code );
00606                 if ( $name ) {
00607                         return $name; # if it's defined as a language name, show that
00608                 } else {
00609                         # otherwise, output the language code
00610                         return $code;
00611                 }
00612         }
00613 
00618         function specialPage( $name ) {
00619                 $aliases = $this->getSpecialPageAliases();
00620                 if ( isset( $aliases[$name][0] ) ) {
00621                         $name = $aliases[$name][0];
00622                 }
00623                 return $this->getNsText( NS_SPECIAL ) . ':' . $name;
00624         }
00625 
00629         function getQuickbarSettings() {
00630                 return array(
00631                         $this->getMessage( 'qbsettings-none' ),
00632                         $this->getMessage( 'qbsettings-fixedleft' ),
00633                         $this->getMessage( 'qbsettings-fixedright' ),
00634                         $this->getMessage( 'qbsettings-floatingleft' ),
00635                         $this->getMessage( 'qbsettings-floatingright' ),
00636                         $this->getMessage( 'qbsettings-directionality' )
00637                 );
00638         }
00639 
00643         function getDatePreferences() {
00644                 return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
00645         }
00646 
00650         function getDateFormats() {
00651                 return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
00652         }
00653 
00657         function getDefaultDateFormat() {
00658                 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
00659                 if ( $df === 'dmy or mdy' ) {
00660                         global $wgAmericanDates;
00661                         return $wgAmericanDates ? 'mdy' : 'dmy';
00662                 } else {
00663                         return $df;
00664                 }
00665         }
00666 
00670         function getDatePreferenceMigrationMap() {
00671                 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
00672         }
00673 
00678         function getImageFile( $image ) {
00679                 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
00680         }
00681 
00685         function getExtraUserToggles() {
00686                 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
00687         }
00688 
00693         function getUserToggle( $tog ) {
00694                 return $this->getMessageFromDB( "tog-$tog" );
00695         }
00696 
00707         public static function getLanguageNames( $customisedOnly = false ) {
00708                 return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' );
00709         }
00710 
00720         public static function getTranslatedLanguageNames( $code ) {
00721                 return self::fetchLanguageNames( $code, 'all' );
00722         }
00723 
00735         public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) {
00736                 global $wgExtraLanguageNames;
00737                 static $coreLanguageNames;
00738 
00739                 if ( $coreLanguageNames === null ) {
00740                         include( MWInit::compiledPath( 'languages/Names.php' ) );
00741                 }
00742 
00743                 $names = array();
00744 
00745                 if ( $inLanguage ) {
00746                         # TODO: also include when $inLanguage is null, when this code is more efficient
00747                         wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) );
00748                 }
00749 
00750                 $mwNames = $wgExtraLanguageNames + $coreLanguageNames;
00751                 foreach ( $mwNames as $mwCode => $mwName ) {
00752                         # - Prefer own MediaWiki native name when not using the hook
00753                         # - For other names just add if not added through the hook
00754                         if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
00755                                 $names[$mwCode] = $mwName;
00756                         }
00757                 }
00758 
00759                 if ( $include === 'all' ) {
00760                         return $names;
00761                 }
00762 
00763                 $returnMw = array();
00764                 $coreCodes = array_keys( $mwNames );
00765                 foreach ( $coreCodes as $coreCode ) {
00766                         $returnMw[$coreCode] = $names[$coreCode];
00767                 }
00768 
00769                 if ( $include === 'mwfile' ) {
00770                         $namesMwFile = array();
00771                         # We do this using a foreach over the codes instead of a directory
00772                         # loop so that messages files in extensions will work correctly.
00773                         foreach ( $returnMw as $code => $value ) {
00774                                 if ( is_readable( self::getMessagesFileName( $code ) ) ) {
00775                                         $namesMwFile[$code] = $names[$code];
00776                                 }
00777                         }
00778                         return $namesMwFile;
00779                 }
00780                 # 'mw' option; default if it's not one of the other two options (all/mwfile)
00781                 return $returnMw;
00782         }
00783 
00791         public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) {
00792                 $array = self::fetchLanguageNames( $inLanguage, $include );
00793                 return !array_key_exists( $code, $array ) ? '' : $array[$code];
00794         }
00795 
00802         function getMessageFromDB( $msg ) {
00803                 return wfMessage( $msg )->inLanguage( $this )->text();
00804         }
00805 
00813         function getLanguageName( $code ) {
00814                 return self::fetchLanguageName( $code );
00815         }
00816 
00821         function getMonthName( $key ) {
00822                 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
00823         }
00824 
00828         function getMonthNamesArray() {
00829                 $monthNames = array( '' );
00830                 for ( $i = 1; $i < 13; $i++ ) {
00831                         $monthNames[] = $this->getMonthName( $i );
00832                 }
00833                 return $monthNames;
00834         }
00835 
00840         function getMonthNameGen( $key ) {
00841                 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
00842         }
00843 
00848         function getMonthAbbreviation( $key ) {
00849                 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] );
00850         }
00851 
00855         function getMonthAbbreviationsArray() {
00856                 $monthNames = array( '' );
00857                 for ( $i = 1; $i < 13; $i++ ) {
00858                         $monthNames[] = $this->getMonthAbbreviation( $i );
00859                 }
00860                 return $monthNames;
00861         }
00862 
00867         function getWeekdayName( $key ) {
00868                 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] );
00869         }
00870 
00875         function getWeekdayAbbreviation( $key ) {
00876                 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] );
00877         }
00878 
00883         function getIranianCalendarMonthName( $key ) {
00884                 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
00885         }
00886 
00891         function getHebrewCalendarMonthName( $key ) {
00892                 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
00893         }
00894 
00899         function getHebrewCalendarMonthNameGen( $key ) {
00900                 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
00901         }
00902 
00907         function getHijriCalendarMonthName( $key ) {
00908                 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
00909         }
00910 
00973         function sprintfDate( $format, $ts ) {
00974                 $s = '';
00975                 $raw = false;
00976                 $roman = false;
00977                 $hebrewNum = false;
00978                 $unix = false;
00979                 $rawToggle = false;
00980                 $iranian = false;
00981                 $hebrew = false;
00982                 $hijri = false;
00983                 $thai = false;
00984                 $minguo = false;
00985                 $tenno = false;
00986                 for ( $p = 0; $p < strlen( $format ); $p++ ) {
00987                         $num = false;
00988                         $code = $format[$p];
00989                         if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
00990                                 $code .= $format[++$p];
00991                         }
00992 
00993                         if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' || $code == 'xo' || $code == 'xt' ) && $p < strlen( $format ) - 1 ) {
00994                                 $code .= $format[++$p];
00995                         }
00996 
00997                         switch ( $code ) {
00998                                 case 'xx':
00999                                         $s .= 'x';
01000                                         break;
01001                                 case 'xn':
01002                                         $raw = true;
01003                                         break;
01004                                 case 'xN':
01005                                         $rawToggle = !$rawToggle;
01006                                         break;
01007                                 case 'xr':
01008                                         $roman = true;
01009                                         break;
01010                                 case 'xh':
01011                                         $hebrewNum = true;
01012                                         break;
01013                                 case 'xg':
01014                                         $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
01015                                         break;
01016                                 case 'xjx':
01017                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
01018                                         $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
01019                                         break;
01020                                 case 'd':
01021                                         $num = substr( $ts, 6, 2 );
01022                                         break;
01023                                 case 'D':
01024                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
01025                                         $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) + 1 );
01026                                         break;
01027                                 case 'j':
01028                                         $num = intval( substr( $ts, 6, 2 ) );
01029                                         break;
01030                                 case 'xij':
01031                                         if ( !$iranian ) {
01032                                                 $iranian = self::tsToIranian( $ts );
01033                                         }
01034                                         $num = $iranian[2];
01035                                         break;
01036                                 case 'xmj':
01037                                         if ( !$hijri ) {
01038                                                 $hijri = self::tsToHijri( $ts );
01039                                         }
01040                                         $num = $hijri[2];
01041                                         break;
01042                                 case 'xjj':
01043                                         if ( !$hebrew ) {
01044                                                 $hebrew = self::tsToHebrew( $ts );
01045                                         }
01046                                         $num = $hebrew[2];
01047                                         break;
01048                                 case 'l':
01049                                         if ( !$unix ) {
01050                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01051                                         }
01052                                         $s .= $this->getWeekdayName( gmdate( 'w', $unix ) + 1 );
01053                                         break;
01054                                 case 'N':
01055                                         if ( !$unix ) {
01056                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01057                                         }
01058                                         $w = gmdate( 'w', $unix );
01059                                         $num = $w ? $w : 7;
01060                                         break;
01061                                 case 'w':
01062                                         if ( !$unix ) {
01063                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01064                                         }
01065                                         $num = gmdate( 'w', $unix );
01066                                         break;
01067                                 case 'z':
01068                                         if ( !$unix ) {
01069                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01070                                         }
01071                                         $num = gmdate( 'z', $unix );
01072                                         break;
01073                                 case 'W':
01074                                         if ( !$unix ) {
01075                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01076                                         }
01077                                         $num = gmdate( 'W', $unix );
01078                                         break;
01079                                 case 'F':
01080                                         $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
01081                                         break;
01082                                 case 'xiF':
01083                                         if ( !$iranian ) {
01084                                                 $iranian = self::tsToIranian( $ts );
01085                                         }
01086                                         $s .= $this->getIranianCalendarMonthName( $iranian[1] );
01087                                         break;
01088                                 case 'xmF':
01089                                         if ( !$hijri ) {
01090                                                 $hijri = self::tsToHijri( $ts );
01091                                         }
01092                                         $s .= $this->getHijriCalendarMonthName( $hijri[1] );
01093                                         break;
01094                                 case 'xjF':
01095                                         if ( !$hebrew ) {
01096                                                 $hebrew = self::tsToHebrew( $ts );
01097                                         }
01098                                         $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
01099                                         break;
01100                                 case 'm':
01101                                         $num = substr( $ts, 4, 2 );
01102                                         break;
01103                                 case 'M':
01104                                         $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
01105                                         break;
01106                                 case 'n':
01107                                         $num = intval( substr( $ts, 4, 2 ) );
01108                                         break;
01109                                 case 'xin':
01110                                         if ( !$iranian ) {
01111                                                 $iranian = self::tsToIranian( $ts );
01112                                         }
01113                                         $num = $iranian[1];
01114                                         break;
01115                                 case 'xmn':
01116                                         if ( !$hijri ) {
01117                                                 $hijri = self::tsToHijri ( $ts );
01118                                         }
01119                                         $num = $hijri[1];
01120                                         break;
01121                                 case 'xjn':
01122                                         if ( !$hebrew ) {
01123                                                 $hebrew = self::tsToHebrew( $ts );
01124                                         }
01125                                         $num = $hebrew[1];
01126                                         break;
01127                                 case 't':
01128                                         if ( !$unix ) {
01129                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01130                                         }
01131                                         $num = gmdate( 't', $unix );
01132                                         break;
01133                                 case 'xjt':
01134                                         if ( !$hebrew ) {
01135                                                 $hebrew = self::tsToHebrew( $ts );
01136                                         }
01137                                         $num = $hebrew[3];
01138                                         break;
01139                                 case 'L':
01140                                         if ( !$unix ) {
01141                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01142                                         }
01143                                         $num = gmdate( 'L', $unix );
01144                                         break;
01145                                 case 'o':
01146                                         if ( !$unix ) {
01147                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01148                                         }
01149                                         $num = gmdate( 'o', $unix );
01150                                         break;
01151                                 case 'Y':
01152                                         $num = substr( $ts, 0, 4 );
01153                                         break;
01154                                 case 'xiY':
01155                                         if ( !$iranian ) {
01156                                                 $iranian = self::tsToIranian( $ts );
01157                                         }
01158                                         $num = $iranian[0];
01159                                         break;
01160                                 case 'xmY':
01161                                         if ( !$hijri ) {
01162                                                 $hijri = self::tsToHijri( $ts );
01163                                         }
01164                                         $num = $hijri[0];
01165                                         break;
01166                                 case 'xjY':
01167                                         if ( !$hebrew ) {
01168                                                 $hebrew = self::tsToHebrew( $ts );
01169                                         }
01170                                         $num = $hebrew[0];
01171                                         break;
01172                                 case 'xkY':
01173                                         if ( !$thai ) {
01174                                                 $thai = self::tsToYear( $ts, 'thai' );
01175                                         }
01176                                         $num = $thai[0];
01177                                         break;
01178                                 case 'xoY':
01179                                         if ( !$minguo ) {
01180                                                 $minguo = self::tsToYear( $ts, 'minguo' );
01181                                         }
01182                                         $num = $minguo[0];
01183                                         break;
01184                                 case 'xtY':
01185                                         if ( !$tenno ) {
01186                                                 $tenno = self::tsToYear( $ts, 'tenno' );
01187                                         }
01188                                         $num = $tenno[0];
01189                                         break;
01190                                 case 'y':
01191                                         $num = substr( $ts, 2, 2 );
01192                                         break;
01193                                 case 'xiy':
01194                                         if ( !$iranian ) {
01195                                                 $iranian = self::tsToIranian( $ts );
01196                                         }
01197                                         $num = substr( $iranian[0], -2 );
01198                                         break;
01199                                 case 'a':
01200                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
01201                                         break;
01202                                 case 'A':
01203                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
01204                                         break;
01205                                 case 'g':
01206                                         $h = substr( $ts, 8, 2 );
01207                                         $num = $h % 12 ? $h % 12 : 12;
01208                                         break;
01209                                 case 'G':
01210                                         $num = intval( substr( $ts, 8, 2 ) );
01211                                         break;
01212                                 case 'h':
01213                                         $h = substr( $ts, 8, 2 );
01214                                         $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
01215                                         break;
01216                                 case 'H':
01217                                         $num = substr( $ts, 8, 2 );
01218                                         break;
01219                                 case 'i':
01220                                         $num = substr( $ts, 10, 2 );
01221                                         break;
01222                                 case 's':
01223                                         $num = substr( $ts, 12, 2 );
01224                                         break;
01225                                 case 'c':
01226                                         if ( !$unix ) {
01227                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01228                                         }
01229                                         $s .= gmdate( 'c', $unix );
01230                                         break;
01231                                 case 'r':
01232                                         if ( !$unix ) {
01233                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01234                                         }
01235                                         $s .= gmdate( 'r', $unix );
01236                                         break;
01237                                 case 'U':
01238                                         if ( !$unix ) {
01239                                                 $unix = wfTimestamp( TS_UNIX, $ts );
01240                                         }
01241                                         $num = $unix;
01242                                         break;
01243                                 case '\\':
01244                                         # Backslash escaping
01245                                         if ( $p < strlen( $format ) - 1 ) {
01246                                                 $s .= $format[++$p];
01247                                         } else {
01248                                                 $s .= '\\';
01249                                         }
01250                                         break;
01251                                 case '"':
01252                                         # Quoted literal
01253                                         if ( $p < strlen( $format ) - 1 ) {
01254                                                 $endQuote = strpos( $format, '"', $p + 1 );
01255                                                 if ( $endQuote === false ) {
01256                                                         # No terminating quote, assume literal "
01257                                                         $s .= '"';
01258                                                 } else {
01259                                                         $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
01260                                                         $p = $endQuote;
01261                                                 }
01262                                         } else {
01263                                                 # Quote at end of string, assume literal "
01264                                                 $s .= '"';
01265                                         }
01266                                         break;
01267                                 default:
01268                                         $s .= $format[$p];
01269                         }
01270                         if ( $num !== false ) {
01271                                 if ( $rawToggle || $raw ) {
01272                                         $s .= $num;
01273                                         $raw = false;
01274                                 } elseif ( $roman ) {
01275                                         $s .= Language::romanNumeral( $num );
01276                                         $roman = false;
01277                                 } elseif ( $hebrewNum ) {
01278                                         $s .= self::hebrewNumeral( $num );
01279                                         $hebrewNum = false;
01280                                 } else {
01281                                         $s .= $this->formatNum( $num, true );
01282                                 }
01283                         }
01284                 }
01285                 return $s;
01286         }
01287 
01288         private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
01289         private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
01290 
01303         private static function tsToIranian( $ts ) {
01304                 $gy = substr( $ts, 0, 4 ) -1600;
01305                 $gm = substr( $ts, 4, 2 ) -1;
01306                 $gd = substr( $ts, 6, 2 ) -1;
01307 
01308                 # Days passed from the beginning (including leap years)
01309                 $gDayNo = 365 * $gy
01310                         + floor( ( $gy + 3 ) / 4 )
01311                         - floor( ( $gy + 99 ) / 100 )
01312                         + floor( ( $gy + 399 ) / 400 );
01313 
01314                 // Add days of the past months of this year
01315                 for ( $i = 0; $i < $gm; $i++ ) {
01316                         $gDayNo += self::$GREG_DAYS[$i];
01317                 }
01318 
01319                 // Leap years
01320                 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
01321                         $gDayNo++;
01322                 }
01323 
01324                 // Days passed in current month
01325                 $gDayNo += (int)$gd;
01326 
01327                 $jDayNo = $gDayNo - 79;
01328 
01329                 $jNp = floor( $jDayNo / 12053 );
01330                 $jDayNo %= 12053;
01331 
01332                 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
01333                 $jDayNo %= 1461;
01334 
01335                 if ( $jDayNo >= 366 ) {
01336                         $jy += floor( ( $jDayNo - 1 ) / 365 );
01337                         $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
01338                 }
01339 
01340                 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
01341                         $jDayNo -= self::$IRANIAN_DAYS[$i];
01342                 }
01343 
01344                 $jm = $i + 1;
01345                 $jd = $jDayNo + 1;
01346 
01347                 return array( $jy, $jm, $jd );
01348         }
01349 
01361         private static function tsToHijri( $ts ) {
01362                 $year = substr( $ts, 0, 4 );
01363                 $month = substr( $ts, 4, 2 );
01364                 $day = substr( $ts, 6, 2 );
01365 
01366                 $zyr = $year;
01367                 $zd = $day;
01368                 $zm = $month;
01369                 $zy = $zyr;
01370 
01371                 if (
01372                         ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
01373                         ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
01374                 )
01375                 {
01376                         $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
01377                                         (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
01378                                         (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
01379                                         $zd - 32075;
01380                 } else {
01381                         $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
01382                                                                 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
01383                 }
01384 
01385                 $zl = $zjd -1948440 + 10632;
01386                 $zn = (int)( ( $zl - 1 ) / 10631 );
01387                 $zl = $zl - 10631 * $zn + 354;
01388                 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) + ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) );
01389                 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) - ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29;
01390                 $zm = (int)( ( 24 * $zl ) / 709 );
01391                 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
01392                 $zy = 30 * $zn + $zj - 30;
01393 
01394                 return array( $zy, $zm, $zd );
01395         }
01396 
01412         private static function tsToHebrew( $ts ) {
01413                 # Parse date
01414                 $year = substr( $ts, 0, 4 );
01415                 $month = substr( $ts, 4, 2 );
01416                 $day = substr( $ts, 6, 2 );
01417 
01418                 # Calculate Hebrew year
01419                 $hebrewYear = $year + 3760;
01420 
01421                 # Month number when September = 1, August = 12
01422                 $month += 4;
01423                 if ( $month > 12 ) {
01424                         # Next year
01425                         $month -= 12;
01426                         $year++;
01427                         $hebrewYear++;
01428                 }
01429 
01430                 # Calculate day of year from 1 September
01431                 $dayOfYear = $day;
01432                 for ( $i = 1; $i < $month; $i++ ) {
01433                         if ( $i == 6 ) {
01434                                 # February
01435                                 $dayOfYear += 28;
01436                                 # Check if the year is leap
01437                                 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
01438                                         $dayOfYear++;
01439                                 }
01440                         } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
01441                                 $dayOfYear += 30;
01442                         } else {
01443                                 $dayOfYear += 31;
01444                         }
01445                 }
01446 
01447                 # Calculate the start of the Hebrew year
01448                 $start = self::hebrewYearStart( $hebrewYear );
01449 
01450                 # Calculate next year's start
01451                 if ( $dayOfYear <= $start ) {
01452                         # Day is before the start of the year - it is the previous year
01453                         # Next year's start
01454                         $nextStart = $start;
01455                         # Previous year
01456                         $year--;
01457                         $hebrewYear--;
01458                         # Add days since previous year's 1 September
01459                         $dayOfYear += 365;
01460                         if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
01461                                 # Leap year
01462                                 $dayOfYear++;
01463                         }
01464                         # Start of the new (previous) year
01465                         $start = self::hebrewYearStart( $hebrewYear );
01466                 } else {
01467                         # Next year's start
01468                         $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
01469                 }
01470 
01471                 # Calculate Hebrew day of year
01472                 $hebrewDayOfYear = $dayOfYear - $start;
01473 
01474                 # Difference between year's days
01475                 $diff = $nextStart - $start;
01476                 # Add 12 (or 13 for leap years) days to ignore the difference between
01477                 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
01478                 # difference is only about the year type
01479                 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
01480                         $diff += 13;
01481                 } else {
01482                         $diff += 12;
01483                 }
01484 
01485                 # Check the year pattern, and is leap year
01486                 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
01487                 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
01488                 # and non-leap years
01489                 $yearPattern = $diff % 30;
01490                 # Check if leap year
01491                 $isLeap = $diff >= 30;
01492 
01493                 # Calculate day in the month from number of day in the Hebrew year
01494                 # Don't check Adar - if the day is not in Adar, we will stop before;
01495                 # if it is in Adar, we will use it to check if it is Adar I or Adar II
01496                 $hebrewDay = $hebrewDayOfYear;
01497                 $hebrewMonth = 1;
01498                 $days = 0;
01499                 while ( $hebrewMonth <= 12 ) {
01500                         # Calculate days in this month
01501                         if ( $isLeap && $hebrewMonth == 6 ) {
01502                                 # Adar in a leap year
01503                                 if ( $isLeap ) {
01504                                         # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
01505                                         $days = 30;
01506                                         if ( $hebrewDay <= $days ) {
01507                                                 # Day in Adar I
01508                                                 $hebrewMonth = 13;
01509                                         } else {
01510                                                 # Subtract the days of Adar I
01511                                                 $hebrewDay -= $days;
01512                                                 # Try Adar II
01513                                                 $days = 29;
01514                                                 if ( $hebrewDay <= $days ) {
01515                                                         # Day in Adar II
01516                                                         $hebrewMonth = 14;
01517                                                 }
01518                                         }
01519                                 }
01520                         } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
01521                                 # Cheshvan in a complete year (otherwise as the rule below)
01522                                 $days = 30;
01523                         } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
01524                                 # Kislev in an incomplete year (otherwise as the rule below)
01525                                 $days = 29;
01526                         } else {
01527                                 # Odd months have 30 days, even have 29
01528                                 $days = 30 - ( $hebrewMonth - 1 ) % 2;
01529                         }
01530                         if ( $hebrewDay <= $days ) {
01531                                 # In the current month
01532                                 break;
01533                         } else {
01534                                 # Subtract the days of the current month
01535                                 $hebrewDay -= $days;
01536                                 # Try in the next month
01537                                 $hebrewMonth++;
01538                         }
01539                 }
01540 
01541                 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
01542         }
01543 
01553         private static function hebrewYearStart( $year ) {
01554                 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
01555                 $b = intval( ( $year - 1 ) % 4 );
01556                 $m = 32.044093161144 + 1.5542417966212 * $a +  $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
01557                 if ( $m < 0 ) {
01558                         $m--;
01559                 }
01560                 $Mar = intval( $m );
01561                 if ( $m < 0 ) {
01562                         $m++;
01563                 }
01564                 $m -= $Mar;
01565 
01566                 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
01567                 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
01568                         $Mar++;
01569                 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
01570                         $Mar += 2;
01571                 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
01572                         $Mar++;
01573                 }
01574 
01575                 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
01576                 return $Mar;
01577         }
01578 
01591         private static function tsToYear( $ts, $cName ) {
01592                 $gy = substr( $ts, 0, 4 );
01593                 $gm = substr( $ts, 4, 2 );
01594                 $gd = substr( $ts, 6, 2 );
01595 
01596                 if ( !strcmp( $cName, 'thai' ) ) {
01597                         # Thai solar dates
01598                         # Add 543 years to the Gregorian calendar
01599                         # Months and days are identical
01600                         $gy_offset = $gy + 543;
01601                 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) {
01602                         # Minguo dates
01603                         # Deduct 1911 years from the Gregorian calendar
01604                         # Months and days are identical
01605                         $gy_offset = $gy - 1911;
01606                 } elseif ( !strcmp( $cName, 'tenno' ) ) {
01607                         # Nengō dates up to Meiji period
01608                         # Deduct years from the Gregorian calendar
01609                         # depending on the nengo periods
01610                         # Months and days are identical
01611                         if ( ( $gy < 1912 ) || ( ( $gy == 1912 ) && ( $gm < 7 ) ) || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) ) ) {
01612                                 # Meiji period
01613                                 $gy_gannen = $gy - 1868 + 1;
01614                                 $gy_offset = $gy_gannen;
01615                                 if ( $gy_gannen == 1 ) {
01616                                         $gy_offset = '元';
01617                                 }
01618                                 $gy_offset = '明治' . $gy_offset;
01619                         } elseif (
01620                                 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
01621                                 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
01622                                 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
01623                                 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
01624                                 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
01625                         )
01626                         {
01627                                 # Taishō period
01628                                 $gy_gannen = $gy - 1912 + 1;
01629                                 $gy_offset = $gy_gannen;
01630                                 if ( $gy_gannen == 1 ) {
01631                                         $gy_offset = '元';
01632                                 }
01633                                 $gy_offset = '大正' . $gy_offset;
01634                         } elseif (
01635                                 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
01636                                 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
01637                                 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
01638                         )
01639                         {
01640                                 # Shōwa period
01641                                 $gy_gannen = $gy - 1926 + 1;
01642                                 $gy_offset = $gy_gannen;
01643                                 if ( $gy_gannen == 1 ) {
01644                                         $gy_offset = '元';
01645                                 }
01646                                 $gy_offset = '昭和' . $gy_offset;
01647                         } else {
01648                                 # Heisei period
01649                                 $gy_gannen = $gy - 1989 + 1;
01650                                 $gy_offset = $gy_gannen;
01651                                 if ( $gy_gannen == 1 ) {
01652                                         $gy_offset = '元';
01653                                 }
01654                                 $gy_offset = '平成' . $gy_offset;
01655                         }
01656                 } else {
01657                         $gy_offset = $gy;
01658                 }
01659 
01660                 return array( $gy_offset, $gm, $gd );
01661         }
01662 
01670         static function romanNumeral( $num ) {
01671                 static $table = array(
01672                         array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
01673                         array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
01674                         array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
01675                         array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM', 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' )
01676                 );
01677 
01678                 $num = intval( $num );
01679                 if ( $num > 10000 || $num <= 0 ) {
01680                         return $num;
01681                 }
01682 
01683                 $s = '';
01684                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
01685                         if ( $num >= $pow10 ) {
01686                                 $s .= $table[$i][(int)floor( $num / $pow10 )];
01687                         }
01688                         $num = $num % $pow10;
01689                 }
01690                 return $s;
01691         }
01692 
01700         static function hebrewNumeral( $num ) {
01701                 static $table = array(
01702                         array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
01703                         array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
01704                         array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
01705                         array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
01706                 );
01707 
01708                 $num = intval( $num );
01709                 if ( $num > 9999 || $num <= 0 ) {
01710                         return $num;
01711                 }
01712 
01713                 $s = '';
01714                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
01715                         if ( $num >= $pow10 ) {
01716                                 if ( $num == 15 || $num == 16 ) {
01717                                         $s .= $table[0][9] . $table[0][$num - 9];
01718                                         $num = 0;
01719                                 } else {
01720                                         $s .= $table[$i][intval( ( $num / $pow10 ) )];
01721                                         if ( $pow10 == 1000 ) {
01722                                                 $s .= "'";
01723                                         }
01724                                 }
01725                         }
01726                         $num = $num % $pow10;
01727                 }
01728                 if ( strlen( $s ) == 2 ) {
01729                         $str = $s . "'";
01730                 } else  {
01731                         $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
01732                         $str .= substr( $s, strlen( $s ) - 2, 2 );
01733                 }
01734                 $start = substr( $str, 0, strlen( $str ) - 2 );
01735                 $end = substr( $str, strlen( $str ) - 2 );
01736                 switch( $end ) {
01737                         case 'כ':
01738                                 $str = $start . 'ך';
01739                                 break;
01740                         case 'מ':
01741                                 $str = $start . 'ם';
01742                                 break;
01743                         case 'נ':
01744                                 $str = $start . 'ן';
01745                                 break;
01746                         case 'פ':
01747                                 $str = $start . 'ף';
01748                                 break;
01749                         case 'צ':
01750                                 $str = $start . 'ץ';
01751                                 break;
01752                 }
01753                 return $str;
01754         }
01755 
01764         function userAdjust( $ts, $tz = false ) {
01765                 global $wgUser, $wgLocalTZoffset;
01766 
01767                 if ( $tz === false ) {
01768                         $tz = $wgUser->getOption( 'timecorrection' );
01769                 }
01770 
01771                 $data = explode( '|', $tz, 3 );
01772 
01773                 if ( $data[0] == 'ZoneInfo' ) {
01774                         wfSuppressWarnings();
01775                         $userTZ = timezone_open( $data[2] );
01776                         wfRestoreWarnings();
01777                         if ( $userTZ !== false ) {
01778                                 $date = date_create( $ts, timezone_open( 'UTC' ) );
01779                                 date_timezone_set( $date, $userTZ );
01780                                 $date = date_format( $date, 'YmdHis' );
01781                                 return $date;
01782                         }
01783                         # Unrecognized timezone, default to 'Offset' with the stored offset.
01784                         $data[0] = 'Offset';
01785                 }
01786 
01787                 $minDiff = 0;
01788                 if ( $data[0] == 'System' || $tz == '' ) {
01789                         #  Global offset in minutes.
01790                         if ( isset( $wgLocalTZoffset ) ) {
01791                                 $minDiff = $wgLocalTZoffset;
01792                         }
01793                 } elseif ( $data[0] == 'Offset' ) {
01794                         $minDiff = intval( $data[1] );
01795                 } else {
01796                         $data = explode( ':', $tz );
01797                         if ( count( $data ) == 2 ) {
01798                                 $data[0] = intval( $data[0] );
01799                                 $data[1] = intval( $data[1] );
01800                                 $minDiff = abs( $data[0] ) * 60 + $data[1];
01801                                 if ( $data[0] < 0 ) {
01802                                         $minDiff = -$minDiff;
01803                                 }
01804                         } else {
01805                                 $minDiff = intval( $data[0] ) * 60;
01806                         }
01807                 }
01808 
01809                 # No difference ? Return time unchanged
01810                 if ( 0 == $minDiff ) {
01811                         return $ts;
01812                 }
01813 
01814                 wfSuppressWarnings(); // E_STRICT system time bitching
01815                 # Generate an adjusted date; take advantage of the fact that mktime
01816                 # will normalize out-of-range values so we don't have to split $minDiff
01817                 # into hours and minutes.
01818                 $t = mktime( (
01819                   (int)substr( $ts, 8, 2 ) ), # Hours
01820                   (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
01821                   (int)substr( $ts, 12, 2 ), # Seconds
01822                   (int)substr( $ts, 4, 2 ), # Month
01823                   (int)substr( $ts, 6, 2 ), # Day
01824                   (int)substr( $ts, 0, 4 ) ); # Year
01825 
01826                 $date = date( 'YmdHis', $t );
01827                 wfRestoreWarnings();
01828 
01829                 return $date;
01830         }
01831 
01849         function dateFormat( $usePrefs = true ) {
01850                 global $wgUser;
01851 
01852                 if ( is_bool( $usePrefs ) ) {
01853                         if ( $usePrefs ) {
01854                                 $datePreference = $wgUser->getDatePreference();
01855                         } else {
01856                                 $datePreference = (string)User::getDefaultOption( 'date' );
01857                         }
01858                 } else {
01859                         $datePreference = (string)$usePrefs;
01860                 }
01861 
01862                 // return int
01863                 if ( $datePreference == '' ) {
01864                         return 'default';
01865                 }
01866 
01867                 return $datePreference;
01868         }
01869 
01877         function getDateFormatString( $type, $pref ) {
01878                 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
01879                         if ( $pref == 'default' ) {
01880                                 $pref = $this->getDefaultDateFormat();
01881                                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
01882                         } else {
01883                                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
01884                                 if ( is_null( $df ) ) {
01885                                         $pref = $this->getDefaultDateFormat();
01886                                         $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
01887                                 }
01888                         }
01889                         $this->dateFormatStrings[$type][$pref] = $df;
01890                 }
01891                 return $this->dateFormatStrings[$type][$pref];
01892         }
01893 
01904         function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
01905                 $ts = wfTimestamp( TS_MW, $ts );
01906                 if ( $adj ) {
01907                         $ts = $this->userAdjust( $ts, $timecorrection );
01908                 }
01909                 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
01910                 return $this->sprintfDate( $df, $ts );
01911         }
01912 
01923         function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
01924                 $ts = wfTimestamp( TS_MW, $ts );
01925                 if ( $adj ) {
01926                         $ts = $this->userAdjust( $ts, $timecorrection );
01927                 }
01928                 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
01929                 return $this->sprintfDate( $df, $ts );
01930         }
01931 
01943         function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) {
01944                 $ts = wfTimestamp( TS_MW, $ts );
01945                 if ( $adj ) {
01946                         $ts = $this->userAdjust( $ts, $timecorrection );
01947                 }
01948                 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
01949                 return $this->sprintfDate( $df, $ts );
01950         }
01951 
01962         public function formatDuration( $seconds, array $chosenIntervals = array() ) {
01963                 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals );
01964 
01965                 $segments = array();
01966 
01967                 foreach ( $intervals as $intervalName => $intervalValue ) {
01968                         $message = new Message( 'duration-' . $intervalName, array( $intervalValue ) );
01969                         $segments[] = $message->inLanguage( $this )->escaped();
01970                 }
01971 
01972                 return $this->listToText( $segments );
01973         }
01974 
01986         public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) {
01987                 if ( empty( $chosenIntervals ) ) {
01988                         $chosenIntervals = array( 'millennia', 'centuries', 'decades', 'years', 'days', 'hours', 'minutes', 'seconds' );
01989                 }
01990 
01991                 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
01992                 $sortedNames = array_keys( $intervals );
01993                 $smallestInterval = array_pop( $sortedNames );
01994 
01995                 $segments = array();
01996 
01997                 foreach ( $intervals as $name => $length ) {
01998                         $value = floor( $seconds / $length );
01999 
02000                         if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) {
02001                                 $seconds -= $value * $length;
02002                                 $segments[$name] = $value;
02003                         }
02004                 }
02005 
02006                 return $segments;
02007         }
02008 
02028         private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) {
02029                 $ts = wfTimestamp( TS_MW, $ts );
02030                 $options += array( 'timecorrection' => true, 'format' => true );
02031                 if ( $options['timecorrection'] !== false ) {
02032                         if ( $options['timecorrection'] === true ) {
02033                                 $offset = $user->getOption( 'timecorrection' );
02034                         } else {
02035                                 $offset = $options['timecorrection'];
02036                         }
02037                         $ts = $this->userAdjust( $ts, $offset );
02038                 }
02039                 if ( $options['format'] === true ) {
02040                         $format = $user->getDatePreference();
02041                 } else {
02042                         $format = $options['format'];
02043                 }
02044                 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) );
02045                 return $this->sprintfDate( $df, $ts );
02046         }
02047 
02067         public function userDate( $ts, User $user, array $options = array() ) {
02068                 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options );
02069         }
02070 
02090         public function userTime( $ts, User $user, array $options = array() ) {
02091                 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options );
02092         }
02093 
02113         public function userTimeAndDate( $ts, User $user, array $options = array() ) {
02114                 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
02115         }
02116 
02121         function getMessage( $key ) {
02122                 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
02123         }
02124 
02128         function getAllMessages() {
02129                 return self::$dataCache->getItem( $this->mCode, 'messages' );
02130         }
02131 
02138         function iconv( $in, $out, $string ) {
02139                 # This is a wrapper for iconv in all languages except esperanto,
02140                 # which does some nasty x-conversions beforehand
02141 
02142                 # Even with //IGNORE iconv can whine about illegal characters in
02143                 # *input* string. We just ignore those too.
02144                 # REF: http://bugs.php.net/bug.php?id=37166
02145                 # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885
02146                 wfSuppressWarnings();
02147                 $text = iconv( $in, $out . '//IGNORE', $string );
02148                 wfRestoreWarnings();
02149                 return $text;
02150         }
02151 
02152         // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
02153 
02158         function ucwordbreaksCallbackAscii( $matches ) {
02159                 return $this->ucfirst( $matches[1] );
02160         }
02161 
02166         function ucwordbreaksCallbackMB( $matches ) {
02167                 return mb_strtoupper( $matches[0] );
02168         }
02169 
02174         function ucCallback( $matches ) {
02175                 list( $wikiUpperChars ) = self::getCaseMaps();
02176                 return strtr( $matches[1], $wikiUpperChars );
02177         }
02178 
02183         function lcCallback( $matches ) {
02184                 list( , $wikiLowerChars ) = self::getCaseMaps();
02185                 return strtr( $matches[1], $wikiLowerChars );
02186         }
02187 
02192         function ucwordsCallbackMB( $matches ) {
02193                 return mb_strtoupper( $matches[0] );
02194         }
02195 
02200         function ucwordsCallbackWiki( $matches ) {
02201                 list( $wikiUpperChars ) = self::getCaseMaps();
02202                 return strtr( $matches[0], $wikiUpperChars );
02203         }
02204 
02212         function ucfirst( $str ) {
02213                 $o = ord( $str );
02214                 if ( $o < 96 ) { // if already uppercase...
02215                         return $str;
02216                 } elseif ( $o < 128 ) {
02217                         return ucfirst( $str ); // use PHP's ucfirst()
02218                 } else {
02219                         // fall back to more complex logic in case of multibyte strings
02220                         return $this->uc( $str, true );
02221                 }
02222         }
02223 
02232         function uc( $str, $first = false ) {
02233                 if ( function_exists( 'mb_strtoupper' ) ) {
02234                         if ( $first ) {
02235                                 if ( $this->isMultibyte( $str ) ) {
02236                                         return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
02237                                 } else {
02238                                         return ucfirst( $str );
02239                                 }
02240                         } else {
02241                                 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
02242                         }
02243                 } else {
02244                         if ( $this->isMultibyte( $str ) ) {
02245                                 $x = $first ? '^' : '';
02246                                 return preg_replace_callback(
02247                                         "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
02248                                         array( $this, 'ucCallback' ),
02249                                         $str
02250                                 );
02251                         } else {
02252                                 return $first ? ucfirst( $str ) : strtoupper( $str );
02253                         }
02254                 }
02255         }
02256 
02261         function lcfirst( $str ) {
02262                 $o = ord( $str );
02263                 if ( !$o ) {
02264                         return strval( $str );
02265                 } elseif ( $o >= 128 ) {
02266                         return $this->lc( $str, true );
02267                 } elseif ( $o > 96 ) {
02268                         return $str;
02269                 } else {
02270                         $str[0] = strtolower( $str[0] );
02271                         return $str;
02272                 }
02273         }
02274 
02280         function lc( $str, $first = false ) {
02281                 if ( function_exists( 'mb_strtolower' ) ) {
02282                         if ( $first ) {
02283                                 if ( $this->isMultibyte( $str ) ) {
02284                                         return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
02285                                 } else {
02286                                         return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
02287                                 }
02288                         } else {
02289                                 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
02290                         }
02291                 } else {
02292                         if ( $this->isMultibyte( $str ) ) {
02293                                 $x = $first ? '^' : '';
02294                                 return preg_replace_callback(
02295                                         "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
02296                                         array( $this, 'lcCallback' ),
02297                                         $str
02298                                 );
02299                         } else {
02300                                 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
02301                         }
02302                 }
02303         }
02304 
02309         function isMultibyte( $str ) {
02310                 return (bool)preg_match( '/[\x80-\xff]/', $str );
02311         }
02312 
02317         function ucwords( $str ) {
02318                 if ( $this->isMultibyte( $str ) ) {
02319                         $str = $this->lc( $str );
02320 
02321                         // regexp to find first letter in each word (i.e. after each space)
02322                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
02323 
02324                         // function to use to capitalize a single char
02325                         if ( function_exists( 'mb_strtoupper' ) ) {
02326                                 return preg_replace_callback(
02327                                         $replaceRegexp,
02328                                         array( $this, 'ucwordsCallbackMB' ),
02329                                         $str
02330                                 );
02331                         } else {
02332                                 return preg_replace_callback(
02333                                         $replaceRegexp,
02334                                         array( $this, 'ucwordsCallbackWiki' ),
02335                                         $str
02336                                 );
02337                         }
02338                 } else {
02339                         return ucwords( strtolower( $str ) );
02340                 }
02341         }
02342 
02349         function ucwordbreaks( $str ) {
02350                 if ( $this->isMultibyte( $str ) ) {
02351                         $str = $this->lc( $str );
02352 
02353                         // since \b doesn't work for UTF-8, we explicitely define word break chars
02354                         $breaks = "[ \-\(\)\}\{\.,\?!]";
02355 
02356                         // find first letter after word break
02357                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
02358 
02359                         if ( function_exists( 'mb_strtoupper' ) ) {
02360                                 return preg_replace_callback(
02361                                         $replaceRegexp,
02362                                         array( $this, 'ucwordbreaksCallbackMB' ),
02363                                         $str
02364                                 );
02365                         } else {
02366                                 return preg_replace_callback(
02367                                         $replaceRegexp,
02368                                         array( $this, 'ucwordsCallbackWiki' ),
02369                                         $str
02370                                 );
02371                         }
02372                 } else {
02373                         return preg_replace_callback(
02374                                 '/\b([\w\x80-\xff]+)\b/',
02375                                 array( $this, 'ucwordbreaksCallbackAscii' ),
02376                                 $str
02377                         );
02378                 }
02379         }
02380 
02396         function caseFold( $s ) {
02397                 return $this->uc( $s );
02398         }
02399 
02404         function checkTitleEncoding( $s ) {
02405                 if ( is_array( $s ) ) {
02406                         wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
02407                 }
02408                 # Check for non-UTF-8 URLs
02409                 $ishigh = preg_match( '/[\x80-\xff]/', $s );
02410                 if ( !$ishigh ) {
02411                         return $s;
02412                 }
02413 
02414                 if ( function_exists( 'mb_check_encoding' ) ) {
02415                         $isutf8 = mb_check_encoding( $s, 'UTF-8' );
02416                 } else {
02417                         $isutf8 = preg_match( '/^(?>[\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
02418                                         '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
02419                 }
02420                 if ( $isutf8 ) {
02421                         return $s;
02422                 }
02423 
02424                 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s );
02425         }
02426 
02430         function fallback8bitEncoding() {
02431                 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
02432         }
02433 
02442         function hasWordBreaks() {
02443                 return true;
02444         }
02445 
02453         function segmentByWord( $string ) {
02454                 return $string;
02455         }
02456 
02464         function normalizeForSearch( $string ) {
02465                 return self::convertDoubleWidth( $string );
02466         }
02467 
02476         protected static function convertDoubleWidth( $string ) {
02477                 static $full = null;
02478                 static $half = null;
02479 
02480                 if ( $full === null ) {
02481                         $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
02482                         $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
02483                         $full = str_split( $fullWidth, 3 );
02484                         $half = str_split( $halfWidth );
02485                 }
02486 
02487                 $string = str_replace( $full, $half, $string );
02488                 return $string;
02489         }
02490 
02496         protected static function insertSpace( $string, $pattern ) {
02497                 $string = preg_replace( $pattern, " $1 ", $string );
02498                 $string = preg_replace( '/ +/', ' ', $string );
02499                 return $string;
02500         }
02501 
02506         function convertForSearchResult( $termsArray ) {
02507                 # some languages, e.g. Chinese, need to do a conversion
02508                 # in order for search results to be displayed correctly
02509                 return $termsArray;
02510         }
02511 
02518         function firstChar( $s ) {
02519                 $matches = array();
02520                 preg_match(
02521                         '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
02522                                 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
02523                         $s,
02524                         $matches
02525                 );
02526 
02527                 if ( isset( $matches[1] ) ) {
02528                         if ( strlen( $matches[1] ) != 3 ) {
02529                                 return $matches[1];
02530                         }
02531 
02532                         // Break down Hangul syllables to grab the first jamo
02533                         $code = utf8ToCodepoint( $matches[1] );
02534                         if ( $code < 0xac00 || 0xd7a4 <= $code ) {
02535                                 return $matches[1];
02536                         } elseif ( $code < 0xb098 ) {
02537                                 return "\xe3\x84\xb1";
02538                         } elseif ( $code < 0xb2e4 ) {
02539                                 return "\xe3\x84\xb4";
02540                         } elseif ( $code < 0xb77c ) {
02541                                 return "\xe3\x84\xb7";
02542                         } elseif ( $code < 0xb9c8 ) {
02543                                 return "\xe3\x84\xb9";
02544                         } elseif ( $code < 0xbc14 ) {
02545                                 return "\xe3\x85\x81";
02546                         } elseif ( $code < 0xc0ac ) {
02547                                 return "\xe3\x85\x82";
02548                         } elseif ( $code < 0xc544 ) {
02549                                 return "\xe3\x85\x85";
02550                         } elseif ( $code < 0xc790 ) {
02551                                 return "\xe3\x85\x87";
02552                         } elseif ( $code < 0xcc28 ) {
02553                                 return "\xe3\x85\x88";
02554                         } elseif ( $code < 0xce74 ) {
02555                                 return "\xe3\x85\x8a";
02556                         } elseif ( $code < 0xd0c0 ) {
02557                                 return "\xe3\x85\x8b";
02558                         } elseif ( $code < 0xd30c ) {
02559                                 return "\xe3\x85\x8c";
02560                         } elseif ( $code < 0xd558 ) {
02561                                 return "\xe3\x85\x8d";
02562                         } else {
02563                                 return "\xe3\x85\x8e";
02564                         }
02565                 } else {
02566                         return '';
02567                 }
02568         }
02569 
02570         function initEncoding() {
02571                 # Some languages may have an alternate char encoding option
02572                 # (Esperanto X-coding, Japanese furigana conversion, etc)
02573                 # If this language is used as the primary content language,
02574                 # an override to the defaults can be set here on startup.
02575         }
02576 
02581         function recodeForEdit( $s ) {
02582                 # For some languages we'll want to explicitly specify
02583                 # which characters make it into the edit box raw
02584                 # or are converted in some way or another.
02585                 global $wgEditEncoding;
02586                 if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) {
02587                         return $s;
02588                 } else {
02589                         return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
02590                 }
02591         }
02592 
02597         function recodeInput( $s ) {
02598                 # Take the previous into account.
02599                 global $wgEditEncoding;
02600                 if ( $wgEditEncoding != '' ) {
02601                         $enc = $wgEditEncoding;
02602                 } else {
02603                         $enc = 'UTF-8';
02604                 }
02605                 if ( $enc == 'UTF-8' ) {
02606                         return $s;
02607                 } else {
02608                         return $this->iconv( $enc, 'UTF-8', $s );
02609                 }
02610         }
02611 
02623         function normalize( $s ) {
02624                 global $wgAllUnicodeFixes;
02625                 $s = UtfNormal::cleanUp( $s );
02626                 if ( $wgAllUnicodeFixes ) {
02627                         $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s );
02628                         $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s );
02629                 }
02630 
02631                 return $s;
02632         }
02633 
02648         function transformUsingPairFile( $file, $string ) {
02649                 if ( !isset( $this->transformData[$file] ) ) {
02650                         $data = wfGetPrecompiledData( $file );
02651                         if ( $data === false ) {
02652                                 throw new MWException( __METHOD__ . ": The transformation file $file is missing" );
02653                         }
02654                         $this->transformData[$file] = new ReplacementArray( $data );
02655                 }
02656                 return $this->transformData[$file]->replace( $string );
02657         }
02658 
02664         function isRTL() {
02665                 return self::$dataCache->getItem( $this->mCode, 'rtl' );
02666         }
02667 
02672         function getDir() {
02673                 return $this->isRTL() ? 'rtl' : 'ltr';
02674         }
02675 
02684         function alignStart() {
02685                 return $this->isRTL() ? 'right' : 'left';
02686         }
02687 
02696         function alignEnd() {
02697                 return $this->isRTL() ? 'left' : 'right';
02698         }
02699 
02711         function getDirMarkEntity( $opposite = false ) {
02712                 if ( $opposite ) { return $this->isRTL() ? '&lrm;' : '&rlm;'; }
02713                 return $this->isRTL() ? '&rlm;' : '&lrm;';
02714         }
02715 
02726         function getDirMark( $opposite = false ) {
02727                 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
02728                 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
02729                 if ( $opposite ) { return $this->isRTL() ? $lrm : $rlm; }
02730                 return $this->isRTL() ? $rlm : $lrm;
02731         }
02732 
02736         function capitalizeAllNouns() {
02737                 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
02738         }
02739 
02746         function getArrow( $direction = 'forwards' ) {
02747                 switch ( $direction ) {
02748                 case 'forwards':
02749                         return $this->isRTL() ? '←' : '→';
02750                 case 'backwards':
02751                         return $this->isRTL() ? '→' : '←';
02752                 case 'left':
02753                         return '←';
02754                 case 'right':
02755                         return '→';
02756                 case 'up':
02757                         return '↑';
02758                 case 'down':
02759                         return '↓';
02760                 }
02761         }
02762 
02768         function linkPrefixExtension() {
02769                 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
02770         }
02771 
02775         function getMagicWords() {
02776                 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
02777         }
02778 
02779         protected function doMagicHook() {
02780                 if ( $this->mMagicHookDone ) {
02781                         return;
02782                 }
02783                 $this->mMagicHookDone = true;
02784                 wfProfileIn( 'LanguageGetMagic' );
02785                 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
02786                 wfProfileOut( 'LanguageGetMagic' );
02787         }
02788 
02794         function getMagic( $mw ) {
02795                 $this->doMagicHook();
02796 
02797                 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
02798                         $rawEntry = $this->mMagicExtensions[$mw->mId];
02799                 } else {
02800                         $magicWords = $this->getMagicWords();
02801                         if ( isset( $magicWords[$mw->mId] ) ) {
02802                                 $rawEntry = $magicWords[$mw->mId];
02803                         } else {
02804                                 $rawEntry = false;
02805                         }
02806                 }
02807 
02808                 if ( !is_array( $rawEntry ) ) {
02809                         error_log( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
02810                 } else {
02811                         $mw->mCaseSensitive = $rawEntry[0];
02812                         $mw->mSynonyms = array_slice( $rawEntry, 1 );
02813                 }
02814         }
02815 
02821         function addMagicWordsByLang( $newWords ) {
02822                 $fallbackChain = $this->getFallbackLanguages();
02823                 $fallbackChain = array_reverse( $fallbackChain );
02824                 foreach ( $fallbackChain as $code ) {
02825                         if ( isset( $newWords[$code] ) ) {
02826                                 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
02827                         }
02828                 }
02829         }
02830 
02835         function getSpecialPageAliases() {
02836                 // Cache aliases because it may be slow to load them
02837                 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
02838                         // Initialise array
02839                         $this->mExtendedSpecialPageAliases =
02840                                 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
02841                         wfRunHooks( 'LanguageGetSpecialPageAliases',
02842                                 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
02843                 }
02844 
02845                 return $this->mExtendedSpecialPageAliases;
02846         }
02847 
02854         function emphasize( $text ) {
02855                 return "<em>$text</em>";
02856         }
02857 
02882         public function formatNum( $number, $nocommafy = false ) {
02883                 global $wgTranslateNumerals;
02884                 if ( !$nocommafy ) {
02885                         $number = $this->commafy( $number );
02886                         $s = $this->separatorTransformTable();
02887                         if ( $s ) {
02888                                 $number = strtr( $number, $s );
02889                         }
02890                 }
02891 
02892                 if ( $wgTranslateNumerals ) {
02893                         $s = $this->digitTransformTable();
02894                         if ( $s ) {
02895                                 $number = strtr( $number, $s );
02896                         }
02897                 }
02898 
02899                 return $number;
02900         }
02901 
02906         function parseFormattedNumber( $number ) {
02907                 $s = $this->digitTransformTable();
02908                 if ( $s ) {
02909                         $number = strtr( $number, array_flip( $s ) );
02910                 }
02911 
02912                 $s = $this->separatorTransformTable();
02913                 if ( $s ) {
02914                         $number = strtr( $number, array_flip( $s ) );
02915                 }
02916 
02917                 $number = strtr( $number, array( ',' => '' ) );
02918                 return $number;
02919         }
02920 
02927         function commafy( $_ ) {
02928                 $digitGroupingPattern = $this->digitGroupingPattern();
02929                 if ( $_ === null ) {
02930                         return '';
02931                 }
02932 
02933                 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) {
02934                         // default grouping is at thousands,  use the same for ###,###,### pattern too.
02935                         return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $_ ) ) );
02936                 } else {
02937                         // Ref: http://cldr.unicode.org/translation/number-patterns
02938                         $sign = "";
02939                         if ( intval( $_ ) < 0 ) {
02940                                 // For negative numbers apply the algorithm like positive number and add sign.
02941                                 $sign =  "-";
02942                                 $_ = substr( $_, 1 );
02943                         }
02944                         $numberpart = array();
02945                         $decimalpart = array();
02946                         $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
02947                         preg_match( "/\d+/", $_, $numberpart );
02948                         preg_match( "/\.\d*/", $_, $decimalpart );
02949                         $groupedNumber = ( count( $decimalpart ) > 0 ) ? $decimalpart[0]:"";
02950                         if ( $groupedNumber  === $_ ) {
02951                                 // the string does not have any number part. Eg: .12345
02952                                 return $sign . $groupedNumber;
02953                         }
02954                         $start = $end = strlen( $numberpart[0] );
02955                         while ( $start > 0 ) {
02956                                 $match = $matches[0][$numMatches -1] ;
02957                                 $matchLen = strlen( $match );
02958                                 $start = $end - $matchLen;
02959                                 if ( $start < 0 ) {
02960                                         $start = 0;
02961                                 }
02962                                 $groupedNumber = substr( $_ , $start, $end -$start ) . $groupedNumber ;
02963                                 $end = $start;
02964                                 if ( $numMatches > 1 ) {
02965                                         // use the last pattern for the rest of the number
02966                                         $numMatches--;
02967                                 }
02968                                 if ( $start > 0 ) {
02969                                         $groupedNumber = "," . $groupedNumber;
02970                                 }
02971                         }
02972                         return $sign . $groupedNumber;
02973                 }
02974         }
02978         function digitGroupingPattern() {
02979                 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' );
02980         }
02981 
02985         function digitTransformTable() {
02986                 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
02987         }
02988 
02992         function separatorTransformTable() {
02993                 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
02994         }
02995 
03005         function listToText( array $l ) {
03006                 $s = '';
03007                 $m = count( $l ) - 1;
03008 
03009                 if ( $m === 0 ) {
03010                         return $l[0];
03011                 } elseif ( $m === 1 ) {
03012                         return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
03013                 } else {
03014                         for ( $i = $m; $i >= 0; $i-- ) {
03015                                 if ( $i == $m ) {
03016                                         $s = $l[$i];
03017                                 } elseif ( $i == $m - 1 ) {
03018                                         $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
03019                                 } else {
03020                                         $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
03021                                 }
03022                         }
03023                         return $s;
03024                 }
03025         }
03026 
03033         function commaList( array $list ) {
03034                 return implode(
03035                         wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(),
03036                         $list
03037                 );
03038         }
03039 
03046         function semicolonList( array $list ) {
03047                 return implode(
03048                         wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(),
03049                         $list
03050                 );
03051         }
03052 
03058         function pipeList( array $list ) {
03059                 return implode(
03060                         wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(),
03061                         $list
03062                 );
03063         }
03064 
03082         function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) {
03083                 # Use the localized ellipsis character
03084                 if ( $ellipsis == '...' ) {
03085                         $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
03086                 }
03087                 # Check if there is no need to truncate
03088                 if ( $length == 0 ) {
03089                         return $ellipsis; // convention
03090                 } elseif ( strlen( $string ) <= abs( $length ) ) {
03091                         return $string; // no need to truncate
03092                 }
03093                 $stringOriginal = $string;
03094                 # If ellipsis length is >= $length then we can't apply $adjustLength
03095                 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
03096                         $string = $ellipsis; // this can be slightly unexpected
03097                 # Otherwise, truncate and add ellipsis...
03098                 } else {
03099                         $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
03100                         if ( $length > 0 ) {
03101                                 $length -= $eLength;
03102                                 $string = substr( $string, 0, $length ); // xyz...
03103                                 $string = $this->removeBadCharLast( $string );
03104                                 $string = $string . $ellipsis;
03105                         } else {
03106                                 $length += $eLength;
03107                                 $string = substr( $string, $length ); // ...xyz
03108                                 $string = $this->removeBadCharFirst( $string );
03109                                 $string = $ellipsis . $string;
03110                         }
03111                 }
03112                 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
03113                 # This check is *not* redundant if $adjustLength, due to the single case where
03114                 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
03115                 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
03116                         return $string;
03117                 } else {
03118                         return $stringOriginal;
03119                 }
03120         }
03121 
03129         protected function removeBadCharLast( $string ) {
03130                 if ( $string != '' ) {
03131                         $char = ord( $string[strlen( $string ) - 1] );
03132                         $m = array();
03133                         if ( $char >= 0xc0 ) {
03134                                 # We got the first byte only of a multibyte char; remove it.
03135                                 $string = substr( $string, 0, -1 );
03136                         } elseif ( $char >= 0x80 &&
03137                                   preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
03138                                                           '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) )
03139                         {
03140                                 # We chopped in the middle of a character; remove it
03141                                 $string = $m[1];
03142                         }
03143                 }
03144                 return $string;
03145         }
03146 
03154         protected function removeBadCharFirst( $string ) {
03155                 if ( $string != '' ) {
03156                         $char = ord( $string[0] );
03157                         if ( $char >= 0x80 && $char < 0xc0 ) {
03158                                 # We chopped in the middle of a character; remove the whole thing
03159                                 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
03160                         }
03161                 }
03162                 return $string;
03163         }
03164 
03180         function truncateHtml( $text, $length, $ellipsis = '...' ) {
03181                 # Use the localized ellipsis character
03182                 if ( $ellipsis == '...' ) {
03183                         $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped();
03184                 }
03185                 # Check if there is clearly no need to truncate
03186                 if ( $length <= 0 ) {
03187                         return $ellipsis; // no text shown, nothing to format (convention)
03188                 } elseif ( strlen( $text ) <= $length ) {
03189                         return $text; // string short enough even *with* HTML (short-circuit)
03190                 }
03191 
03192                 $dispLen = 0; // innerHTML legth so far
03193                 $testingEllipsis = false; // checking if ellipses will make string longer/equal?
03194                 $tagType = 0; // 0-open, 1-close
03195                 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither
03196                 $entityState = 0; // 0-not entity, 1-entity
03197                 $tag = $ret = ''; // accumulated tag name, accumulated result string
03198                 $openTags = array(); // open tag stack
03199                 $maybeState = null; // possible truncation state
03200 
03201                 $textLen = strlen( $text );
03202                 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated
03203                 for ( $pos = 0; true; ++$pos ) {
03204                         # Consider truncation once the display length has reached the maximim.
03205                         # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
03206                         # Check that we're not in the middle of a bracket/entity...
03207                         if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
03208                                 if ( !$testingEllipsis ) {
03209                                         $testingEllipsis = true;
03210                                         # Save where we are; we will truncate here unless there turn out to
03211                                         # be so few remaining characters that truncation is not necessary.
03212                                         if ( !$maybeState ) { // already saved? ($neLength = 0 case)
03213                                                 $maybeState = array( $ret, $openTags ); // save state
03214                                         }
03215                                 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
03216                                         # String in fact does need truncation, the truncation point was OK.
03217                                         list( $ret, $openTags ) = $maybeState; // reload state
03218                                         $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix
03219                                         $ret .= $ellipsis; // add ellipsis
03220                                         break;
03221                                 }
03222                         }
03223                         if ( $pos >= $textLen ) break; // extra iteration just for above checks
03224 
03225                         # Read the next char...
03226                         $ch = $text[$pos];
03227                         $lastCh = $pos ? $text[$pos - 1] : '';
03228                         $ret .= $ch; // add to result string
03229                         if ( $ch == '<' ) {
03230                                 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML
03231                                 $entityState = 0; // for bad HTML
03232                                 $bracketState = 1; // tag started (checking for backslash)
03233                         } elseif ( $ch == '>' ) {
03234                                 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags );
03235                                 $entityState = 0; // for bad HTML
03236                                 $bracketState = 0; // out of brackets
03237                         } elseif ( $bracketState == 1 ) {
03238                                 if ( $ch == '/' ) {
03239                                         $tagType = 1; // close tag (e.g. "</span>")
03240                                 } else {
03241                                         $tagType = 0; // open tag (e.g. "<span>")
03242                                         $tag .= $ch;
03243                                 }
03244                                 $bracketState = 2; // building tag name
03245                         } elseif ( $bracketState == 2 ) {
03246                                 if ( $ch != ' ' ) {
03247                                         $tag .= $ch;
03248                                 } else {
03249                                         // Name found (e.g. "<a href=..."), add on tag attributes...
03250                                         $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 );
03251                                 }
03252                         } elseif ( $bracketState == 0 ) {
03253                                 if ( $entityState ) {
03254                                         if ( $ch == ';' ) {
03255                                                 $entityState = 0;
03256                                                 $dispLen++; // entity is one displayed char
03257                                         }
03258                                 } else {
03259                                         if ( $neLength == 0 && !$maybeState ) {
03260                                                 // Save state without $ch. We want to *hit* the first
03261                                                 // display char (to get tags) but not *use* it if truncating.
03262                                                 $maybeState = array( substr( $ret, 0, -1 ), $openTags );
03263                                         }
03264                                         if ( $ch == '&' ) {
03265                                                 $entityState = 1; // entity found, (e.g. "&#160;")
03266                                         } else {
03267                                                 $dispLen++; // this char is displayed
03268                                                 // Add the next $max display text chars after this in one swoop...
03269                                                 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
03270                                                 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max );
03271                                                 $dispLen += $skipped;
03272                                                 $pos += $skipped;
03273                                         }
03274                                 }
03275                         }
03276                 }
03277                 // Close the last tag if left unclosed by bad HTML
03278                 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags );
03279                 while ( count( $openTags ) > 0 ) {
03280                         $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags
03281                 }
03282                 return $ret;
03283         }
03284 
03296         private function truncate_skip( &$ret, $text, $search, $start, $len = null ) {
03297                 if ( $len === null ) {
03298                         $len = -1; // -1 means "no limit" for strcspn
03299                 } elseif ( $len < 0 ) {
03300                         $len = 0; // sanity
03301                 }
03302                 $skipCount = 0;
03303                 if ( $start < strlen( $text ) ) {
03304                         $skipCount = strcspn( $text, $search, $start, $len );
03305                         $ret .= substr( $text, $start, $skipCount );
03306                 }
03307                 return $skipCount;
03308         }
03309 
03319         private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) {
03320                 $tag = ltrim( $tag );
03321                 if ( $tag != '' ) {
03322                         if ( $tagType == 0 && $lastCh != '/' ) {
03323                                 $openTags[] = $tag; // tag opened (didn't close itself)
03324                         } elseif ( $tagType == 1 ) {
03325                                 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
03326                                         array_pop( $openTags ); // tag closed
03327                                 }
03328                         }
03329                         $tag = '';
03330                 }
03331         }
03332 
03341         function convertGrammar( $word, $case ) {
03342                 global $wgGrammarForms;
03343                 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) {
03344                         return $wgGrammarForms[$this->getCode()][$case][$word];
03345                 }
03346                 return $word;
03347         }
03353         function getGrammarForms() {
03354                 global $wgGrammarForms;
03355                 if ( isset( $wgGrammarForms[$this->getCode()] ) && is_array( $wgGrammarForms[$this->getCode()] ) ) {
03356                          return $wgGrammarForms[$this->getCode()];
03357                 }
03358                 return array();
03359         }
03379         function gender( $gender, $forms ) {
03380                 if ( !count( $forms ) ) {
03381                         return '';
03382                 }
03383                 $forms = $this->preConvertPlural( $forms, 2 );
03384                 if ( $gender === 'male' ) {
03385                         return $forms[0];
03386                 }
03387                 if ( $gender === 'female' ) {
03388                         return $forms[1];
03389                 }
03390                 return isset( $forms[2] ) ? $forms[2] : $forms[0];
03391         }
03392 
03408         function convertPlural( $count, $forms ) {
03409                 if ( !count( $forms ) ) {
03410                         return '';
03411                 }
03412                 $pluralForm = $this->getPluralForm( $count );
03413                 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
03414                 return $forms[$pluralForm];
03415         }
03416 
03425         protected function preConvertPlural( /* Array */ $forms, $count ) {
03426                 while ( count( $forms ) < $count ) {
03427                         $forms[] = $forms[count( $forms ) - 1];
03428                 }
03429                 return $forms;
03430         }
03431 
03443         function translateBlockExpiry( $str ) {
03444                 $duration = SpecialBlock::getSuggestedDurations( $this );
03445                 foreach ( $duration as $show => $value ) {
03446                         if ( strcmp( $str, $value ) == 0 ) {
03447                                 return htmlspecialchars( trim( $show ) );
03448                         }
03449                 }
03450 
03451                 // Since usually only infinite or indefinite is only on list, so try
03452                 // equivalents if still here.
03453                 $indefs = array( 'infinite', 'infinity', 'indefinite' );
03454                 if ( in_array( $str, $indefs ) ) {
03455                         foreach ( $indefs as $val ) {
03456                                 $show = array_search( $val, $duration, true );
03457                                 if ( $show !== false ) {
03458                                         return htmlspecialchars( trim( $show ) );
03459                                 }
03460                         }
03461                 }
03462                 // If all else fails, return the original string.
03463                 return $str;
03464         }
03465 
03473         public function segmentForDiff( $text ) {
03474                 return $text;
03475         }
03476 
03483         public function unsegmentForDiff( $text ) {
03484                 return $text;
03485         }
03486 
03493         public function getConverter() {
03494                 return $this->mConverter;
03495         }
03496 
03503         public function autoConvertToAllVariants( $text ) {
03504                 return $this->mConverter->autoConvertToAllVariants( $text );
03505         }
03506 
03513         public function convert( $text ) {
03514                 return $this->mConverter->convert( $text );
03515         }
03516 
03523         public function convertTitle( $title ) {
03524                 return $this->mConverter->convertTitle( $title );
03525         }
03526 
03532         public function hasVariants() {
03533                 return sizeof( $this->getVariants() ) > 1;
03534         }
03535 
03543         public function hasVariant( $variant ) {
03544                 return (bool)$this->mConverter->validateVariant( $variant );
03545         }
03546 
03553         public function armourMath( $text ) {
03554                 return $this->mConverter->armourMath( $text );
03555         }
03556 
03564         public function convertHtml( $text, $isTitle = false ) {
03565                 return htmlspecialchars( $this->convert( $text, $isTitle ) );
03566         }
03567 
03572         public function convertCategoryKey( $key ) {
03573                 return $this->mConverter->convertCategoryKey( $key );
03574         }
03575 
03582         public function getVariants() {
03583                 return $this->mConverter->getVariants();
03584         }
03585 
03589         public function getPreferredVariant() {
03590                 return $this->mConverter->getPreferredVariant();
03591         }
03592 
03596         public function getDefaultVariant() {
03597                 return $this->mConverter->getDefaultVariant();
03598         }
03599 
03603         public function getURLVariant() {
03604                 return $this->mConverter->getURLVariant();
03605         }
03606 
03619         public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
03620                 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
03621         }
03622 
03634         public function convertLinkToAllVariants( $text ) {
03635                 return $this->mConverter->convertLinkToAllVariants( $text );
03636         }
03637 
03644         function getExtraHashOptions() {
03645                 return $this->mConverter->getExtraHashOptions();
03646         }
03647 
03655         public function getParsedTitle() {
03656                 return $this->mConverter->getParsedTitle();
03657         }
03658 
03667         public function markNoConversion( $text, $noParse = false ) {
03668                 return $this->mConverter->markNoConversion( $text, $noParse );
03669         }
03670 
03677         public function linkTrail() {
03678                 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
03679         }
03680 
03684         function getLangObj() {
03685                 return $this;
03686         }
03687 
03696         public function getCode() {
03697                 return $this->mCode;
03698         }
03699 
03710         public function getHtmlCode() {
03711                 if ( is_null( $this->mHtmlCode ) ) {
03712                         $this->mHtmlCode = wfBCP47( $this->getCode() );
03713                 }
03714                 return $this->mHtmlCode;
03715         }
03716 
03720         public function setCode( $code ) {
03721                 $this->mCode = $code;
03722                 // Ensure we don't leave an incorrect html code lying around
03723                 $this->mHtmlCode = null;
03724         }
03725 
03734         public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
03735                 // Protect against path traversal
03736                 if ( !Language::isValidCode( $code )
03737                         || strcspn( $code, ":/\\\000" ) !== strlen( $code ) )
03738                 {
03739                         throw new MWException( "Invalid language code \"$code\"" );
03740                 }
03741 
03742                 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
03743         }
03744 
03752         public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
03753                 $m = null;
03754                 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
03755                         preg_quote( $suffix, '/' ) . '/', $filename, $m );
03756                 if ( !count( $m ) ) {
03757                         return false;
03758                 }
03759                 return str_replace( '_', '-', strtolower( $m[1] ) );
03760         }
03761 
03766         public static function getMessagesFileName( $code ) {
03767                 global $IP;
03768                 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
03769                 wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) );
03770                 return $file;
03771         }
03772 
03777         public static function getClassFileName( $code ) {
03778                 global $IP;
03779                 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
03780         }
03781 
03789         public static function getFallbackFor( $code ) {
03790                 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
03791                         return false;
03792                 } else {
03793                         $fallbacks = self::getFallbacksFor( $code );
03794                         $first = array_shift( $fallbacks );
03795                         return $first;
03796                 }
03797         }
03798 
03806         public static function getFallbacksFor( $code ) {
03807                 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) {
03808                         return array();
03809                 } else {
03810                         $v = self::getLocalisationCache()->getItem( $code, 'fallback' );
03811                         $v = array_map( 'trim', explode( ',', $v ) );
03812                         if ( $v[count( $v ) - 1] !== 'en' ) {
03813                                 $v[] = 'en';
03814                         }
03815                         return $v;
03816                 }
03817         }
03818 
03828         public static function getMessagesFor( $code ) {
03829                 return self::getLocalisationCache()->getItem( $code, 'messages' );
03830         }
03831 
03840         public static function getMessageFor( $key, $code ) {
03841                 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
03842         }
03843 
03852         public static function getMessageKeysFor( $code ) {
03853                 return self::getLocalisationCache()->getSubItemList( $code, 'messages' );
03854         }
03855 
03860         function fixVariableInNamespace( $talk ) {
03861                 if ( strpos( $talk, '$1' ) === false ) {
03862                         return $talk;
03863                 }
03864 
03865                 global $wgMetaNamespace;
03866                 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
03867 
03868                 # Allow grammar transformations
03869                 # Allowing full message-style parsing would make simple requests
03870                 # such as action=raw much more expensive than they need to be.
03871                 # This will hopefully cover most cases.
03872                 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
03873                         array( &$this, 'replaceGrammarInNamespace' ), $talk );
03874                 return str_replace( ' ', '_', $talk );
03875         }
03876 
03881         function replaceGrammarInNamespace( $m ) {
03882                 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
03883         }
03884 
03889         static function getCaseMaps() {
03890                 static $wikiUpperChars, $wikiLowerChars;
03891                 if ( isset( $wikiUpperChars ) ) {
03892                         return array( $wikiUpperChars, $wikiLowerChars );
03893                 }
03894 
03895                 wfProfileIn( __METHOD__ );
03896                 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
03897                 if ( $arr === false ) {
03898                         throw new MWException(
03899                                 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
03900                 }
03901                 $wikiUpperChars = $arr['wikiUpperChars'];
03902                 $wikiLowerChars = $arr['wikiLowerChars'];
03903                 wfProfileOut( __METHOD__ );
03904                 return array( $wikiUpperChars, $wikiLowerChars );
03905         }
03906 
03918         public function formatExpiry( $expiry, $format = true ) {
03919                 static $infinity, $infinityMsg;
03920                 if ( $infinity === null ) {
03921                         $infinityMsg = wfMessage( 'infiniteblock' );
03922                         $infinity = wfGetDB( DB_SLAVE )->getInfinity();
03923                 }
03924 
03925                 if ( $expiry == '' || $expiry == $infinity ) {
03926                         return $format === true
03927                                 ? $infinityMsg
03928                                 : $infinity;
03929                 } else {
03930                         return $format === true
03931                                 ? $this->timeanddate( $expiry, /* User preference timezone */ true )
03932                                 : wfTimestamp( $format, $expiry );
03933                 }
03934         }
03935 
03946         function formatTimePeriod( $seconds, $format = array() ) {
03947                 if ( !is_array( $format ) ) {
03948                         $format = array( 'avoid' => $format ); // For backwards compatibility
03949                 }
03950                 if ( !isset( $format['avoid'] ) ) {
03951                         $format['avoid'] = false;
03952                 }
03953                 if ( !isset( $format['noabbrevs' ] ) ) {
03954                         $format['noabbrevs'] = false;
03955                 }
03956                 $secondsMsg = wfMessage(
03957                         $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this );
03958                 $minutesMsg = wfMessage(
03959                         $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this );
03960                 $hoursMsg = wfMessage(
03961                         $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this );
03962                 $daysMsg = wfMessage(
03963                         $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this );
03964 
03965                 if ( round( $seconds * 10 ) < 100 ) {
03966                         $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) );
03967                         $s = $secondsMsg->params( $s )->text();
03968                 } elseif ( round( $seconds ) < 60 ) {
03969                         $s = $this->formatNum( round( $seconds ) );
03970                         $s = $secondsMsg->params( $s )->text();
03971                 } elseif ( round( $seconds ) < 3600 ) {
03972                         $minutes = floor( $seconds / 60 );
03973                         $secondsPart = round( fmod( $seconds, 60 ) );
03974                         if ( $secondsPart == 60 ) {
03975                                 $secondsPart = 0;
03976                                 $minutes++;
03977                         }
03978                         $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text();
03979                         $s .= ' ';
03980                         $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
03981                 } elseif ( round( $seconds ) <= 2 * 86400 ) {
03982                         $hours = floor( $seconds / 3600 );
03983                         $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
03984                         $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
03985                         if ( $secondsPart == 60 ) {
03986                                 $secondsPart = 0;
03987                                 $minutes++;
03988                         }
03989                         if ( $minutes == 60 ) {
03990                                 $minutes = 0;
03991                                 $hours++;
03992                         }
03993                         $s = $hoursMsg->params( $this->formatNum( $hours ) )->text();
03994                         $s .= ' ';
03995                         $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
03996                         if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) {
03997                                 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text();
03998                         }
03999                 } else {
04000                         $days = floor( $seconds / 86400 );
04001                         if ( $format['avoid'] === 'avoidminutes' ) {
04002                                 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
04003                                 if ( $hours == 24 ) {
04004                                         $hours = 0;
04005                                         $days++;
04006                                 }
04007                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04008                                 $s .= ' ';
04009                                 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
04010                         } elseif ( $format['avoid'] === 'avoidseconds' ) {
04011                                 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
04012                                 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
04013                                 if ( $minutes == 60 ) {
04014                                         $minutes = 0;
04015                                         $hours++;
04016                                 }
04017                                 if ( $hours == 24 ) {
04018                                         $hours = 0;
04019                                         $days++;
04020                                 }
04021                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04022                                 $s .= ' ';
04023                                 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text();
04024                                 $s .= ' ';
04025                                 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text();
04026                         } else {
04027                                 $s = $daysMsg->params( $this->formatNum( $days ) )->text();
04028                                 $s .= ' ';
04029                                 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format );
04030                         }
04031                 }
04032                 return $s;
04033         }
04034 
04045         function formatBitrate( $bps ) {
04046                 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" );
04047         }
04048 
04055         function formatComputingNumbers( $size, $boundary, $messageKey ) {
04056                 if ( $size <= 0 ) {
04057                         return str_replace( '$1', $this->formatNum( $size ),
04058                                 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) )
04059                         );
04060                 }
04061                 $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' );
04062                 $index = 0;
04063 
04064                 $maxIndex = count( $sizes ) - 1;
04065                 while ( $size >= $boundary && $index < $maxIndex ) {
04066                         $index++;
04067                         $size /= $boundary;
04068                 }
04069 
04070                 // For small sizes no decimal places necessary
04071                 $round = 0;
04072                 if ( $index > 1 ) {
04073                         // For MB and bigger two decimal places are smarter
04074                         $round = 2;
04075                 }
04076                 $msg = str_replace( '$1', $sizes[$index], $messageKey );
04077 
04078                 $size = round( $size, $round );
04079                 $text = $this->getMessageFromDB( $msg );
04080                 return str_replace( '$1', $this->formatNum( $size ), $text );
04081         }
04082 
04093         function formatSize( $size ) {
04094                 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" );
04095         }
04096 
04106         function specialList( $page, $details, $oppositedm = true ) {
04107                 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) .
04108                         $this->getDirMark();
04109                 $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) .
04110                         wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : '';
04111                 return $page . $details;
04112         }
04113 
04124         public function viewPrevNext( Title $title, $offset, $limit, array $query = array(), $atend = false ) {
04125                 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
04126 
04127                 # Make 'previous' link
04128                 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04129                 if ( $offset > 0 ) {
04130                         $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit,
04131                                 $query, $prev, 'prevn-title', 'mw-prevlink' );
04132                 } else {
04133                         $plink = htmlspecialchars( $prev );
04134                 }
04135 
04136                 # Make 'next' link
04137                 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04138                 if ( $atend ) {
04139                         $nlink = htmlspecialchars( $next );
04140                 } else {
04141                         $nlink = $this->numLink( $title, $offset + $limit, $limit,
04142                                 $query, $next, 'prevn-title', 'mw-nextlink' );
04143                 }
04144 
04145                 # Make links to set number of items per page
04146                 $numLinks = array();
04147                 foreach ( array( 20, 50, 100, 250, 500 ) as $num ) {
04148                         $numLinks[] = $this->numLink( $title, $offset, $num,
04149                                 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' );
04150                 }
04151 
04152                 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title
04153                         )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped();
04154         }
04155 
04168         private function numLink( Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class ) {
04169                 $query = array( 'limit' => $limit, 'offset' => $offset ) + $query;
04170                 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )->numParams( $limit )->text();
04171                 return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ),
04172                         'title' => $tooltip, 'class' => $class ), $link );
04173         }
04174 
04180         public function getConvRuleTitle() {
04181                 return $this->mConverter->getConvRuleTitle();
04182         }
04183 
04189         public function getCompiledPluralRules() {
04190                 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' );
04191                 $fallbacks = Language::getFallbacksFor( $this->mCode );
04192                 if ( !$pluralRules ) {
04193                         foreach ( $fallbacks as $fallbackCode ) {
04194                                 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' );
04195                                 if ( $pluralRules ) {
04196                                         break;
04197                                 }
04198                         }
04199                 }
04200                 return $pluralRules;
04201         }
04202 
04208         public function getPluralRules() {
04209                 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' );
04210                 $fallbacks = Language::getFallbacksFor( $this->mCode );
04211                 if ( !$pluralRules ) {
04212                         foreach ( $fallbacks as $fallbackCode ) {
04213                                 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' );
04214                                 if ( $pluralRules ) {
04215                                         break;
04216                                 }
04217                         }
04218                 }
04219                 return $pluralRules;
04220         }
04221 
04227         private function getPluralForm( $number ) {
04228                 $pluralRules = $this->getCompiledPluralRules();
04229                 $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules );
04230                 return $form;
04231         }
04232 
04233 }