MediaWiki
REL1_23
|
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 if ( function_exists( 'mb_strtoupper' ) ) { 00034 mb_internal_encoding( 'UTF-8' ); 00035 } 00036 00042 class FakeConverter { 00046 public $mLang; 00047 function __construct( $langobj ) { $this->mLang = $langobj; } 00048 function autoConvert( $text, $variant = false ) { return $text; } 00049 function autoConvertToAllVariants( $text ) { return array( $this->mLang->getCode() => $text ); } 00050 function convert( $t ) { return $t; } 00051 function convertTo( $text, $variant ) { return $text; } 00052 function convertTitle( $t ) { return $t->getPrefixedText(); } 00053 function convertNamespace( $ns ) { return $this->mLang->getFormattedNsText( $ns ); } 00054 function getVariants() { return array( $this->mLang->getCode() ); } 00055 function getVariantFallbacks( $variant ) { return $this->mLang->getCode(); } 00056 function getPreferredVariant() { return $this->mLang->getCode(); } 00057 function getDefaultVariant() { return $this->mLang->getCode(); } 00058 function getURLVariant() { return ''; } 00059 function getConvRuleTitle() { return false; } 00060 function findVariantLink( &$l, &$n, $ignoreOtherCond = false ) { } 00061 function getExtraHashOptions() { return ''; } 00062 function getParsedTitle() { return ''; } 00063 function markNoConversion( $text, $noParse = false ) { return $text; } 00064 function convertCategoryKey( $key ) { return $key; } 00066 function armourMath( $text ) { return $text; } 00067 function validateVariant( $variant = null ) { return $variant === $this->mLang->getCode() ? $variant : null; } 00068 function translate( $text, $variant ) { return $text; } 00069 } 00070 00075 class Language { 00076 00080 public $mConverter; 00081 00082 public $mVariants, $mCode, $mLoaded = false; 00083 public $mMagicExtensions = array(), $mMagicHookDone = false; 00084 private $mHtmlCode = null, $mParentLanguage = false; 00085 00086 public $dateFormatStrings = array(); 00087 public $mExtendedSpecialPageAliases; 00088 00089 protected $namespaceNames, $mNamespaceIds, $namespaceAliases; 00090 00094 public $transformData = array(); 00095 00099 static public $dataCache; 00100 00101 static public $mLangObjCache = array(); 00102 00103 static public $mWeekdayMsgs = array( 00104 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 00105 'friday', 'saturday' 00106 ); 00107 00108 static public $mWeekdayAbbrevMsgs = array( 00109 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' 00110 ); 00111 00112 static public $mMonthMsgs = array( 00113 'january', 'february', 'march', 'april', 'may_long', 'june', 00114 'july', 'august', 'september', 'october', 'november', 00115 'december' 00116 ); 00117 static public $mMonthGenMsgs = array( 00118 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', 00119 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 00120 'december-gen' 00121 ); 00122 static public $mMonthAbbrevMsgs = array( 00123 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 00124 'sep', 'oct', 'nov', 'dec' 00125 ); 00126 00127 static public $mIranianCalendarMonthMsgs = array( 00128 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3', 00129 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6', 00130 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9', 00131 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12' 00132 ); 00133 00134 static public $mHebrewCalendarMonthMsgs = array( 00135 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3', 00136 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6', 00137 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9', 00138 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12', 00139 'hebrew-calendar-m6a', 'hebrew-calendar-m6b' 00140 ); 00141 00142 static public $mHebrewCalendarMonthGenMsgs = array( 00143 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen', 00144 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen', 00145 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen', 00146 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen', 00147 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen' 00148 ); 00149 00150 static public $mHijriCalendarMonthMsgs = array( 00151 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3', 00152 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6', 00153 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9', 00154 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12' 00155 ); 00156 00161 static public $durationIntervals = array( 00162 'millennia' => 31556952000, 00163 'centuries' => 3155695200, 00164 'decades' => 315569520, 00165 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 ) 00166 'weeks' => 604800, 00167 'days' => 86400, 00168 'hours' => 3600, 00169 'minutes' => 60, 00170 'seconds' => 1, 00171 ); 00172 00179 static private $fallbackLanguageCache = array(); 00180 00186 static function factory( $code ) { 00187 global $wgDummyLanguageCodes, $wgLangObjCacheSize; 00188 00189 if ( isset( $wgDummyLanguageCodes[$code] ) ) { 00190 $code = $wgDummyLanguageCodes[$code]; 00191 } 00192 00193 // get the language object to process 00194 $langObj = isset( self::$mLangObjCache[$code] ) 00195 ? self::$mLangObjCache[$code] 00196 : self::newFromCode( $code ); 00197 00198 // merge the language object in to get it up front in the cache 00199 self::$mLangObjCache = array_merge( array( $code => $langObj ), self::$mLangObjCache ); 00200 // get rid of the oldest ones in case we have an overflow 00201 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true ); 00202 00203 return $langObj; 00204 } 00205 00212 protected static function newFromCode( $code ) { 00213 // Protect against path traversal below 00214 if ( !Language::isValidCode( $code ) 00215 || strcspn( $code, ":/\\\000" ) !== strlen( $code ) 00216 ) { 00217 throw new MWException( "Invalid language code \"$code\"" ); 00218 } 00219 00220 if ( !Language::isValidBuiltInCode( $code ) ) { 00221 // It's not possible to customise this code with class files, so 00222 // just return a Language object. This is to support uselang= hacks. 00223 $lang = new Language; 00224 $lang->setCode( $code ); 00225 return $lang; 00226 } 00227 00228 // Check if there is a language class for the code 00229 $class = self::classFromCode( $code ); 00230 self::preloadLanguageClass( $class ); 00231 if ( class_exists( $class ) ) { 00232 $lang = new $class; 00233 return $lang; 00234 } 00235 00236 // Keep trying the fallback list until we find an existing class 00237 $fallbacks = Language::getFallbacksFor( $code ); 00238 foreach ( $fallbacks as $fallbackCode ) { 00239 if ( !Language::isValidBuiltInCode( $fallbackCode ) ) { 00240 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" ); 00241 } 00242 00243 $class = self::classFromCode( $fallbackCode ); 00244 self::preloadLanguageClass( $class ); 00245 if ( class_exists( $class ) ) { 00246 $lang = Language::newFromCode( $fallbackCode ); 00247 $lang->setCode( $code ); 00248 return $lang; 00249 } 00250 } 00251 00252 throw new MWException( "Invalid fallback sequence for language '$code'" ); 00253 } 00254 00263 public static function isSupportedLanguage( $code ) { 00264 return self::isValidBuiltInCode( $code ) 00265 && ( is_readable( self::getMessagesFileName( $code ) ) 00266 || is_readable( self::getJsonMessagesFileName( $code ) ) 00267 ); 00268 } 00269 00285 public static function isWellFormedLanguageTag( $code, $lenient = false ) { 00286 $alpha = '[a-z]'; 00287 $digit = '[0-9]'; 00288 $alphanum = '[a-z0-9]'; 00289 $x = 'x'; # private use singleton 00290 $singleton = '[a-wy-z]'; # other singleton 00291 $s = $lenient ? '[-_]' : '-'; 00292 00293 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}"; 00294 $script = "$alpha{4}"; # ISO 15924 00295 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49 00296 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})"; 00297 $extension = "$singleton(?:$s$alphanum{2,8})+"; 00298 $privateUse = "$x(?:$s$alphanum{1,8})+"; 00299 00300 # Define certain grandfathered codes, since otherwise the regex is pretty useless. 00301 # Since these are limited, this is safe even later changes to the registry -- 00302 # the only oddity is that it might change the type of the tag, and thus 00303 # the results from the capturing groups. 00304 # http://www.iana.org/assignments/language-subtag-registry 00305 00306 $grandfathered = "en{$s}GB{$s}oed" 00307 . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)" 00308 . "|no{$s}(?:bok|nyn)" 00309 . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)" 00310 . "|zh{$s}min{$s}nan"; 00311 00312 $variantList = "$variant(?:$s$variant)*"; 00313 $extensionList = "$extension(?:$s$extension)*"; 00314 00315 $langtag = "(?:($language)" 00316 . "(?:$s$script)?" 00317 . "(?:$s$region)?" 00318 . "(?:$s$variantList)?" 00319 . "(?:$s$extensionList)?" 00320 . "(?:$s$privateUse)?)"; 00321 00322 # The final breakdown, with capturing groups for each of these components 00323 # The variants, extensions, grandfathered, and private-use may have interior '-' 00324 00325 $root = "^(?:$langtag|$privateUse|$grandfathered)$"; 00326 00327 return (bool)preg_match( "/$root/", strtolower( $code ) ); 00328 } 00329 00339 public static function isValidCode( $code ) { 00340 static $cache = array(); 00341 if ( isset( $cache[$code] ) ) { 00342 return $cache[$code]; 00343 } 00344 // People think language codes are html safe, so enforce it. 00345 // Ideally we should only allow a-zA-Z0-9- 00346 // but, .+ and other chars are often used for {{int:}} hacks 00347 // see bugs 37564, 37587, 36938 00348 $cache[$code] = 00349 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) 00350 && !preg_match( Title::getTitleInvalidRegex(), $code ); 00351 00352 return $cache[$code]; 00353 } 00354 00365 public static function isValidBuiltInCode( $code ) { 00366 00367 if ( !is_string( $code ) ) { 00368 if ( is_object( $code ) ) { 00369 $addmsg = " of class " . get_class( $code ); 00370 } else { 00371 $addmsg = ''; 00372 } 00373 $type = gettype( $code ); 00374 throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" ); 00375 } 00376 00377 return (bool)preg_match( '/^[a-z0-9-]{2,}$/i', $code ); 00378 } 00379 00388 public static function isKnownLanguageTag( $tag ) { 00389 static $coreLanguageNames; 00390 00391 // Quick escape for invalid input to avoid exceptions down the line 00392 // when code tries to process tags which are not valid at all. 00393 if ( !self::isValidBuiltInCode( $tag ) ) { 00394 return false; 00395 } 00396 00397 if ( $coreLanguageNames === null ) { 00398 global $IP; 00399 include "$IP/languages/Names.php"; 00400 } 00401 00402 if ( isset( $coreLanguageNames[$tag] ) 00403 || self::fetchLanguageName( $tag, $tag ) !== '' 00404 ) { 00405 return true; 00406 } 00407 00408 return false; 00409 } 00410 00415 public static function classFromCode( $code ) { 00416 if ( $code == 'en' ) { 00417 return 'Language'; 00418 } else { 00419 return 'Language' . str_replace( '-', '_', ucfirst( $code ) ); 00420 } 00421 } 00422 00428 public static function preloadLanguageClass( $class ) { 00429 global $IP; 00430 00431 if ( $class === 'Language' ) { 00432 return; 00433 } 00434 00435 if ( file_exists( "$IP/languages/classes/$class.php" ) ) { 00436 include_once "$IP/languages/classes/$class.php"; 00437 } 00438 } 00439 00445 public static function getLocalisationCache() { 00446 if ( is_null( self::$dataCache ) ) { 00447 global $wgLocalisationCacheConf; 00448 $class = $wgLocalisationCacheConf['class']; 00449 self::$dataCache = new $class( $wgLocalisationCacheConf ); 00450 } 00451 return self::$dataCache; 00452 } 00453 00454 function __construct() { 00455 $this->mConverter = new FakeConverter( $this ); 00456 // Set the code to the name of the descendant 00457 if ( get_class( $this ) == 'Language' ) { 00458 $this->mCode = 'en'; 00459 } else { 00460 $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) ); 00461 } 00462 self::getLocalisationCache(); 00463 } 00464 00468 function __destruct() { 00469 foreach ( $this as $name => $value ) { 00470 unset( $this->$name ); 00471 } 00472 } 00473 00478 function initContLang() { } 00479 00485 function getFallbackLanguageCode() { 00486 wfDeprecated( __METHOD__, '1.19' ); 00487 return self::getFallbackFor( $this->mCode ); 00488 } 00489 00494 function getFallbackLanguages() { 00495 return self::getFallbacksFor( $this->mCode ); 00496 } 00497 00502 function getBookstoreList() { 00503 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' ); 00504 } 00505 00512 public function getNamespaces() { 00513 if ( is_null( $this->namespaceNames ) ) { 00514 global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces; 00515 00516 $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' ); 00517 $validNamespaces = MWNamespace::getCanonicalNamespaces(); 00518 00519 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces; 00520 00521 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace; 00522 if ( $wgMetaNamespaceTalk ) { 00523 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk; 00524 } else { 00525 $talk = $this->namespaceNames[NS_PROJECT_TALK]; 00526 $this->namespaceNames[NS_PROJECT_TALK] = 00527 $this->fixVariableInNamespace( $talk ); 00528 } 00529 00530 # Sometimes a language will be localised but not actually exist on this wiki. 00531 foreach ( $this->namespaceNames as $key => $text ) { 00532 if ( !isset( $validNamespaces[$key] ) ) { 00533 unset( $this->namespaceNames[$key] ); 00534 } 00535 } 00536 00537 # The above mixing may leave namespaces out of canonical order. 00538 # Re-order by namespace ID number... 00539 ksort( $this->namespaceNames ); 00540 00541 wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) ); 00542 } 00543 return $this->namespaceNames; 00544 } 00545 00550 public function setNamespaces( array $namespaces ) { 00551 $this->namespaceNames = $namespaces; 00552 $this->mNamespaceIds = null; 00553 } 00554 00558 public function resetNamespaces() { 00559 $this->namespaceNames = null; 00560 $this->mNamespaceIds = null; 00561 $this->namespaceAliases = null; 00562 } 00563 00572 function getFormattedNamespaces() { 00573 $ns = $this->getNamespaces(); 00574 foreach ( $ns as $k => $v ) { 00575 $ns[$k] = strtr( $v, '_', ' ' ); 00576 } 00577 return $ns; 00578 } 00579 00590 function getNsText( $index ) { 00591 $ns = $this->getNamespaces(); 00592 return isset( $ns[$index] ) ? $ns[$index] : false; 00593 } 00594 00608 function getFormattedNsText( $index ) { 00609 $ns = $this->getNsText( $index ); 00610 return strtr( $ns, '_', ' ' ); 00611 } 00612 00621 function getGenderNsText( $index, $gender ) { 00622 global $wgExtraGenderNamespaces; 00623 00624 $ns = $wgExtraGenderNamespaces + self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00625 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index ); 00626 } 00627 00634 function needsGenderDistinction() { 00635 global $wgExtraGenderNamespaces, $wgExtraNamespaces; 00636 if ( count( $wgExtraGenderNamespaces ) > 0 ) { 00637 // $wgExtraGenderNamespaces overrides everything 00638 return true; 00639 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) { 00641 // $wgExtraNamespaces overrides any gender aliases specified in i18n files 00642 return false; 00643 } else { 00644 // Check what is in i18n files 00645 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00646 return count( $aliases ) > 0; 00647 } 00648 } 00649 00658 function getLocalNsIndex( $text ) { 00659 $lctext = $this->lc( $text ); 00660 $ids = $this->getNamespaceIds(); 00661 return isset( $ids[$lctext] ) ? $ids[$lctext] : false; 00662 } 00663 00667 function getNamespaceAliases() { 00668 if ( is_null( $this->namespaceAliases ) ) { 00669 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' ); 00670 if ( !$aliases ) { 00671 $aliases = array(); 00672 } else { 00673 foreach ( $aliases as $name => $index ) { 00674 if ( $index === NS_PROJECT_TALK ) { 00675 unset( $aliases[$name] ); 00676 $name = $this->fixVariableInNamespace( $name ); 00677 $aliases[$name] = $index; 00678 } 00679 } 00680 } 00681 00682 global $wgExtraGenderNamespaces; 00683 $genders = $wgExtraGenderNamespaces + (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00684 foreach ( $genders as $index => $forms ) { 00685 foreach ( $forms as $alias ) { 00686 $aliases[$alias] = $index; 00687 } 00688 } 00689 00690 # Also add converted namespace names as aliases, to avoid confusion. 00691 $convertedNames = array(); 00692 foreach ( $this->getVariants() as $variant ) { 00693 if ( $variant === $this->mCode ) { 00694 continue; 00695 } 00696 foreach ( $this->getNamespaces() as $ns => $_ ) { 00697 $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns; 00698 } 00699 } 00700 00701 $this->namespaceAliases = $aliases + $convertedNames; 00702 } 00703 return $this->namespaceAliases; 00704 } 00705 00709 function getNamespaceIds() { 00710 if ( is_null( $this->mNamespaceIds ) ) { 00711 global $wgNamespaceAliases; 00712 # Put namespace names and aliases into a hashtable. 00713 # If this is too slow, then we should arrange it so that it is done 00714 # before caching. The catch is that at pre-cache time, the above 00715 # class-specific fixup hasn't been done. 00716 $this->mNamespaceIds = array(); 00717 foreach ( $this->getNamespaces() as $index => $name ) { 00718 $this->mNamespaceIds[$this->lc( $name )] = $index; 00719 } 00720 foreach ( $this->getNamespaceAliases() as $name => $index ) { 00721 $this->mNamespaceIds[$this->lc( $name )] = $index; 00722 } 00723 if ( $wgNamespaceAliases ) { 00724 foreach ( $wgNamespaceAliases as $name => $index ) { 00725 $this->mNamespaceIds[$this->lc( $name )] = $index; 00726 } 00727 } 00728 } 00729 return $this->mNamespaceIds; 00730 } 00731 00739 function getNsIndex( $text ) { 00740 $lctext = $this->lc( $text ); 00741 $ns = MWNamespace::getCanonicalIndex( $lctext ); 00742 if ( $ns !== null ) { 00743 return $ns; 00744 } 00745 $ids = $this->getNamespaceIds(); 00746 return isset( $ids[$lctext] ) ? $ids[$lctext] : false; 00747 } 00748 00756 function getVariantname( $code, $usemsg = true ) { 00757 $msg = "variantname-$code"; 00758 if ( $usemsg && wfMessage( $msg )->exists() ) { 00759 return $this->getMessageFromDB( $msg ); 00760 } 00761 $name = self::fetchLanguageName( $code ); 00762 if ( $name ) { 00763 return $name; # if it's defined as a language name, show that 00764 } else { 00765 # otherwise, output the language code 00766 return $code; 00767 } 00768 } 00769 00774 function specialPage( $name ) { 00775 $aliases = $this->getSpecialPageAliases(); 00776 if ( isset( $aliases[$name][0] ) ) { 00777 $name = $aliases[$name][0]; 00778 } 00779 return $this->getNsText( NS_SPECIAL ) . ':' . $name; 00780 } 00781 00785 function getDatePreferences() { 00786 return self::$dataCache->getItem( $this->mCode, 'datePreferences' ); 00787 } 00788 00792 function getDateFormats() { 00793 return self::$dataCache->getItem( $this->mCode, 'dateFormats' ); 00794 } 00795 00799 function getDefaultDateFormat() { 00800 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' ); 00801 if ( $df === 'dmy or mdy' ) { 00802 global $wgAmericanDates; 00803 return $wgAmericanDates ? 'mdy' : 'dmy'; 00804 } else { 00805 return $df; 00806 } 00807 } 00808 00812 function getDatePreferenceMigrationMap() { 00813 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' ); 00814 } 00815 00820 function getImageFile( $image ) { 00821 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image ); 00822 } 00823 00827 function getExtraUserToggles() { 00828 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' ); 00829 } 00830 00835 function getUserToggle( $tog ) { 00836 return $this->getMessageFromDB( "tog-$tog" ); 00837 } 00838 00849 public static function getLanguageNames( $customisedOnly = false ) { 00850 return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' ); 00851 } 00852 00862 public static function getTranslatedLanguageNames( $code ) { 00863 return self::fetchLanguageNames( $code, 'all' ); 00864 } 00865 00877 public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) { 00878 global $wgExtraLanguageNames; 00879 static $coreLanguageNames; 00880 00881 if ( $coreLanguageNames === null ) { 00882 global $IP; 00883 include "$IP/languages/Names.php"; 00884 } 00885 00886 // If passed an invalid language code to use, fallback to en 00887 if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) { 00888 $inLanguage = 'en'; 00889 } 00890 00891 $names = array(); 00892 00893 if ( $inLanguage ) { 00894 # TODO: also include when $inLanguage is null, when this code is more efficient 00895 wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) ); 00896 } 00897 00898 $mwNames = $wgExtraLanguageNames + $coreLanguageNames; 00899 foreach ( $mwNames as $mwCode => $mwName ) { 00900 # - Prefer own MediaWiki native name when not using the hook 00901 # - For other names just add if not added through the hook 00902 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) { 00903 $names[$mwCode] = $mwName; 00904 } 00905 } 00906 00907 if ( $include === 'all' ) { 00908 return $names; 00909 } 00910 00911 $returnMw = array(); 00912 $coreCodes = array_keys( $mwNames ); 00913 foreach ( $coreCodes as $coreCode ) { 00914 $returnMw[$coreCode] = $names[$coreCode]; 00915 } 00916 00917 if ( $include === 'mwfile' ) { 00918 $namesMwFile = array(); 00919 # We do this using a foreach over the codes instead of a directory 00920 # loop so that messages files in extensions will work correctly. 00921 foreach ( $returnMw as $code => $value ) { 00922 if ( is_readable( self::getMessagesFileName( $code ) ) 00923 || is_readable( self::getJsonMessagesFileName( $code ) ) 00924 ) { 00925 $namesMwFile[$code] = $names[$code]; 00926 } 00927 } 00928 00929 return $namesMwFile; 00930 } 00931 00932 # 'mw' option; default if it's not one of the other two options (all/mwfile) 00933 return $returnMw; 00934 } 00935 00943 public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) { 00944 $code = strtolower( $code ); 00945 $array = self::fetchLanguageNames( $inLanguage, $include ); 00946 return !array_key_exists( $code, $array ) ? '' : $array[$code]; 00947 } 00948 00955 function getMessageFromDB( $msg ) { 00956 return wfMessage( $msg )->inLanguage( $this )->text(); 00957 } 00958 00966 function getLanguageName( $code ) { 00967 return self::fetchLanguageName( $code ); 00968 } 00969 00974 function getMonthName( $key ) { 00975 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] ); 00976 } 00977 00981 function getMonthNamesArray() { 00982 $monthNames = array( '' ); 00983 for ( $i = 1; $i < 13; $i++ ) { 00984 $monthNames[] = $this->getMonthName( $i ); 00985 } 00986 return $monthNames; 00987 } 00988 00993 function getMonthNameGen( $key ) { 00994 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] ); 00995 } 00996 01001 function getMonthAbbreviation( $key ) { 01002 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] ); 01003 } 01004 01008 function getMonthAbbreviationsArray() { 01009 $monthNames = array( '' ); 01010 for ( $i = 1; $i < 13; $i++ ) { 01011 $monthNames[] = $this->getMonthAbbreviation( $i ); 01012 } 01013 return $monthNames; 01014 } 01015 01020 function getWeekdayName( $key ) { 01021 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] ); 01022 } 01023 01028 function getWeekdayAbbreviation( $key ) { 01029 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] ); 01030 } 01031 01036 function getIranianCalendarMonthName( $key ) { 01037 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] ); 01038 } 01039 01044 function getHebrewCalendarMonthName( $key ) { 01045 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] ); 01046 } 01047 01052 function getHebrewCalendarMonthNameGen( $key ) { 01053 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] ); 01054 } 01055 01060 function getHijriCalendarMonthName( $key ) { 01061 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] ); 01062 } 01063 01129 function sprintfDate( $format, $ts, DateTimeZone $zone = null ) { 01130 $s = ''; 01131 $raw = false; 01132 $roman = false; 01133 $hebrewNum = false; 01134 $dateTimeObj = false; 01135 $rawToggle = false; 01136 $iranian = false; 01137 $hebrew = false; 01138 $hijri = false; 01139 $thai = false; 01140 $minguo = false; 01141 $tenno = false; 01142 01143 if ( strlen( $ts ) !== 14 ) { 01144 throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" ); 01145 } 01146 01147 if ( !ctype_digit( $ts ) ) { 01148 throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" ); 01149 } 01150 01151 for ( $p = 0; $p < strlen( $format ); $p++ ) { 01152 $num = false; 01153 $code = $format[$p]; 01154 if ( $code == 'x' && $p < strlen( $format ) - 1 ) { 01155 $code .= $format[++$p]; 01156 } 01157 01158 if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' || $code == 'xo' || $code == 'xt' ) && $p < strlen( $format ) - 1 ) { 01159 $code .= $format[++$p]; 01160 } 01161 01162 switch ( $code ) { 01163 case 'xx': 01164 $s .= 'x'; 01165 break; 01166 case 'xn': 01167 $raw = true; 01168 break; 01169 case 'xN': 01170 $rawToggle = !$rawToggle; 01171 break; 01172 case 'xr': 01173 $roman = true; 01174 break; 01175 case 'xh': 01176 $hebrewNum = true; 01177 break; 01178 case 'xg': 01179 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) ); 01180 break; 01181 case 'xjx': 01182 if ( !$hebrew ) { 01183 $hebrew = self::tsToHebrew( $ts ); 01184 } 01185 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] ); 01186 break; 01187 case 'd': 01188 $num = substr( $ts, 6, 2 ); 01189 break; 01190 case 'D': 01191 if ( !$dateTimeObj ) { 01192 $dateTimeObj = DateTime::createFromFormat( 01193 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01194 ); 01195 } 01196 $s .= $this->getWeekdayAbbreviation( $dateTimeObj->format( 'w' ) + 1 ); 01197 break; 01198 case 'j': 01199 $num = intval( substr( $ts, 6, 2 ) ); 01200 break; 01201 case 'xij': 01202 if ( !$iranian ) { 01203 $iranian = self::tsToIranian( $ts ); 01204 } 01205 $num = $iranian[2]; 01206 break; 01207 case 'xmj': 01208 if ( !$hijri ) { 01209 $hijri = self::tsToHijri( $ts ); 01210 } 01211 $num = $hijri[2]; 01212 break; 01213 case 'xjj': 01214 if ( !$hebrew ) { 01215 $hebrew = self::tsToHebrew( $ts ); 01216 } 01217 $num = $hebrew[2]; 01218 break; 01219 case 'l': 01220 if ( !$dateTimeObj ) { 01221 $dateTimeObj = DateTime::createFromFormat( 01222 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01223 ); 01224 } 01225 $s .= $this->getWeekdayName( $dateTimeObj->format( 'w' ) + 1 ); 01226 break; 01227 case 'F': 01228 $s .= $this->getMonthName( substr( $ts, 4, 2 ) ); 01229 break; 01230 case 'xiF': 01231 if ( !$iranian ) { 01232 $iranian = self::tsToIranian( $ts ); 01233 } 01234 $s .= $this->getIranianCalendarMonthName( $iranian[1] ); 01235 break; 01236 case 'xmF': 01237 if ( !$hijri ) { 01238 $hijri = self::tsToHijri( $ts ); 01239 } 01240 $s .= $this->getHijriCalendarMonthName( $hijri[1] ); 01241 break; 01242 case 'xjF': 01243 if ( !$hebrew ) { 01244 $hebrew = self::tsToHebrew( $ts ); 01245 } 01246 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] ); 01247 break; 01248 case 'm': 01249 $num = substr( $ts, 4, 2 ); 01250 break; 01251 case 'M': 01252 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) ); 01253 break; 01254 case 'n': 01255 $num = intval( substr( $ts, 4, 2 ) ); 01256 break; 01257 case 'xin': 01258 if ( !$iranian ) { 01259 $iranian = self::tsToIranian( $ts ); 01260 } 01261 $num = $iranian[1]; 01262 break; 01263 case 'xmn': 01264 if ( !$hijri ) { 01265 $hijri = self::tsToHijri ( $ts ); 01266 } 01267 $num = $hijri[1]; 01268 break; 01269 case 'xjn': 01270 if ( !$hebrew ) { 01271 $hebrew = self::tsToHebrew( $ts ); 01272 } 01273 $num = $hebrew[1]; 01274 break; 01275 case 'xjt': 01276 if ( !$hebrew ) { 01277 $hebrew = self::tsToHebrew( $ts ); 01278 } 01279 $num = $hebrew[3]; 01280 break; 01281 case 'Y': 01282 $num = substr( $ts, 0, 4 ); 01283 break; 01284 case 'xiY': 01285 if ( !$iranian ) { 01286 $iranian = self::tsToIranian( $ts ); 01287 } 01288 $num = $iranian[0]; 01289 break; 01290 case 'xmY': 01291 if ( !$hijri ) { 01292 $hijri = self::tsToHijri( $ts ); 01293 } 01294 $num = $hijri[0]; 01295 break; 01296 case 'xjY': 01297 if ( !$hebrew ) { 01298 $hebrew = self::tsToHebrew( $ts ); 01299 } 01300 $num = $hebrew[0]; 01301 break; 01302 case 'xkY': 01303 if ( !$thai ) { 01304 $thai = self::tsToYear( $ts, 'thai' ); 01305 } 01306 $num = $thai[0]; 01307 break; 01308 case 'xoY': 01309 if ( !$minguo ) { 01310 $minguo = self::tsToYear( $ts, 'minguo' ); 01311 } 01312 $num = $minguo[0]; 01313 break; 01314 case 'xtY': 01315 if ( !$tenno ) { 01316 $tenno = self::tsToYear( $ts, 'tenno' ); 01317 } 01318 $num = $tenno[0]; 01319 break; 01320 case 'y': 01321 $num = substr( $ts, 2, 2 ); 01322 break; 01323 case 'xiy': 01324 if ( !$iranian ) { 01325 $iranian = self::tsToIranian( $ts ); 01326 } 01327 $num = substr( $iranian[0], -2 ); 01328 break; 01329 case 'a': 01330 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm'; 01331 break; 01332 case 'A': 01333 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM'; 01334 break; 01335 case 'g': 01336 $h = substr( $ts, 8, 2 ); 01337 $num = $h % 12 ? $h % 12 : 12; 01338 break; 01339 case 'G': 01340 $num = intval( substr( $ts, 8, 2 ) ); 01341 break; 01342 case 'h': 01343 $h = substr( $ts, 8, 2 ); 01344 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 ); 01345 break; 01346 case 'H': 01347 $num = substr( $ts, 8, 2 ); 01348 break; 01349 case 'i': 01350 $num = substr( $ts, 10, 2 ); 01351 break; 01352 case 's': 01353 $num = substr( $ts, 12, 2 ); 01354 break; 01355 case 'c': 01356 case 'r': 01357 case 'e': 01358 case 'O': 01359 case 'P': 01360 case 'T': 01361 // Pass through string from $dateTimeObj->format() 01362 if ( !$dateTimeObj ) { 01363 $dateTimeObj = DateTime::createFromFormat( 01364 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01365 ); 01366 } 01367 $s .= $dateTimeObj->format( $code ); 01368 break; 01369 case 'w': 01370 case 'N': 01371 case 'z': 01372 case 'W': 01373 case 't': 01374 case 'L': 01375 case 'o': 01376 case 'U': 01377 case 'I': 01378 case 'Z': 01379 // Pass through number from $dateTimeObj->format() 01380 if ( !$dateTimeObj ) { 01381 $dateTimeObj = DateTime::createFromFormat( 01382 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01383 ); 01384 } 01385 $num = $dateTimeObj->format( $code ); 01386 break; 01387 case '\\': 01388 # Backslash escaping 01389 if ( $p < strlen( $format ) - 1 ) { 01390 $s .= $format[++$p]; 01391 } else { 01392 $s .= '\\'; 01393 } 01394 break; 01395 case '"': 01396 # Quoted literal 01397 if ( $p < strlen( $format ) - 1 ) { 01398 $endQuote = strpos( $format, '"', $p + 1 ); 01399 if ( $endQuote === false ) { 01400 # No terminating quote, assume literal " 01401 $s .= '"'; 01402 } else { 01403 $s .= substr( $format, $p + 1, $endQuote - $p - 1 ); 01404 $p = $endQuote; 01405 } 01406 } else { 01407 # Quote at end of string, assume literal " 01408 $s .= '"'; 01409 } 01410 break; 01411 default: 01412 $s .= $format[$p]; 01413 } 01414 if ( $num !== false ) { 01415 if ( $rawToggle || $raw ) { 01416 $s .= $num; 01417 $raw = false; 01418 } elseif ( $roman ) { 01419 $s .= Language::romanNumeral( $num ); 01420 $roman = false; 01421 } elseif ( $hebrewNum ) { 01422 $s .= self::hebrewNumeral( $num ); 01423 $hebrewNum = false; 01424 } else { 01425 $s .= $this->formatNum( $num, true ); 01426 } 01427 } 01428 } 01429 return $s; 01430 } 01431 01432 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); 01433 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ); 01434 01447 private static function tsToIranian( $ts ) { 01448 $gy = substr( $ts, 0, 4 ) -1600; 01449 $gm = substr( $ts, 4, 2 ) -1; 01450 $gd = substr( $ts, 6, 2 ) -1; 01451 01452 # Days passed from the beginning (including leap years) 01453 $gDayNo = 365 * $gy 01454 + floor( ( $gy + 3 ) / 4 ) 01455 - floor( ( $gy + 99 ) / 100 ) 01456 + floor( ( $gy + 399 ) / 400 ); 01457 01458 // Add days of the past months of this year 01459 for ( $i = 0; $i < $gm; $i++ ) { 01460 $gDayNo += self::$GREG_DAYS[$i]; 01461 } 01462 01463 // Leap years 01464 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) { 01465 $gDayNo++; 01466 } 01467 01468 // Days passed in current month 01469 $gDayNo += (int)$gd; 01470 01471 $jDayNo = $gDayNo - 79; 01472 01473 $jNp = floor( $jDayNo / 12053 ); 01474 $jDayNo %= 12053; 01475 01476 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 ); 01477 $jDayNo %= 1461; 01478 01479 if ( $jDayNo >= 366 ) { 01480 $jy += floor( ( $jDayNo - 1 ) / 365 ); 01481 $jDayNo = floor( ( $jDayNo - 1 ) % 365 ); 01482 } 01483 01484 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) { 01485 $jDayNo -= self::$IRANIAN_DAYS[$i]; 01486 } 01487 01488 $jm = $i + 1; 01489 $jd = $jDayNo + 1; 01490 01491 return array( $jy, $jm, $jd ); 01492 } 01493 01505 private static function tsToHijri( $ts ) { 01506 $year = substr( $ts, 0, 4 ); 01507 $month = substr( $ts, 4, 2 ); 01508 $day = substr( $ts, 6, 2 ); 01509 01510 $zyr = $year; 01511 $zd = $day; 01512 $zm = $month; 01513 $zy = $zyr; 01514 01515 if ( 01516 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) || 01517 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) ) 01518 ) { 01519 $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) + 01520 (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) - 01521 (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) + 01522 $zd - 32075; 01523 } else { 01524 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) + 01525 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777; 01526 } 01527 01528 $zl = $zjd -1948440 + 10632; 01529 $zn = (int)( ( $zl - 1 ) / 10631 ); 01530 $zl = $zl - 10631 * $zn + 354; 01531 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) + ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) ); 01532 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) - ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29; 01533 $zm = (int)( ( 24 * $zl ) / 709 ); 01534 $zd = $zl - (int)( ( 709 * $zm ) / 24 ); 01535 $zy = 30 * $zn + $zj - 30; 01536 01537 return array( $zy, $zm, $zd ); 01538 } 01539 01555 private static function tsToHebrew( $ts ) { 01556 # Parse date 01557 $year = substr( $ts, 0, 4 ); 01558 $month = substr( $ts, 4, 2 ); 01559 $day = substr( $ts, 6, 2 ); 01560 01561 # Calculate Hebrew year 01562 $hebrewYear = $year + 3760; 01563 01564 # Month number when September = 1, August = 12 01565 $month += 4; 01566 if ( $month > 12 ) { 01567 # Next year 01568 $month -= 12; 01569 $year++; 01570 $hebrewYear++; 01571 } 01572 01573 # Calculate day of year from 1 September 01574 $dayOfYear = $day; 01575 for ( $i = 1; $i < $month; $i++ ) { 01576 if ( $i == 6 ) { 01577 # February 01578 $dayOfYear += 28; 01579 # Check if the year is leap 01580 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) { 01581 $dayOfYear++; 01582 } 01583 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) { 01584 $dayOfYear += 30; 01585 } else { 01586 $dayOfYear += 31; 01587 } 01588 } 01589 01590 # Calculate the start of the Hebrew year 01591 $start = self::hebrewYearStart( $hebrewYear ); 01592 01593 # Calculate next year's start 01594 if ( $dayOfYear <= $start ) { 01595 # Day is before the start of the year - it is the previous year 01596 # Next year's start 01597 $nextStart = $start; 01598 # Previous year 01599 $year--; 01600 $hebrewYear--; 01601 # Add days since previous year's 1 September 01602 $dayOfYear += 365; 01603 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) { 01604 # Leap year 01605 $dayOfYear++; 01606 } 01607 # Start of the new (previous) year 01608 $start = self::hebrewYearStart( $hebrewYear ); 01609 } else { 01610 # Next year's start 01611 $nextStart = self::hebrewYearStart( $hebrewYear + 1 ); 01612 } 01613 01614 # Calculate Hebrew day of year 01615 $hebrewDayOfYear = $dayOfYear - $start; 01616 01617 # Difference between year's days 01618 $diff = $nextStart - $start; 01619 # Add 12 (or 13 for leap years) days to ignore the difference between 01620 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the 01621 # difference is only about the year type 01622 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) { 01623 $diff += 13; 01624 } else { 01625 $diff += 12; 01626 } 01627 01628 # Check the year pattern, and is leap year 01629 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year 01630 # This is mod 30, to work on both leap years (which add 30 days of Adar I) 01631 # and non-leap years 01632 $yearPattern = $diff % 30; 01633 # Check if leap year 01634 $isLeap = $diff >= 30; 01635 01636 # Calculate day in the month from number of day in the Hebrew year 01637 # Don't check Adar - if the day is not in Adar, we will stop before; 01638 # if it is in Adar, we will use it to check if it is Adar I or Adar II 01639 $hebrewDay = $hebrewDayOfYear; 01640 $hebrewMonth = 1; 01641 $days = 0; 01642 while ( $hebrewMonth <= 12 ) { 01643 # Calculate days in this month 01644 if ( $isLeap && $hebrewMonth == 6 ) { 01645 # Adar in a leap year 01646 if ( $isLeap ) { 01647 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days 01648 $days = 30; 01649 if ( $hebrewDay <= $days ) { 01650 # Day in Adar I 01651 $hebrewMonth = 13; 01652 } else { 01653 # Subtract the days of Adar I 01654 $hebrewDay -= $days; 01655 # Try Adar II 01656 $days = 29; 01657 if ( $hebrewDay <= $days ) { 01658 # Day in Adar II 01659 $hebrewMonth = 14; 01660 } 01661 } 01662 } 01663 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) { 01664 # Cheshvan in a complete year (otherwise as the rule below) 01665 $days = 30; 01666 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) { 01667 # Kislev in an incomplete year (otherwise as the rule below) 01668 $days = 29; 01669 } else { 01670 # Odd months have 30 days, even have 29 01671 $days = 30 - ( $hebrewMonth - 1 ) % 2; 01672 } 01673 if ( $hebrewDay <= $days ) { 01674 # In the current month 01675 break; 01676 } else { 01677 # Subtract the days of the current month 01678 $hebrewDay -= $days; 01679 # Try in the next month 01680 $hebrewMonth++; 01681 } 01682 } 01683 01684 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days ); 01685 } 01686 01696 private static function hebrewYearStart( $year ) { 01697 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 ); 01698 $b = intval( ( $year - 1 ) % 4 ); 01699 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 ); 01700 if ( $m < 0 ) { 01701 $m--; 01702 } 01703 $Mar = intval( $m ); 01704 if ( $m < 0 ) { 01705 $m++; 01706 } 01707 $m -= $Mar; 01708 01709 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 ); 01710 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) { 01711 $Mar++; 01712 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) { 01713 $Mar += 2; 01714 } elseif ( $c == 2 || $c == 4 || $c == 6 ) { 01715 $Mar++; 01716 } 01717 01718 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24; 01719 return $Mar; 01720 } 01721 01734 private static function tsToYear( $ts, $cName ) { 01735 $gy = substr( $ts, 0, 4 ); 01736 $gm = substr( $ts, 4, 2 ); 01737 $gd = substr( $ts, 6, 2 ); 01738 01739 if ( !strcmp( $cName, 'thai' ) ) { 01740 # Thai solar dates 01741 # Add 543 years to the Gregorian calendar 01742 # Months and days are identical 01743 $gy_offset = $gy + 543; 01744 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) { 01745 # Minguo dates 01746 # Deduct 1911 years from the Gregorian calendar 01747 # Months and days are identical 01748 $gy_offset = $gy - 1911; 01749 } elseif ( !strcmp( $cName, 'tenno' ) ) { 01750 # Nengō dates up to Meiji period 01751 # Deduct years from the Gregorian calendar 01752 # depending on the nengo periods 01753 # Months and days are identical 01754 if ( ( $gy < 1912 ) || ( ( $gy == 1912 ) && ( $gm < 7 ) ) || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) ) ) { 01755 # Meiji period 01756 $gy_gannen = $gy - 1868 + 1; 01757 $gy_offset = $gy_gannen; 01758 if ( $gy_gannen == 1 ) { 01759 $gy_offset = '元'; 01760 } 01761 $gy_offset = '明治' . $gy_offset; 01762 } elseif ( 01763 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) || 01764 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) || 01765 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) || 01766 ( ( $gy == 1926 ) && ( $gm < 12 ) ) || 01767 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) ) 01768 ) { 01769 # Taishō period 01770 $gy_gannen = $gy - 1912 + 1; 01771 $gy_offset = $gy_gannen; 01772 if ( $gy_gannen == 1 ) { 01773 $gy_offset = '元'; 01774 } 01775 $gy_offset = '大正' . $gy_offset; 01776 } elseif ( 01777 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) || 01778 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) || 01779 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) ) 01780 ) { 01781 # Shōwa period 01782 $gy_gannen = $gy - 1926 + 1; 01783 $gy_offset = $gy_gannen; 01784 if ( $gy_gannen == 1 ) { 01785 $gy_offset = '元'; 01786 } 01787 $gy_offset = '昭和' . $gy_offset; 01788 } else { 01789 # Heisei period 01790 $gy_gannen = $gy - 1989 + 1; 01791 $gy_offset = $gy_gannen; 01792 if ( $gy_gannen == 1 ) { 01793 $gy_offset = '元'; 01794 } 01795 $gy_offset = '平成' . $gy_offset; 01796 } 01797 } else { 01798 $gy_offset = $gy; 01799 } 01800 01801 return array( $gy_offset, $gm, $gd ); 01802 } 01803 01811 static function romanNumeral( $num ) { 01812 static $table = array( 01813 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ), 01814 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ), 01815 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ), 01816 array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM', 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ) 01817 ); 01818 01819 $num = intval( $num ); 01820 if ( $num > 10000 || $num <= 0 ) { 01821 return $num; 01822 } 01823 01824 $s = ''; 01825 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) { 01826 if ( $num >= $pow10 ) { 01827 $s .= $table[$i][(int)floor( $num / $pow10 )]; 01828 } 01829 $num = $num % $pow10; 01830 } 01831 return $s; 01832 } 01833 01841 static function hebrewNumeral( $num ) { 01842 static $table = array( 01843 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ), 01844 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ), 01845 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ), 01846 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ) 01847 ); 01848 01849 $num = intval( $num ); 01850 if ( $num > 9999 || $num <= 0 ) { 01851 return $num; 01852 } 01853 01854 $s = ''; 01855 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) { 01856 if ( $num >= $pow10 ) { 01857 if ( $num == 15 || $num == 16 ) { 01858 $s .= $table[0][9] . $table[0][$num - 9]; 01859 $num = 0; 01860 } else { 01861 $s .= $table[$i][intval( ( $num / $pow10 ) )]; 01862 if ( $pow10 == 1000 ) { 01863 $s .= "'"; 01864 } 01865 } 01866 } 01867 $num = $num % $pow10; 01868 } 01869 if ( strlen( $s ) == 2 ) { 01870 $str = $s . "'"; 01871 } else { 01872 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"'; 01873 $str .= substr( $s, strlen( $s ) - 2, 2 ); 01874 } 01875 $start = substr( $str, 0, strlen( $str ) - 2 ); 01876 $end = substr( $str, strlen( $str ) - 2 ); 01877 switch ( $end ) { 01878 case 'כ': 01879 $str = $start . 'ך'; 01880 break; 01881 case 'מ': 01882 $str = $start . 'ם'; 01883 break; 01884 case 'נ': 01885 $str = $start . 'ן'; 01886 break; 01887 case 'פ': 01888 $str = $start . 'ף'; 01889 break; 01890 case 'צ': 01891 $str = $start . 'ץ'; 01892 break; 01893 } 01894 return $str; 01895 } 01896 01905 function userAdjust( $ts, $tz = false ) { 01906 global $wgUser, $wgLocalTZoffset; 01907 01908 if ( $tz === false ) { 01909 $tz = $wgUser->getOption( 'timecorrection' ); 01910 } 01911 01912 $data = explode( '|', $tz, 3 ); 01913 01914 if ( $data[0] == 'ZoneInfo' ) { 01915 wfSuppressWarnings(); 01916 $userTZ = timezone_open( $data[2] ); 01917 wfRestoreWarnings(); 01918 if ( $userTZ !== false ) { 01919 $date = date_create( $ts, timezone_open( 'UTC' ) ); 01920 date_timezone_set( $date, $userTZ ); 01921 $date = date_format( $date, 'YmdHis' ); 01922 return $date; 01923 } 01924 # Unrecognized timezone, default to 'Offset' with the stored offset. 01925 $data[0] = 'Offset'; 01926 } 01927 01928 $minDiff = 0; 01929 if ( $data[0] == 'System' || $tz == '' ) { 01930 # Global offset in minutes. 01931 if ( isset( $wgLocalTZoffset ) ) { 01932 $minDiff = $wgLocalTZoffset; 01933 } 01934 } elseif ( $data[0] == 'Offset' ) { 01935 $minDiff = intval( $data[1] ); 01936 } else { 01937 $data = explode( ':', $tz ); 01938 if ( count( $data ) == 2 ) { 01939 $data[0] = intval( $data[0] ); 01940 $data[1] = intval( $data[1] ); 01941 $minDiff = abs( $data[0] ) * 60 + $data[1]; 01942 if ( $data[0] < 0 ) { 01943 $minDiff = -$minDiff; 01944 } 01945 } else { 01946 $minDiff = intval( $data[0] ) * 60; 01947 } 01948 } 01949 01950 # No difference ? Return time unchanged 01951 if ( 0 == $minDiff ) { 01952 return $ts; 01953 } 01954 01955 wfSuppressWarnings(); // E_STRICT system time bitching 01956 # Generate an adjusted date; take advantage of the fact that mktime 01957 # will normalize out-of-range values so we don't have to split $minDiff 01958 # into hours and minutes. 01959 $t = mktime( ( 01960 (int)substr( $ts, 8, 2 ) ), # Hours 01961 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes 01962 (int)substr( $ts, 12, 2 ), # Seconds 01963 (int)substr( $ts, 4, 2 ), # Month 01964 (int)substr( $ts, 6, 2 ), # Day 01965 (int)substr( $ts, 0, 4 ) ); # Year 01966 01967 $date = date( 'YmdHis', $t ); 01968 wfRestoreWarnings(); 01969 01970 return $date; 01971 } 01972 01990 function dateFormat( $usePrefs = true ) { 01991 global $wgUser; 01992 01993 if ( is_bool( $usePrefs ) ) { 01994 if ( $usePrefs ) { 01995 $datePreference = $wgUser->getDatePreference(); 01996 } else { 01997 $datePreference = (string)User::getDefaultOption( 'date' ); 01998 } 01999 } else { 02000 $datePreference = (string)$usePrefs; 02001 } 02002 02003 // return int 02004 if ( $datePreference == '' ) { 02005 return 'default'; 02006 } 02007 02008 return $datePreference; 02009 } 02010 02020 function getDateFormatString( $type, $pref ) { 02021 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) { 02022 if ( $pref == 'default' ) { 02023 $pref = $this->getDefaultDateFormat(); 02024 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02025 } else { 02026 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02027 02028 if ( $type === 'pretty' && $df === null ) { 02029 $df = $this->getDateFormatString( 'date', $pref ); 02030 } 02031 02032 if ( $df === null ) { 02033 $pref = $this->getDefaultDateFormat(); 02034 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02035 } 02036 } 02037 $this->dateFormatStrings[$type][$pref] = $df; 02038 } 02039 return $this->dateFormatStrings[$type][$pref]; 02040 } 02041 02052 function date( $ts, $adj = false, $format = true, $timecorrection = false ) { 02053 $ts = wfTimestamp( TS_MW, $ts ); 02054 if ( $adj ) { 02055 $ts = $this->userAdjust( $ts, $timecorrection ); 02056 } 02057 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) ); 02058 return $this->sprintfDate( $df, $ts ); 02059 } 02060 02071 function time( $ts, $adj = false, $format = true, $timecorrection = false ) { 02072 $ts = wfTimestamp( TS_MW, $ts ); 02073 if ( $adj ) { 02074 $ts = $this->userAdjust( $ts, $timecorrection ); 02075 } 02076 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) ); 02077 return $this->sprintfDate( $df, $ts ); 02078 } 02079 02091 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) { 02092 $ts = wfTimestamp( TS_MW, $ts ); 02093 if ( $adj ) { 02094 $ts = $this->userAdjust( $ts, $timecorrection ); 02095 } 02096 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) ); 02097 return $this->sprintfDate( $df, $ts ); 02098 } 02099 02110 public function formatDuration( $seconds, array $chosenIntervals = array() ) { 02111 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals ); 02112 02113 $segments = array(); 02114 02115 foreach ( $intervals as $intervalName => $intervalValue ) { 02116 // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks, 02117 // duration-years, duration-decades, duration-centuries, duration-millennia 02118 $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue ); 02119 $segments[] = $message->inLanguage( $this )->escaped(); 02120 } 02121 02122 return $this->listToText( $segments ); 02123 } 02124 02136 public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) { 02137 if ( empty( $chosenIntervals ) ) { 02138 $chosenIntervals = array( 'millennia', 'centuries', 'decades', 'years', 'days', 'hours', 'minutes', 'seconds' ); 02139 } 02140 02141 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) ); 02142 $sortedNames = array_keys( $intervals ); 02143 $smallestInterval = array_pop( $sortedNames ); 02144 02145 $segments = array(); 02146 02147 foreach ( $intervals as $name => $length ) { 02148 $value = floor( $seconds / $length ); 02149 02150 if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) { 02151 $seconds -= $value * $length; 02152 $segments[$name] = $value; 02153 } 02154 } 02155 02156 return $segments; 02157 } 02158 02178 private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) { 02179 $ts = wfTimestamp( TS_MW, $ts ); 02180 $options += array( 'timecorrection' => true, 'format' => true ); 02181 if ( $options['timecorrection'] !== false ) { 02182 if ( $options['timecorrection'] === true ) { 02183 $offset = $user->getOption( 'timecorrection' ); 02184 } else { 02185 $offset = $options['timecorrection']; 02186 } 02187 $ts = $this->userAdjust( $ts, $offset ); 02188 } 02189 if ( $options['format'] === true ) { 02190 $format = $user->getDatePreference(); 02191 } else { 02192 $format = $options['format']; 02193 } 02194 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) ); 02195 return $this->sprintfDate( $df, $ts ); 02196 } 02197 02217 public function userDate( $ts, User $user, array $options = array() ) { 02218 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options ); 02219 } 02220 02240 public function userTime( $ts, User $user, array $options = array() ) { 02241 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options ); 02242 } 02243 02263 public function userTimeAndDate( $ts, User $user, array $options = array() ) { 02264 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options ); 02265 } 02266 02282 public function getHumanTimestamp( MWTimestamp $ts, MWTimestamp $relativeTo, User $user ) { 02283 $diff = $ts->diff( $relativeTo ); 02284 $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) - (int)$relativeTo->timestamp->format( 'w' ) ); 02285 $days = $diff->days ?: (int)$diffDay; 02286 if ( $diff->invert || $days > 5 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' ) ) { 02287 // Timestamps are in different years: use full timestamp 02288 // Also do full timestamp for future dates 02292 $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' ); 02293 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ); 02294 } elseif ( $days > 5 ) { 02295 // Timestamps are in same year, but more than 5 days ago: show day and month only. 02296 $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' ); 02297 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ); 02298 } elseif ( $days > 1 ) { 02299 // Timestamp within the past week: show the day of the week and time 02300 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02301 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )]; 02302 // Messages: 02303 // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at 02304 $ts = wfMessage( "$weekday-at" ) 02305 ->inLanguage( $this ) 02306 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02307 ->text(); 02308 } elseif ( $days == 1 ) { 02309 // Timestamp was yesterday: say 'yesterday' and the time. 02310 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02311 $ts = wfMessage( 'yesterday-at' ) 02312 ->inLanguage( $this ) 02313 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02314 ->text(); 02315 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) { 02316 // Timestamp was today, but more than 90 minutes ago: say 'today' and the time. 02317 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02318 $ts = wfMessage( 'today-at' ) 02319 ->inLanguage( $this ) 02320 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02321 ->text(); 02322 02323 // From here on in, the timestamp was soon enough ago so that we can simply say 02324 // XX units ago, e.g., "2 hours ago" or "5 minutes ago" 02325 } elseif ( $diff->h == 1 ) { 02326 // Less than 90 minutes, but more than an hour ago. 02327 $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text(); 02328 } elseif ( $diff->i >= 1 ) { 02329 // A few minutes ago. 02330 $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text(); 02331 } elseif ( $diff->s >= 30 ) { 02332 // Less than a minute, but more than 30 sec ago. 02333 $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text(); 02334 } else { 02335 // Less than 30 seconds ago. 02336 $ts = wfMessage( 'just-now' )->text(); 02337 } 02338 02339 return $ts; 02340 } 02341 02346 function getMessage( $key ) { 02347 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key ); 02348 } 02349 02353 function getAllMessages() { 02354 return self::$dataCache->getItem( $this->mCode, 'messages' ); 02355 } 02356 02363 function iconv( $in, $out, $string ) { 02364 # This is a wrapper for iconv in all languages except esperanto, 02365 # which does some nasty x-conversions beforehand 02366 02367 # Even with //IGNORE iconv can whine about illegal characters in 02368 # *input* string. We just ignore those too. 02369 # REF: http://bugs.php.net/bug.php?id=37166 02370 # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885 02371 wfSuppressWarnings(); 02372 $text = iconv( $in, $out . '//IGNORE', $string ); 02373 wfRestoreWarnings(); 02374 return $text; 02375 } 02376 02377 // callback functions for uc(), lc(), ucwords(), ucwordbreaks() 02378 02383 function ucwordbreaksCallbackAscii( $matches ) { 02384 return $this->ucfirst( $matches[1] ); 02385 } 02386 02391 function ucwordbreaksCallbackMB( $matches ) { 02392 return mb_strtoupper( $matches[0] ); 02393 } 02394 02399 function ucCallback( $matches ) { 02400 list( $wikiUpperChars ) = self::getCaseMaps(); 02401 return strtr( $matches[1], $wikiUpperChars ); 02402 } 02403 02408 function lcCallback( $matches ) { 02409 list( , $wikiLowerChars ) = self::getCaseMaps(); 02410 return strtr( $matches[1], $wikiLowerChars ); 02411 } 02412 02417 function ucwordsCallbackMB( $matches ) { 02418 return mb_strtoupper( $matches[0] ); 02419 } 02420 02425 function ucwordsCallbackWiki( $matches ) { 02426 list( $wikiUpperChars ) = self::getCaseMaps(); 02427 return strtr( $matches[0], $wikiUpperChars ); 02428 } 02429 02437 function ucfirst( $str ) { 02438 $o = ord( $str ); 02439 if ( $o < 96 ) { // if already uppercase... 02440 return $str; 02441 } elseif ( $o < 128 ) { 02442 return ucfirst( $str ); // use PHP's ucfirst() 02443 } else { 02444 // fall back to more complex logic in case of multibyte strings 02445 return $this->uc( $str, true ); 02446 } 02447 } 02448 02457 function uc( $str, $first = false ) { 02458 if ( function_exists( 'mb_strtoupper' ) ) { 02459 if ( $first ) { 02460 if ( $this->isMultibyte( $str ) ) { 02461 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 ); 02462 } else { 02463 return ucfirst( $str ); 02464 } 02465 } else { 02466 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str ); 02467 } 02468 } else { 02469 if ( $this->isMultibyte( $str ) ) { 02470 $x = $first ? '^' : ''; 02471 return preg_replace_callback( 02472 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/", 02473 array( $this, 'ucCallback' ), 02474 $str 02475 ); 02476 } else { 02477 return $first ? ucfirst( $str ) : strtoupper( $str ); 02478 } 02479 } 02480 } 02481 02486 function lcfirst( $str ) { 02487 $o = ord( $str ); 02488 if ( !$o ) { 02489 return strval( $str ); 02490 } elseif ( $o >= 128 ) { 02491 return $this->lc( $str, true ); 02492 } elseif ( $o > 96 ) { 02493 return $str; 02494 } else { 02495 $str[0] = strtolower( $str[0] ); 02496 return $str; 02497 } 02498 } 02499 02505 function lc( $str, $first = false ) { 02506 if ( function_exists( 'mb_strtolower' ) ) { 02507 if ( $first ) { 02508 if ( $this->isMultibyte( $str ) ) { 02509 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 ); 02510 } else { 02511 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ); 02512 } 02513 } else { 02514 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str ); 02515 } 02516 } else { 02517 if ( $this->isMultibyte( $str ) ) { 02518 $x = $first ? '^' : ''; 02519 return preg_replace_callback( 02520 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/", 02521 array( $this, 'lcCallback' ), 02522 $str 02523 ); 02524 } else { 02525 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str ); 02526 } 02527 } 02528 } 02529 02534 function isMultibyte( $str ) { 02535 return (bool)preg_match( '/[\x80-\xff]/', $str ); 02536 } 02537 02542 function ucwords( $str ) { 02543 if ( $this->isMultibyte( $str ) ) { 02544 $str = $this->lc( $str ); 02545 02546 // regexp to find first letter in each word (i.e. after each space) 02547 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/"; 02548 02549 // function to use to capitalize a single char 02550 if ( function_exists( 'mb_strtoupper' ) ) { 02551 return preg_replace_callback( 02552 $replaceRegexp, 02553 array( $this, 'ucwordsCallbackMB' ), 02554 $str 02555 ); 02556 } else { 02557 return preg_replace_callback( 02558 $replaceRegexp, 02559 array( $this, 'ucwordsCallbackWiki' ), 02560 $str 02561 ); 02562 } 02563 } else { 02564 return ucwords( strtolower( $str ) ); 02565 } 02566 } 02567 02574 function ucwordbreaks( $str ) { 02575 if ( $this->isMultibyte( $str ) ) { 02576 $str = $this->lc( $str ); 02577 02578 // since \b doesn't work for UTF-8, we explicitely define word break chars 02579 $breaks = "[ \-\(\)\}\{\.,\?!]"; 02580 02581 // find first letter after word break 02582 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/"; 02583 02584 if ( function_exists( 'mb_strtoupper' ) ) { 02585 return preg_replace_callback( 02586 $replaceRegexp, 02587 array( $this, 'ucwordbreaksCallbackMB' ), 02588 $str 02589 ); 02590 } else { 02591 return preg_replace_callback( 02592 $replaceRegexp, 02593 array( $this, 'ucwordsCallbackWiki' ), 02594 $str 02595 ); 02596 } 02597 } else { 02598 return preg_replace_callback( 02599 '/\b([\w\x80-\xff]+)\b/', 02600 array( $this, 'ucwordbreaksCallbackAscii' ), 02601 $str 02602 ); 02603 } 02604 } 02605 02621 function caseFold( $s ) { 02622 return $this->uc( $s ); 02623 } 02624 02629 function checkTitleEncoding( $s ) { 02630 if ( is_array( $s ) ) { 02631 throw new MWException( 'Given array to checkTitleEncoding.' ); 02632 } 02633 if ( StringUtils::isUtf8( $s ) ) { 02634 return $s; 02635 } 02636 02637 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s ); 02638 } 02639 02643 function fallback8bitEncoding() { 02644 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' ); 02645 } 02646 02655 function hasWordBreaks() { 02656 return true; 02657 } 02658 02666 function segmentByWord( $string ) { 02667 return $string; 02668 } 02669 02677 function normalizeForSearch( $string ) { 02678 return self::convertDoubleWidth( $string ); 02679 } 02680 02689 protected static function convertDoubleWidth( $string ) { 02690 static $full = null; 02691 static $half = null; 02692 02693 if ( $full === null ) { 02694 $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 02695 $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 02696 $full = str_split( $fullWidth, 3 ); 02697 $half = str_split( $halfWidth ); 02698 } 02699 02700 $string = str_replace( $full, $half, $string ); 02701 return $string; 02702 } 02703 02709 protected static function insertSpace( $string, $pattern ) { 02710 $string = preg_replace( $pattern, " $1 ", $string ); 02711 $string = preg_replace( '/ +/', ' ', $string ); 02712 return $string; 02713 } 02714 02719 function convertForSearchResult( $termsArray ) { 02720 # some languages, e.g. Chinese, need to do a conversion 02721 # in order for search results to be displayed correctly 02722 return $termsArray; 02723 } 02724 02731 function firstChar( $s ) { 02732 $matches = array(); 02733 preg_match( 02734 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' . 02735 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', 02736 $s, 02737 $matches 02738 ); 02739 02740 if ( isset( $matches[1] ) ) { 02741 if ( strlen( $matches[1] ) != 3 ) { 02742 return $matches[1]; 02743 } 02744 02745 // Break down Hangul syllables to grab the first jamo 02746 $code = utf8ToCodepoint( $matches[1] ); 02747 if ( $code < 0xac00 || 0xd7a4 <= $code ) { 02748 return $matches[1]; 02749 } elseif ( $code < 0xb098 ) { 02750 return "\xe3\x84\xb1"; 02751 } elseif ( $code < 0xb2e4 ) { 02752 return "\xe3\x84\xb4"; 02753 } elseif ( $code < 0xb77c ) { 02754 return "\xe3\x84\xb7"; 02755 } elseif ( $code < 0xb9c8 ) { 02756 return "\xe3\x84\xb9"; 02757 } elseif ( $code < 0xbc14 ) { 02758 return "\xe3\x85\x81"; 02759 } elseif ( $code < 0xc0ac ) { 02760 return "\xe3\x85\x82"; 02761 } elseif ( $code < 0xc544 ) { 02762 return "\xe3\x85\x85"; 02763 } elseif ( $code < 0xc790 ) { 02764 return "\xe3\x85\x87"; 02765 } elseif ( $code < 0xcc28 ) { 02766 return "\xe3\x85\x88"; 02767 } elseif ( $code < 0xce74 ) { 02768 return "\xe3\x85\x8a"; 02769 } elseif ( $code < 0xd0c0 ) { 02770 return "\xe3\x85\x8b"; 02771 } elseif ( $code < 0xd30c ) { 02772 return "\xe3\x85\x8c"; 02773 } elseif ( $code < 0xd558 ) { 02774 return "\xe3\x85\x8d"; 02775 } else { 02776 return "\xe3\x85\x8e"; 02777 } 02778 } else { 02779 return ''; 02780 } 02781 } 02782 02783 function initEncoding() { 02784 # Some languages may have an alternate char encoding option 02785 # (Esperanto X-coding, Japanese furigana conversion, etc) 02786 # If this language is used as the primary content language, 02787 # an override to the defaults can be set here on startup. 02788 } 02789 02794 function recodeForEdit( $s ) { 02795 # For some languages we'll want to explicitly specify 02796 # which characters make it into the edit box raw 02797 # or are converted in some way or another. 02798 global $wgEditEncoding; 02799 if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) { 02800 return $s; 02801 } else { 02802 return $this->iconv( 'UTF-8', $wgEditEncoding, $s ); 02803 } 02804 } 02805 02810 function recodeInput( $s ) { 02811 # Take the previous into account. 02812 global $wgEditEncoding; 02813 if ( $wgEditEncoding != '' ) { 02814 $enc = $wgEditEncoding; 02815 } else { 02816 $enc = 'UTF-8'; 02817 } 02818 if ( $enc == 'UTF-8' ) { 02819 return $s; 02820 } else { 02821 return $this->iconv( $enc, 'UTF-8', $s ); 02822 } 02823 } 02824 02836 function normalize( $s ) { 02837 global $wgAllUnicodeFixes; 02838 $s = UtfNormal::cleanUp( $s ); 02839 if ( $wgAllUnicodeFixes ) { 02840 $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s ); 02841 $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s ); 02842 } 02843 02844 return $s; 02845 } 02846 02861 function transformUsingPairFile( $file, $string ) { 02862 if ( !isset( $this->transformData[$file] ) ) { 02863 $data = wfGetPrecompiledData( $file ); 02864 if ( $data === false ) { 02865 throw new MWException( __METHOD__ . ": The transformation file $file is missing" ); 02866 } 02867 $this->transformData[$file] = new ReplacementArray( $data ); 02868 } 02869 return $this->transformData[$file]->replace( $string ); 02870 } 02871 02877 function isRTL() { 02878 return self::$dataCache->getItem( $this->mCode, 'rtl' ); 02879 } 02880 02885 function getDir() { 02886 return $this->isRTL() ? 'rtl' : 'ltr'; 02887 } 02888 02897 function alignStart() { 02898 return $this->isRTL() ? 'right' : 'left'; 02899 } 02900 02909 function alignEnd() { 02910 return $this->isRTL() ? 'left' : 'right'; 02911 } 02912 02924 function getDirMarkEntity( $opposite = false ) { 02925 if ( $opposite ) { 02926 return $this->isRTL() ? '‎' : '‏'; 02927 } 02928 return $this->isRTL() ? '‏' : '‎'; 02929 } 02930 02941 function getDirMark( $opposite = false ) { 02942 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM 02943 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM 02944 if ( $opposite ) { 02945 return $this->isRTL() ? $lrm : $rlm; 02946 } 02947 return $this->isRTL() ? $rlm : $lrm; 02948 } 02949 02953 function capitalizeAllNouns() { 02954 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' ); 02955 } 02956 02963 function getArrow( $direction = 'forwards' ) { 02964 switch ( $direction ) { 02965 case 'forwards': 02966 return $this->isRTL() ? '←' : '→'; 02967 case 'backwards': 02968 return $this->isRTL() ? '→' : '←'; 02969 case 'left': 02970 return '←'; 02971 case 'right': 02972 return '→'; 02973 case 'up': 02974 return '↑'; 02975 case 'down': 02976 return '↓'; 02977 } 02978 } 02979 02985 function linkPrefixExtension() { 02986 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' ); 02987 } 02988 02993 function getMagicWords() { 02994 return self::$dataCache->getItem( $this->mCode, 'magicWords' ); 02995 } 02996 03000 protected function doMagicHook() { 03001 if ( $this->mMagicHookDone ) { 03002 return; 03003 } 03004 $this->mMagicHookDone = true; 03005 wfProfileIn( 'LanguageGetMagic' ); 03006 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) ); 03007 wfProfileOut( 'LanguageGetMagic' ); 03008 } 03009 03015 function getMagic( $mw ) { 03016 // Saves a function call 03017 if ( ! $this->mMagicHookDone ) { 03018 $this->doMagicHook(); 03019 } 03020 03021 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) { 03022 $rawEntry = $this->mMagicExtensions[$mw->mId]; 03023 } else { 03024 $rawEntry = self::$dataCache->getSubitem( 03025 $this->mCode, 'magicWords', $mw->mId ); 03026 } 03027 03028 if ( !is_array( $rawEntry ) ) { 03029 error_log( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" ); 03030 } else { 03031 $mw->mCaseSensitive = $rawEntry[0]; 03032 $mw->mSynonyms = array_slice( $rawEntry, 1 ); 03033 } 03034 } 03035 03041 function addMagicWordsByLang( $newWords ) { 03042 $fallbackChain = $this->getFallbackLanguages(); 03043 $fallbackChain = array_reverse( $fallbackChain ); 03044 foreach ( $fallbackChain as $code ) { 03045 if ( isset( $newWords[$code] ) ) { 03046 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions; 03047 } 03048 } 03049 } 03050 03055 function getSpecialPageAliases() { 03056 // Cache aliases because it may be slow to load them 03057 if ( is_null( $this->mExtendedSpecialPageAliases ) ) { 03058 // Initialise array 03059 $this->mExtendedSpecialPageAliases = 03060 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' ); 03061 wfRunHooks( 'LanguageGetSpecialPageAliases', 03062 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) ); 03063 } 03064 03065 return $this->mExtendedSpecialPageAliases; 03066 } 03067 03074 function emphasize( $text ) { 03075 return "<em>$text</em>"; 03076 } 03077 03101 public function formatNum( $number, $nocommafy = false ) { 03102 global $wgTranslateNumerals; 03103 if ( !$nocommafy ) { 03104 $number = $this->commafy( $number ); 03105 $s = $this->separatorTransformTable(); 03106 if ( $s ) { 03107 $number = strtr( $number, $s ); 03108 } 03109 } 03110 03111 if ( $wgTranslateNumerals ) { 03112 $s = $this->digitTransformTable(); 03113 if ( $s ) { 03114 $number = strtr( $number, $s ); 03115 } 03116 } 03117 03118 return $number; 03119 } 03120 03129 public function formatNumNoSeparators( $number ) { 03130 return $this->formatNum( $number, true ); 03131 } 03132 03137 function parseFormattedNumber( $number ) { 03138 $s = $this->digitTransformTable(); 03139 if ( $s ) { 03140 $number = strtr( $number, array_flip( $s ) ); 03141 } 03142 03143 $s = $this->separatorTransformTable(); 03144 if ( $s ) { 03145 $number = strtr( $number, array_flip( $s ) ); 03146 } 03147 03148 $number = strtr( $number, array( ',' => '' ) ); 03149 return $number; 03150 } 03151 03158 function commafy( $number ) { 03159 $digitGroupingPattern = $this->digitGroupingPattern(); 03160 if ( $number === null ) { 03161 return ''; 03162 } 03163 03164 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) { 03165 // default grouping is at thousands, use the same for ###,###,### pattern too. 03166 return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) ); 03167 } else { 03168 // Ref: http://cldr.unicode.org/translation/number-patterns 03169 $sign = ""; 03170 if ( intval( $number ) < 0 ) { 03171 // For negative numbers apply the algorithm like positive number and add sign. 03172 $sign = "-"; 03173 $number = substr( $number, 1 ); 03174 } 03175 $integerPart = array(); 03176 $decimalPart = array(); 03177 $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches ); 03178 preg_match( "/\d+/", $number, $integerPart ); 03179 preg_match( "/\.\d*/", $number, $decimalPart ); 03180 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : ""; 03181 if ( $groupedNumber === $number ) { 03182 // the string does not have any number part. Eg: .12345 03183 return $sign . $groupedNumber; 03184 } 03185 $start = $end = strlen( $integerPart[0] ); 03186 while ( $start > 0 ) { 03187 $match = $matches[0][$numMatches - 1]; 03188 $matchLen = strlen( $match ); 03189 $start = $end - $matchLen; 03190 if ( $start < 0 ) { 03191 $start = 0; 03192 } 03193 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber; 03194 $end = $start; 03195 if ( $numMatches > 1 ) { 03196 // use the last pattern for the rest of the number 03197 $numMatches--; 03198 } 03199 if ( $start > 0 ) { 03200 $groupedNumber = "," . $groupedNumber; 03201 } 03202 } 03203 return $sign . $groupedNumber; 03204 } 03205 } 03206 03210 function digitGroupingPattern() { 03211 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' ); 03212 } 03213 03217 function digitTransformTable() { 03218 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' ); 03219 } 03220 03224 function separatorTransformTable() { 03225 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' ); 03226 } 03227 03237 function listToText( array $l ) { 03238 $m = count( $l ) - 1; 03239 if ( $m < 0 ) { 03240 return ''; 03241 } 03242 if ( $m > 0 ) { 03243 $and = $this->getMessageFromDB( 'and' ); 03244 $space = $this->getMessageFromDB( 'word-separator' ); 03245 if ( $m > 1 ) { 03246 $comma = $this->getMessageFromDB( 'comma-separator' ); 03247 } 03248 } 03249 $s = $l[$m]; 03250 for ( $i = $m - 1; $i >= 0; $i-- ) { 03251 if ( $i == $m - 1 ) { 03252 $s = $l[$i] . $and . $space . $s; 03253 } else { 03254 $s = $l[$i] . $comma . $s; 03255 } 03256 } 03257 return $s; 03258 } 03259 03266 function commaList( array $list ) { 03267 return implode( 03268 wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(), 03269 $list 03270 ); 03271 } 03272 03279 function semicolonList( array $list ) { 03280 return implode( 03281 wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(), 03282 $list 03283 ); 03284 } 03285 03291 function pipeList( array $list ) { 03292 return implode( 03293 wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(), 03294 $list 03295 ); 03296 } 03297 03315 function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) { 03316 # Use the localized ellipsis character 03317 if ( $ellipsis == '...' ) { 03318 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); 03319 } 03320 # Check if there is no need to truncate 03321 if ( $length == 0 ) { 03322 return $ellipsis; // convention 03323 } elseif ( strlen( $string ) <= abs( $length ) ) { 03324 return $string; // no need to truncate 03325 } 03326 $stringOriginal = $string; 03327 # If ellipsis length is >= $length then we can't apply $adjustLength 03328 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) { 03329 $string = $ellipsis; // this can be slightly unexpected 03330 # Otherwise, truncate and add ellipsis... 03331 } else { 03332 $eLength = $adjustLength ? strlen( $ellipsis ) : 0; 03333 if ( $length > 0 ) { 03334 $length -= $eLength; 03335 $string = substr( $string, 0, $length ); // xyz... 03336 $string = $this->removeBadCharLast( $string ); 03337 $string = rtrim( $string ); 03338 $string = $string . $ellipsis; 03339 } else { 03340 $length += $eLength; 03341 $string = substr( $string, $length ); // ...xyz 03342 $string = $this->removeBadCharFirst( $string ); 03343 $string = ltrim( $string ); 03344 $string = $ellipsis . $string; 03345 } 03346 } 03347 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181). 03348 # This check is *not* redundant if $adjustLength, due to the single case where 03349 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string. 03350 if ( strlen( $string ) < strlen( $stringOriginal ) ) { 03351 return $string; 03352 } else { 03353 return $stringOriginal; 03354 } 03355 } 03356 03364 protected function removeBadCharLast( $string ) { 03365 if ( $string != '' ) { 03366 $char = ord( $string[strlen( $string ) - 1] ); 03367 $m = array(); 03368 if ( $char >= 0xc0 ) { 03369 # We got the first byte only of a multibyte char; remove it. 03370 $string = substr( $string, 0, -1 ); 03371 } elseif ( $char >= 0x80 && 03372 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' . 03373 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) 03374 ) { 03375 # We chopped in the middle of a character; remove it 03376 $string = $m[1]; 03377 } 03378 } 03379 return $string; 03380 } 03381 03389 protected function removeBadCharFirst( $string ) { 03390 if ( $string != '' ) { 03391 $char = ord( $string[0] ); 03392 if ( $char >= 0x80 && $char < 0xc0 ) { 03393 # We chopped in the middle of a character; remove the whole thing 03394 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string ); 03395 } 03396 } 03397 return $string; 03398 } 03399 03415 function truncateHtml( $text, $length, $ellipsis = '...' ) { 03416 # Use the localized ellipsis character 03417 if ( $ellipsis == '...' ) { 03418 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); 03419 } 03420 # Check if there is clearly no need to truncate 03421 if ( $length <= 0 ) { 03422 return $ellipsis; // no text shown, nothing to format (convention) 03423 } elseif ( strlen( $text ) <= $length ) { 03424 return $text; // string short enough even *with* HTML (short-circuit) 03425 } 03426 03427 $dispLen = 0; // innerHTML legth so far 03428 $testingEllipsis = false; // checking if ellipses will make string longer/equal? 03429 $tagType = 0; // 0-open, 1-close 03430 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither 03431 $entityState = 0; // 0-not entity, 1-entity 03432 $tag = $ret = ''; // accumulated tag name, accumulated result string 03433 $openTags = array(); // open tag stack 03434 $maybeState = null; // possible truncation state 03435 03436 $textLen = strlen( $text ); 03437 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated 03438 for ( $pos = 0; true; ++$pos ) { 03439 # Consider truncation once the display length has reached the maximim. 03440 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case. 03441 # Check that we're not in the middle of a bracket/entity... 03442 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) { 03443 if ( !$testingEllipsis ) { 03444 $testingEllipsis = true; 03445 # Save where we are; we will truncate here unless there turn out to 03446 # be so few remaining characters that truncation is not necessary. 03447 if ( !$maybeState ) { // already saved? ($neLength = 0 case) 03448 $maybeState = array( $ret, $openTags ); // save state 03449 } 03450 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) { 03451 # String in fact does need truncation, the truncation point was OK. 03452 list( $ret, $openTags ) = $maybeState; // reload state 03453 $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix 03454 $ret .= $ellipsis; // add ellipsis 03455 break; 03456 } 03457 } 03458 if ( $pos >= $textLen ) { 03459 break; // extra iteration just for above checks 03460 } 03461 03462 # Read the next char... 03463 $ch = $text[$pos]; 03464 $lastCh = $pos ? $text[$pos - 1] : ''; 03465 $ret .= $ch; // add to result string 03466 if ( $ch == '<' ) { 03467 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML 03468 $entityState = 0; // for bad HTML 03469 $bracketState = 1; // tag started (checking for backslash) 03470 } elseif ( $ch == '>' ) { 03471 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); 03472 $entityState = 0; // for bad HTML 03473 $bracketState = 0; // out of brackets 03474 } elseif ( $bracketState == 1 ) { 03475 if ( $ch == '/' ) { 03476 $tagType = 1; // close tag (e.g. "</span>") 03477 } else { 03478 $tagType = 0; // open tag (e.g. "<span>") 03479 $tag .= $ch; 03480 } 03481 $bracketState = 2; // building tag name 03482 } elseif ( $bracketState == 2 ) { 03483 if ( $ch != ' ' ) { 03484 $tag .= $ch; 03485 } else { 03486 // Name found (e.g. "<a href=..."), add on tag attributes... 03487 $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 ); 03488 } 03489 } elseif ( $bracketState == 0 ) { 03490 if ( $entityState ) { 03491 if ( $ch == ';' ) { 03492 $entityState = 0; 03493 $dispLen++; // entity is one displayed char 03494 } 03495 } else { 03496 if ( $neLength == 0 && !$maybeState ) { 03497 // Save state without $ch. We want to *hit* the first 03498 // display char (to get tags) but not *use* it if truncating. 03499 $maybeState = array( substr( $ret, 0, -1 ), $openTags ); 03500 } 03501 if ( $ch == '&' ) { 03502 $entityState = 1; // entity found, (e.g. " ") 03503 } else { 03504 $dispLen++; // this char is displayed 03505 // Add the next $max display text chars after this in one swoop... 03506 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen; 03507 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max ); 03508 $dispLen += $skipped; 03509 $pos += $skipped; 03510 } 03511 } 03512 } 03513 } 03514 // Close the last tag if left unclosed by bad HTML 03515 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags ); 03516 while ( count( $openTags ) > 0 ) { 03517 $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags 03518 } 03519 return $ret; 03520 } 03521 03533 private function truncate_skip( &$ret, $text, $search, $start, $len = null ) { 03534 if ( $len === null ) { 03535 $len = -1; // -1 means "no limit" for strcspn 03536 } elseif ( $len < 0 ) { 03537 $len = 0; // sanity 03538 } 03539 $skipCount = 0; 03540 if ( $start < strlen( $text ) ) { 03541 $skipCount = strcspn( $text, $search, $start, $len ); 03542 $ret .= substr( $text, $start, $skipCount ); 03543 } 03544 return $skipCount; 03545 } 03546 03556 private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) { 03557 $tag = ltrim( $tag ); 03558 if ( $tag != '' ) { 03559 if ( $tagType == 0 && $lastCh != '/' ) { 03560 $openTags[] = $tag; // tag opened (didn't close itself) 03561 } elseif ( $tagType == 1 ) { 03562 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) { 03563 array_pop( $openTags ); // tag closed 03564 } 03565 } 03566 $tag = ''; 03567 } 03568 } 03569 03578 function convertGrammar( $word, $case ) { 03579 global $wgGrammarForms; 03580 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) { 03581 return $wgGrammarForms[$this->getCode()][$case][$word]; 03582 } 03583 return $word; 03584 } 03590 function getGrammarForms() { 03591 global $wgGrammarForms; 03592 if ( isset( $wgGrammarForms[$this->getCode()] ) && is_array( $wgGrammarForms[$this->getCode()] ) ) { 03593 return $wgGrammarForms[$this->getCode()]; 03594 } 03595 return array(); 03596 } 03616 function gender( $gender, $forms ) { 03617 if ( !count( $forms ) ) { 03618 return ''; 03619 } 03620 $forms = $this->preConvertPlural( $forms, 2 ); 03621 if ( $gender === 'male' ) { 03622 return $forms[0]; 03623 } 03624 if ( $gender === 'female' ) { 03625 return $forms[1]; 03626 } 03627 return isset( $forms[2] ) ? $forms[2] : $forms[0]; 03628 } 03629 03645 function convertPlural( $count, $forms ) { 03646 // Handle explicit n=pluralform cases 03647 $forms = $this->handleExplicitPluralForms( $count, $forms ); 03648 if ( is_string( $forms ) ) { 03649 return $forms; 03650 } 03651 if ( !count( $forms ) ) { 03652 return ''; 03653 } 03654 03655 $pluralForm = $this->getPluralRuleIndexNumber( $count ); 03656 $pluralForm = min( $pluralForm, count( $forms ) - 1 ); 03657 return $forms[$pluralForm]; 03658 } 03659 03675 protected function handleExplicitPluralForms( $count, array $forms ) { 03676 foreach ( $forms as $index => $form ) { 03677 if ( preg_match( '/\d+=/i', $form ) ) { 03678 $pos = strpos( $form, '=' ); 03679 if ( substr( $form, 0, $pos ) === (string) $count ) { 03680 return substr( $form, $pos + 1 ); 03681 } 03682 unset( $forms[$index] ); 03683 } 03684 } 03685 return array_values( $forms ); 03686 } 03687 03696 protected function preConvertPlural( /* Array */ $forms, $count ) { 03697 while ( count( $forms ) < $count ) { 03698 $forms[] = $forms[count( $forms ) - 1]; 03699 } 03700 return $forms; 03701 } 03702 03714 function translateBlockExpiry( $str ) { 03715 $duration = SpecialBlock::getSuggestedDurations( $this ); 03716 foreach ( $duration as $show => $value ) { 03717 if ( strcmp( $str, $value ) == 0 ) { 03718 return htmlspecialchars( trim( $show ) ); 03719 } 03720 } 03721 03722 // Since usually only infinite or indefinite is only on list, so try 03723 // equivalents if still here. 03724 $indefs = array( 'infinite', 'infinity', 'indefinite' ); 03725 if ( in_array( $str, $indefs ) ) { 03726 foreach ( $indefs as $val ) { 03727 $show = array_search( $val, $duration, true ); 03728 if ( $show !== false ) { 03729 return htmlspecialchars( trim( $show ) ); 03730 } 03731 } 03732 } 03733 03734 // If all else fails, return a standard duration or timestamp description. 03735 $time = strtotime( $str, 0 ); 03736 if ( $time === false ) { // Unknown format. Return it as-is in case. 03737 return $str; 03738 } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp. 03739 // $time is relative to 0 so it's a duration length. 03740 return $this->formatDuration( $time ); 03741 } else { // It's an absolute timestamp. 03742 if ( $time === 0 ) { 03743 // wfTimestamp() handles 0 as current time instead of epoch. 03744 return $this->timeanddate( '19700101000000' ); 03745 } else { 03746 return $this->timeanddate( $time ); 03747 } 03748 } 03749 } 03750 03758 public function segmentForDiff( $text ) { 03759 return $text; 03760 } 03761 03768 public function unsegmentForDiff( $text ) { 03769 return $text; 03770 } 03771 03778 public function getConverter() { 03779 return $this->mConverter; 03780 } 03781 03788 public function autoConvertToAllVariants( $text ) { 03789 return $this->mConverter->autoConvertToAllVariants( $text ); 03790 } 03791 03798 public function convert( $text ) { 03799 return $this->mConverter->convert( $text ); 03800 } 03801 03808 public function convertTitle( $title ) { 03809 return $this->mConverter->convertTitle( $title ); 03810 } 03811 03818 public function convertNamespace( $ns ) { 03819 return $this->mConverter->convertNamespace( $ns ); 03820 } 03821 03827 public function hasVariants() { 03828 return count( $this->getVariants() ) > 1; 03829 } 03830 03838 public function hasVariant( $variant ) { 03839 return (bool)$this->mConverter->validateVariant( $variant ); 03840 } 03841 03849 public function armourMath( $text ) { 03850 return $this->mConverter->armourMath( $text ); 03851 } 03852 03860 public function convertHtml( $text, $isTitle = false ) { 03861 return htmlspecialchars( $this->convert( $text, $isTitle ) ); 03862 } 03863 03868 public function convertCategoryKey( $key ) { 03869 return $this->mConverter->convertCategoryKey( $key ); 03870 } 03871 03878 public function getVariants() { 03879 return $this->mConverter->getVariants(); 03880 } 03881 03885 public function getPreferredVariant() { 03886 return $this->mConverter->getPreferredVariant(); 03887 } 03888 03892 public function getDefaultVariant() { 03893 return $this->mConverter->getDefaultVariant(); 03894 } 03895 03899 public function getURLVariant() { 03900 return $this->mConverter->getURLVariant(); 03901 } 03902 03915 public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) { 03916 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond ); 03917 } 03918 03925 function getExtraHashOptions() { 03926 return $this->mConverter->getExtraHashOptions(); 03927 } 03928 03936 public function getParsedTitle() { 03937 return $this->mConverter->getParsedTitle(); 03938 } 03939 03952 public function markNoConversion( $text, $noParse = false ) { 03953 // Excluding protocal-relative URLs may avoid many false positives. 03954 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) { 03955 return $this->mConverter->markNoConversion( $text ); 03956 } else { 03957 return $text; 03958 } 03959 } 03960 03967 public function linkTrail() { 03968 return self::$dataCache->getItem( $this->mCode, 'linkTrail' ); 03969 } 03970 03977 public function linkPrefixCharset() { 03978 return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' ); 03979 } 03980 03984 function getLangObj() { 03985 return $this; 03986 } 03987 03995 public function getParentLanguage() { 03996 if ( $this->mParentLanguage !== false ) { 03997 return $this->mParentLanguage; 03998 } 03999 04000 $pieces = explode( '-', $this->getCode() ); 04001 $code = $pieces[0]; 04002 if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) { 04003 $this->mParentLanguage = null; 04004 return null; 04005 } 04006 $lang = Language::factory( $code ); 04007 if ( !$lang->hasVariant( $this->getCode() ) ) { 04008 $this->mParentLanguage = null; 04009 return null; 04010 } 04011 04012 $this->mParentLanguage = $lang; 04013 return $lang; 04014 } 04015 04024 public function getCode() { 04025 return $this->mCode; 04026 } 04027 04038 public function getHtmlCode() { 04039 if ( is_null( $this->mHtmlCode ) ) { 04040 $this->mHtmlCode = wfBCP47( $this->getCode() ); 04041 } 04042 return $this->mHtmlCode; 04043 } 04044 04048 public function setCode( $code ) { 04049 $this->mCode = $code; 04050 // Ensure we don't leave incorrect cached data lying around 04051 $this->mHtmlCode = null; 04052 $this->mParentLanguage = false; 04053 } 04054 04063 public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) { 04064 if ( !self::isValidBuiltInCode( $code ) ) { 04065 throw new MWException( "Invalid language code \"$code\"" ); 04066 } 04067 04068 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix; 04069 } 04070 04078 public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) { 04079 $m = null; 04080 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' . 04081 preg_quote( $suffix, '/' ) . '/', $filename, $m ); 04082 if ( !count( $m ) ) { 04083 return false; 04084 } 04085 return str_replace( '_', '-', strtolower( $m[1] ) ); 04086 } 04087 04092 public static function getMessagesFileName( $code ) { 04093 global $IP; 04094 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' ); 04095 wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) ); 04096 return $file; 04097 } 04098 04104 public static function getJsonMessagesFileName( $code ) { 04105 global $IP; 04106 04107 if ( !self::isValidBuiltInCode( $code ) ) { 04108 throw new MWException( "Invalid language code \"$code\"" ); 04109 } 04110 04111 return "$IP/languages/i18n/$code.json" ; 04112 } 04113 04118 public static function getClassFileName( $code ) { 04119 global $IP; 04120 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' ); 04121 } 04122 04130 public static function getFallbackFor( $code ) { 04131 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { 04132 return false; 04133 } else { 04134 $fallbacks = self::getFallbacksFor( $code ); 04135 $first = array_shift( $fallbacks ); 04136 return $first; 04137 } 04138 } 04139 04147 public static function getFallbacksFor( $code ) { 04148 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { 04149 return array(); 04150 } else { 04151 $v = self::getLocalisationCache()->getItem( $code, 'fallback' ); 04152 $v = array_map( 'trim', explode( ',', $v ) ); 04153 if ( $v[count( $v ) - 1] !== 'en' ) { 04154 $v[] = 'en'; 04155 } 04156 return $v; 04157 } 04158 } 04159 04168 public static function getFallbacksIncludingSiteLanguage( $code ) { 04169 global $wgLanguageCode; 04170 04171 // Usually, we will only store a tiny number of fallback chains, so we 04172 // keep them in static memory. 04173 $cacheKey = "{$code}-{$wgLanguageCode}"; 04174 04175 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) { 04176 $fallbacks = self::getFallbacksFor( $code ); 04177 04178 // Append the site's fallback chain, including the site language itself 04179 $siteFallbacks = self::getFallbacksFor( $wgLanguageCode ); 04180 array_unshift( $siteFallbacks, $wgLanguageCode ); 04181 04182 // Eliminate any languages already included in the chain 04183 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks ); 04184 04185 self::$fallbackLanguageCache[$cacheKey] = array( $fallbacks, $siteFallbacks ); 04186 } 04187 return self::$fallbackLanguageCache[$cacheKey]; 04188 } 04189 04199 public static function getMessagesFor( $code ) { 04200 return self::getLocalisationCache()->getItem( $code, 'messages' ); 04201 } 04202 04211 public static function getMessageFor( $key, $code ) { 04212 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key ); 04213 } 04214 04223 public static function getMessageKeysFor( $code ) { 04224 return self::getLocalisationCache()->getSubItemList( $code, 'messages' ); 04225 } 04226 04231 function fixVariableInNamespace( $talk ) { 04232 if ( strpos( $talk, '$1' ) === false ) { 04233 return $talk; 04234 } 04235 04236 global $wgMetaNamespace; 04237 $talk = str_replace( '$1', $wgMetaNamespace, $talk ); 04238 04239 # Allow grammar transformations 04240 # Allowing full message-style parsing would make simple requests 04241 # such as action=raw much more expensive than they need to be. 04242 # This will hopefully cover most cases. 04243 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i', 04244 array( &$this, 'replaceGrammarInNamespace' ), $talk ); 04245 return str_replace( ' ', '_', $talk ); 04246 } 04247 04252 function replaceGrammarInNamespace( $m ) { 04253 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) ); 04254 } 04255 04260 static function getCaseMaps() { 04261 static $wikiUpperChars, $wikiLowerChars; 04262 if ( isset( $wikiUpperChars ) ) { 04263 return array( $wikiUpperChars, $wikiLowerChars ); 04264 } 04265 04266 wfProfileIn( __METHOD__ ); 04267 $arr = wfGetPrecompiledData( 'Utf8Case.ser' ); 04268 if ( $arr === false ) { 04269 throw new MWException( 04270 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" ); 04271 } 04272 $wikiUpperChars = $arr['wikiUpperChars']; 04273 $wikiLowerChars = $arr['wikiLowerChars']; 04274 wfProfileOut( __METHOD__ ); 04275 return array( $wikiUpperChars, $wikiLowerChars ); 04276 } 04277 04289 public function formatExpiry( $expiry, $format = true ) { 04290 static $infinity; 04291 if ( $infinity === null ) { 04292 $infinity = wfGetDB( DB_SLAVE )->getInfinity(); 04293 } 04294 04295 if ( $expiry == '' || $expiry == $infinity ) { 04296 return $format === true 04297 ? $this->getMessageFromDB( 'infiniteblock' ) 04298 : $infinity; 04299 } else { 04300 return $format === true 04301 ? $this->timeanddate( $expiry, /* User preference timezone */ true ) 04302 : wfTimestamp( $format, $expiry ); 04303 } 04304 } 04305 04316 function formatTimePeriod( $seconds, $format = array() ) { 04317 if ( !is_array( $format ) ) { 04318 $format = array( 'avoid' => $format ); // For backwards compatibility 04319 } 04320 if ( !isset( $format['avoid'] ) ) { 04321 $format['avoid'] = false; 04322 } 04323 if ( !isset( $format['noabbrevs' ] ) ) { 04324 $format['noabbrevs'] = false; 04325 } 04326 $secondsMsg = wfMessage( 04327 $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this ); 04328 $minutesMsg = wfMessage( 04329 $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this ); 04330 $hoursMsg = wfMessage( 04331 $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this ); 04332 $daysMsg = wfMessage( 04333 $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this ); 04334 04335 if ( round( $seconds * 10 ) < 100 ) { 04336 $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) ); 04337 $s = $secondsMsg->params( $s )->text(); 04338 } elseif ( round( $seconds ) < 60 ) { 04339 $s = $this->formatNum( round( $seconds ) ); 04340 $s = $secondsMsg->params( $s )->text(); 04341 } elseif ( round( $seconds ) < 3600 ) { 04342 $minutes = floor( $seconds / 60 ); 04343 $secondsPart = round( fmod( $seconds, 60 ) ); 04344 if ( $secondsPart == 60 ) { 04345 $secondsPart = 0; 04346 $minutes++; 04347 } 04348 $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04349 $s .= ' '; 04350 $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text(); 04351 } elseif ( round( $seconds ) <= 2 * 86400 ) { 04352 $hours = floor( $seconds / 3600 ); 04353 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 ); 04354 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 ); 04355 if ( $secondsPart == 60 ) { 04356 $secondsPart = 0; 04357 $minutes++; 04358 } 04359 if ( $minutes == 60 ) { 04360 $minutes = 0; 04361 $hours++; 04362 } 04363 $s = $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04364 $s .= ' '; 04365 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04366 if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) { 04367 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text(); 04368 } 04369 } else { 04370 $days = floor( $seconds / 86400 ); 04371 if ( $format['avoid'] === 'avoidminutes' ) { 04372 $hours = round( ( $seconds - $days * 86400 ) / 3600 ); 04373 if ( $hours == 24 ) { 04374 $hours = 0; 04375 $days++; 04376 } 04377 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04378 $s .= ' '; 04379 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04380 } elseif ( $format['avoid'] === 'avoidseconds' ) { 04381 $hours = floor( ( $seconds - $days * 86400 ) / 3600 ); 04382 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 ); 04383 if ( $minutes == 60 ) { 04384 $minutes = 0; 04385 $hours++; 04386 } 04387 if ( $hours == 24 ) { 04388 $hours = 0; 04389 $days++; 04390 } 04391 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04392 $s .= ' '; 04393 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04394 $s .= ' '; 04395 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04396 } else { 04397 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04398 $s .= ' '; 04399 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format ); 04400 } 04401 } 04402 return $s; 04403 } 04404 04415 function formatBitrate( $bps ) { 04416 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" ); 04417 } 04418 04425 function formatComputingNumbers( $size, $boundary, $messageKey ) { 04426 if ( $size <= 0 ) { 04427 return str_replace( '$1', $this->formatNum( $size ), 04428 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) ) 04429 ); 04430 } 04431 $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ); 04432 $index = 0; 04433 04434 $maxIndex = count( $sizes ) - 1; 04435 while ( $size >= $boundary && $index < $maxIndex ) { 04436 $index++; 04437 $size /= $boundary; 04438 } 04439 04440 // For small sizes no decimal places necessary 04441 $round = 0; 04442 if ( $index > 1 ) { 04443 // For MB and bigger two decimal places are smarter 04444 $round = 2; 04445 } 04446 $msg = str_replace( '$1', $sizes[$index], $messageKey ); 04447 04448 $size = round( $size, $round ); 04449 $text = $this->getMessageFromDB( $msg ); 04450 return str_replace( '$1', $this->formatNum( $size ), $text ); 04451 } 04452 04463 function formatSize( $size ) { 04464 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" ); 04465 } 04466 04476 function specialList( $page, $details, $oppositedm = true ) { 04477 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . 04478 $this->getDirMark(); 04479 $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) . 04480 wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : ''; 04481 return $page . $details; 04482 } 04483 04494 public function viewPrevNext( Title $title, $offset, $limit, array $query = array(), $atend = false ) { 04495 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip? 04496 04497 # Make 'previous' link 04498 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); 04499 if ( $offset > 0 ) { 04500 $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit, 04501 $query, $prev, 'prevn-title', 'mw-prevlink' ); 04502 } else { 04503 $plink = htmlspecialchars( $prev ); 04504 } 04505 04506 # Make 'next' link 04507 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); 04508 if ( $atend ) { 04509 $nlink = htmlspecialchars( $next ); 04510 } else { 04511 $nlink = $this->numLink( $title, $offset + $limit, $limit, 04512 $query, $next, 'nextn-title', 'mw-nextlink' ); 04513 } 04514 04515 # Make links to set number of items per page 04516 $numLinks = array(); 04517 foreach ( array( 20, 50, 100, 250, 500 ) as $num ) { 04518 $numLinks[] = $this->numLink( $title, $offset, $num, 04519 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' ); 04520 } 04521 04522 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title 04523 )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped(); 04524 } 04525 04538 private function numLink( Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class ) { 04539 $query = array( 'limit' => $limit, 'offset' => $offset ) + $query; 04540 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); 04541 return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ), 04542 'title' => $tooltip, 'class' => $class ), $link ); 04543 } 04544 04550 public function getConvRuleTitle() { 04551 return $this->mConverter->getConvRuleTitle(); 04552 } 04553 04559 public function getCompiledPluralRules() { 04560 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' ); 04561 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04562 if ( !$pluralRules ) { 04563 foreach ( $fallbacks as $fallbackCode ) { 04564 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' ); 04565 if ( $pluralRules ) { 04566 break; 04567 } 04568 } 04569 } 04570 return $pluralRules; 04571 } 04572 04578 public function getPluralRules() { 04579 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' ); 04580 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04581 if ( !$pluralRules ) { 04582 foreach ( $fallbacks as $fallbackCode ) { 04583 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' ); 04584 if ( $pluralRules ) { 04585 break; 04586 } 04587 } 04588 } 04589 return $pluralRules; 04590 } 04591 04597 public function getPluralRuleTypes() { 04598 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' ); 04599 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04600 if ( !$pluralRuleTypes ) { 04601 foreach ( $fallbacks as $fallbackCode ) { 04602 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' ); 04603 if ( $pluralRuleTypes ) { 04604 break; 04605 } 04606 } 04607 } 04608 return $pluralRuleTypes; 04609 } 04610 04615 public function getPluralRuleIndexNumber( $number ) { 04616 $pluralRules = $this->getCompiledPluralRules(); 04617 $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules ); 04618 return $form; 04619 } 04620 04628 public function getPluralRuleType( $number ) { 04629 $index = $this->getPluralRuleIndexNumber( $number ); 04630 $pluralRuleTypes = $this->getPluralRuleTypes(); 04631 if ( isset( $pluralRuleTypes[$index] ) ) { 04632 return $pluralRuleTypes[$index]; 04633 } else { 04634 return 'other'; 04635 } 04636 } 04637 }