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