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