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