MediaWiki
REL1_22
|
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; } 00065 function convertLinkToAllVariants( $text ) { return $this->autoConvertToAllVariants( $text ); } 00067 function armourMath( $text ) { return $text; } 00068 function validateVariant( $variant = null ) { return $variant === $this->mLang->getCode() ? $variant : null; } 00069 function translate( $text, $variant ) { return $text; } 00070 } 00071 00076 class Language { 00077 00081 public $mConverter; 00082 00083 public $mVariants, $mCode, $mLoaded = false; 00084 public $mMagicExtensions = array(), $mMagicHookDone = false; 00085 private $mHtmlCode = null, $mParentLanguage = false; 00086 00087 public $dateFormatStrings = array(); 00088 public $mExtendedSpecialPageAliases; 00089 00090 protected $namespaceNames, $mNamespaceIds, $namespaceAliases; 00091 00095 public $transformData = array(); 00096 00100 static public $dataCache; 00101 00102 static public $mLangObjCache = array(); 00103 00104 static public $mWeekdayMsgs = array( 00105 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 00106 'friday', 'saturday' 00107 ); 00108 00109 static public $mWeekdayAbbrevMsgs = array( 00110 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' 00111 ); 00112 00113 static public $mMonthMsgs = array( 00114 'january', 'february', 'march', 'april', 'may_long', 'june', 00115 'july', 'august', 'september', 'october', 'november', 00116 'december' 00117 ); 00118 static public $mMonthGenMsgs = array( 00119 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', 00120 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 00121 'december-gen' 00122 ); 00123 static public $mMonthAbbrevMsgs = array( 00124 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 00125 'sep', 'oct', 'nov', 'dec' 00126 ); 00127 00128 static public $mIranianCalendarMonthMsgs = array( 00129 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3', 00130 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6', 00131 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9', 00132 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12' 00133 ); 00134 00135 static public $mHebrewCalendarMonthMsgs = array( 00136 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3', 00137 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6', 00138 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9', 00139 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12', 00140 'hebrew-calendar-m6a', 'hebrew-calendar-m6b' 00141 ); 00142 00143 static public $mHebrewCalendarMonthGenMsgs = array( 00144 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen', 00145 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen', 00146 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen', 00147 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen', 00148 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen' 00149 ); 00150 00151 static public $mHijriCalendarMonthMsgs = array( 00152 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3', 00153 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6', 00154 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9', 00155 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12' 00156 ); 00157 00162 static public $durationIntervals = array( 00163 'millennia' => 31556952000, 00164 'centuries' => 3155695200, 00165 'decades' => 315569520, 00166 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 ) 00167 'weeks' => 604800, 00168 'days' => 86400, 00169 'hours' => 3600, 00170 'minutes' => 60, 00171 'seconds' => 1, 00172 ); 00173 00180 static private $fallbackLanguageCache = array(); 00181 00187 static function factory( $code ) { 00188 global $wgDummyLanguageCodes, $wgLangObjCacheSize; 00189 00190 if ( isset( $wgDummyLanguageCodes[$code] ) ) { 00191 $code = $wgDummyLanguageCodes[$code]; 00192 } 00193 00194 // get the language object to process 00195 $langObj = isset( self::$mLangObjCache[$code] ) 00196 ? self::$mLangObjCache[$code] 00197 : self::newFromCode( $code ); 00198 00199 // merge the language object in to get it up front in the cache 00200 self::$mLangObjCache = array_merge( array( $code => $langObj ), self::$mLangObjCache ); 00201 // get rid of the oldest ones in case we have an overflow 00202 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true ); 00203 00204 return $langObj; 00205 } 00206 00213 protected static function newFromCode( $code ) { 00214 // Protect against path traversal below 00215 if ( !Language::isValidCode( $code ) 00216 || strcspn( $code, ":/\\\000" ) !== strlen( $code ) ) 00217 { 00218 throw new MWException( "Invalid language code \"$code\"" ); 00219 } 00220 00221 if ( !Language::isValidBuiltInCode( $code ) ) { 00222 // It's not possible to customise this code with class files, so 00223 // just return a Language object. This is to support uselang= hacks. 00224 $lang = new Language; 00225 $lang->setCode( $code ); 00226 return $lang; 00227 } 00228 00229 // Check if there is a language class for the code 00230 $class = self::classFromCode( $code ); 00231 self::preloadLanguageClass( $class ); 00232 if ( class_exists( $class ) ) { 00233 $lang = new $class; 00234 return $lang; 00235 } 00236 00237 // Keep trying the fallback list until we find an existing class 00238 $fallbacks = Language::getFallbacksFor( $code ); 00239 foreach ( $fallbacks as $fallbackCode ) { 00240 if ( !Language::isValidBuiltInCode( $fallbackCode ) ) { 00241 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" ); 00242 } 00243 00244 $class = self::classFromCode( $fallbackCode ); 00245 self::preloadLanguageClass( $class ); 00246 if ( class_exists( $class ) ) { 00247 $lang = Language::newFromCode( $fallbackCode ); 00248 $lang->setCode( $code ); 00249 return $lang; 00250 } 00251 } 00252 00253 throw new MWException( "Invalid fallback sequence for language '$code'" ); 00254 } 00255 00264 public static function isSupportedLanguage( $code ) { 00265 return $code === strtolower( $code ) && is_readable( self::getMessagesFileName( $code ) ); 00266 } 00267 00283 public static function isWellFormedLanguageTag( $code, $lenient = false ) { 00284 $alpha = '[a-z]'; 00285 $digit = '[0-9]'; 00286 $alphanum = '[a-z0-9]'; 00287 $x = 'x'; # private use singleton 00288 $singleton = '[a-wy-z]'; # other singleton 00289 $s = $lenient ? '[-_]' : '-'; 00290 00291 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}"; 00292 $script = "$alpha{4}"; # ISO 15924 00293 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49 00294 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})"; 00295 $extension = "$singleton(?:$s$alphanum{2,8})+"; 00296 $privateUse = "$x(?:$s$alphanum{1,8})+"; 00297 00298 # Define certain grandfathered codes, since otherwise the regex is pretty useless. 00299 # Since these are limited, this is safe even later changes to the registry -- 00300 # the only oddity is that it might change the type of the tag, and thus 00301 # the results from the capturing groups. 00302 # http://www.iana.org/assignments/language-subtag-registry 00303 00304 $grandfathered = "en{$s}GB{$s}oed" 00305 . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)" 00306 . "|no{$s}(?:bok|nyn)" 00307 . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)" 00308 . "|zh{$s}min{$s}nan"; 00309 00310 $variantList = "$variant(?:$s$variant)*"; 00311 $extensionList = "$extension(?:$s$extension)*"; 00312 00313 $langtag = "(?:($language)" 00314 . "(?:$s$script)?" 00315 . "(?:$s$region)?" 00316 . "(?:$s$variantList)?" 00317 . "(?:$s$extensionList)?" 00318 . "(?:$s$privateUse)?)"; 00319 00320 # The final breakdown, with capturing groups for each of these components 00321 # The variants, extensions, grandfathered, and private-use may have interior '-' 00322 00323 $root = "^(?:$langtag|$privateUse|$grandfathered)$"; 00324 00325 return (bool)preg_match( "/$root/", strtolower( $code ) ); 00326 } 00327 00337 public static function isValidCode( $code ) { 00338 static $cache = array(); 00339 if ( isset( $cache[$code] ) ) { 00340 return $cache[$code]; 00341 } 00342 // People think language codes are html safe, so enforce it. 00343 // Ideally we should only allow a-zA-Z0-9- 00344 // but, .+ and other chars are often used for {{int:}} hacks 00345 // see bugs 37564, 37587, 36938 00346 $cache[$code] = 00347 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) 00348 && !preg_match( Title::getTitleInvalidRegex(), $code ); 00349 00350 return $cache[$code]; 00351 } 00352 00363 public static function isValidBuiltInCode( $code ) { 00364 00365 if ( !is_string( $code ) ) { 00366 if ( is_object( $code ) ) { 00367 $addmsg = " of class " . get_class( $code ); 00368 } else { 00369 $addmsg = ''; 00370 } 00371 $type = gettype( $code ); 00372 throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" ); 00373 } 00374 00375 return (bool)preg_match( '/^[a-z0-9-]{2,}$/i', $code ); 00376 } 00377 00386 public static function isKnownLanguageTag( $tag ) { 00387 static $coreLanguageNames; 00388 00389 // Quick escape for invalid input to avoid exceptions down the line 00390 // when code tries to process tags which are not valid at all. 00391 if ( !self::isValidBuiltInCode( $tag ) ) { 00392 return false; 00393 } 00394 00395 if ( $coreLanguageNames === null ) { 00396 global $IP; 00397 include "$IP/languages/Names.php"; 00398 } 00399 00400 if ( isset( $coreLanguageNames[$tag] ) 00401 || self::fetchLanguageName( $tag, $tag ) !== '' 00402 ) { 00403 return true; 00404 } 00405 00406 return false; 00407 } 00408 00413 public static function classFromCode( $code ) { 00414 if ( $code == 'en' ) { 00415 return 'Language'; 00416 } else { 00417 return 'Language' . str_replace( '-', '_', ucfirst( $code ) ); 00418 } 00419 } 00420 00426 public static function preloadLanguageClass( $class ) { 00427 global $IP; 00428 00429 if ( $class === 'Language' ) { 00430 return; 00431 } 00432 00433 if ( file_exists( "$IP/languages/classes/$class.php" ) ) { 00434 include_once "$IP/languages/classes/$class.php"; 00435 } 00436 } 00437 00443 public static function getLocalisationCache() { 00444 if ( is_null( self::$dataCache ) ) { 00445 global $wgLocalisationCacheConf; 00446 $class = $wgLocalisationCacheConf['class']; 00447 self::$dataCache = new $class( $wgLocalisationCacheConf ); 00448 } 00449 return self::$dataCache; 00450 } 00451 00452 function __construct() { 00453 $this->mConverter = new FakeConverter( $this ); 00454 // Set the code to the name of the descendant 00455 if ( get_class( $this ) == 'Language' ) { 00456 $this->mCode = 'en'; 00457 } else { 00458 $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) ); 00459 } 00460 self::getLocalisationCache(); 00461 } 00462 00466 function __destruct() { 00467 foreach ( $this as $name => $value ) { 00468 unset( $this->$name ); 00469 } 00470 } 00471 00476 function initContLang() { } 00477 00483 function getFallbackLanguageCode() { 00484 wfDeprecated( __METHOD__, '1.19' ); 00485 return self::getFallbackFor( $this->mCode ); 00486 } 00487 00492 function getFallbackLanguages() { 00493 return self::getFallbacksFor( $this->mCode ); 00494 } 00495 00500 function getBookstoreList() { 00501 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' ); 00502 } 00503 00510 public function getNamespaces() { 00511 if ( is_null( $this->namespaceNames ) ) { 00512 global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces; 00513 00514 $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' ); 00515 $validNamespaces = MWNamespace::getCanonicalNamespaces(); 00516 00517 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces; 00518 00519 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace; 00520 if ( $wgMetaNamespaceTalk ) { 00521 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk; 00522 } else { 00523 $talk = $this->namespaceNames[NS_PROJECT_TALK]; 00524 $this->namespaceNames[NS_PROJECT_TALK] = 00525 $this->fixVariableInNamespace( $talk ); 00526 } 00527 00528 # Sometimes a language will be localised but not actually exist on this wiki. 00529 foreach ( $this->namespaceNames as $key => $text ) { 00530 if ( !isset( $validNamespaces[$key] ) ) { 00531 unset( $this->namespaceNames[$key] ); 00532 } 00533 } 00534 00535 # The above mixing may leave namespaces out of canonical order. 00536 # Re-order by namespace ID number... 00537 ksort( $this->namespaceNames ); 00538 00539 wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) ); 00540 } 00541 return $this->namespaceNames; 00542 } 00543 00548 public function setNamespaces( array $namespaces ) { 00549 $this->namespaceNames = $namespaces; 00550 $this->mNamespaceIds = null; 00551 } 00552 00556 public function resetNamespaces() { 00557 $this->namespaceNames = null; 00558 $this->mNamespaceIds = null; 00559 $this->namespaceAliases = null; 00560 } 00561 00570 function getFormattedNamespaces() { 00571 $ns = $this->getNamespaces(); 00572 foreach ( $ns as $k => $v ) { 00573 $ns[$k] = strtr( $v, '_', ' ' ); 00574 } 00575 return $ns; 00576 } 00577 00588 function getNsText( $index ) { 00589 $ns = $this->getNamespaces(); 00590 return isset( $ns[$index] ) ? $ns[$index] : false; 00591 } 00592 00606 function getFormattedNsText( $index ) { 00607 $ns = $this->getNsText( $index ); 00608 return strtr( $ns, '_', ' ' ); 00609 } 00610 00618 function getGenderNsText( $index, $gender ) { 00619 global $wgExtraGenderNamespaces; 00620 00621 $ns = $wgExtraGenderNamespaces + self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00622 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index ); 00623 } 00624 00631 function needsGenderDistinction() { 00632 global $wgExtraGenderNamespaces, $wgExtraNamespaces; 00633 if ( count( $wgExtraGenderNamespaces ) > 0 ) { 00634 // $wgExtraGenderNamespaces overrides everything 00635 return true; 00636 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) { 00638 // $wgExtraNamespaces overrides any gender aliases specified in i18n files 00639 return false; 00640 } else { 00641 // Check what is in i18n files 00642 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00643 return count( $aliases ) > 0; 00644 } 00645 } 00646 00655 function getLocalNsIndex( $text ) { 00656 $lctext = $this->lc( $text ); 00657 $ids = $this->getNamespaceIds(); 00658 return isset( $ids[$lctext] ) ? $ids[$lctext] : false; 00659 } 00660 00664 function getNamespaceAliases() { 00665 if ( is_null( $this->namespaceAliases ) ) { 00666 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' ); 00667 if ( !$aliases ) { 00668 $aliases = array(); 00669 } else { 00670 foreach ( $aliases as $name => $index ) { 00671 if ( $index === NS_PROJECT_TALK ) { 00672 unset( $aliases[$name] ); 00673 $name = $this->fixVariableInNamespace( $name ); 00674 $aliases[$name] = $index; 00675 } 00676 } 00677 } 00678 00679 global $wgExtraGenderNamespaces; 00680 $genders = $wgExtraGenderNamespaces + (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00681 foreach ( $genders as $index => $forms ) { 00682 foreach ( $forms as $alias ) { 00683 $aliases[$alias] = $index; 00684 } 00685 } 00686 00687 # Also add converted namespace names as aliases, to avoid confusion. 00688 $convertedNames = array(); 00689 foreach ( $this->getVariants() as $variant ) { 00690 if ( $variant === $this->mCode ) { 00691 continue; 00692 } 00693 foreach ( $this->getNamespaces() as $ns => $_ ) { 00694 $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns; 00695 } 00696 } 00697 00698 $this->namespaceAliases = $aliases + $convertedNames; 00699 } 00700 return $this->namespaceAliases; 00701 } 00702 00706 function getNamespaceIds() { 00707 if ( is_null( $this->mNamespaceIds ) ) { 00708 global $wgNamespaceAliases; 00709 # Put namespace names and aliases into a hashtable. 00710 # If this is too slow, then we should arrange it so that it is done 00711 # before caching. The catch is that at pre-cache time, the above 00712 # class-specific fixup hasn't been done. 00713 $this->mNamespaceIds = array(); 00714 foreach ( $this->getNamespaces() as $index => $name ) { 00715 $this->mNamespaceIds[$this->lc( $name )] = $index; 00716 } 00717 foreach ( $this->getNamespaceAliases() as $name => $index ) { 00718 $this->mNamespaceIds[$this->lc( $name )] = $index; 00719 } 00720 if ( $wgNamespaceAliases ) { 00721 foreach ( $wgNamespaceAliases as $name => $index ) { 00722 $this->mNamespaceIds[$this->lc( $name )] = $index; 00723 } 00724 } 00725 } 00726 return $this->mNamespaceIds; 00727 } 00728 00736 function getNsIndex( $text ) { 00737 $lctext = $this->lc( $text ); 00738 $ns = MWNamespace::getCanonicalIndex( $lctext ); 00739 if ( $ns !== null ) { 00740 return $ns; 00741 } 00742 $ids = $this->getNamespaceIds(); 00743 return isset( $ids[$lctext] ) ? $ids[$lctext] : false; 00744 } 00745 00753 function getVariantname( $code, $usemsg = true ) { 00754 $msg = "variantname-$code"; 00755 if ( $usemsg && wfMessage( $msg )->exists() ) { 00756 return $this->getMessageFromDB( $msg ); 00757 } 00758 $name = self::fetchLanguageName( $code ); 00759 if ( $name ) { 00760 return $name; # if it's defined as a language name, show that 00761 } else { 00762 # otherwise, output the language code 00763 return $code; 00764 } 00765 } 00766 00771 function specialPage( $name ) { 00772 $aliases = $this->getSpecialPageAliases(); 00773 if ( isset( $aliases[$name][0] ) ) { 00774 $name = $aliases[$name][0]; 00775 } 00776 return $this->getNsText( NS_SPECIAL ) . ':' . $name; 00777 } 00778 00782 function getDatePreferences() { 00783 return self::$dataCache->getItem( $this->mCode, 'datePreferences' ); 00784 } 00785 00789 function getDateFormats() { 00790 return self::$dataCache->getItem( $this->mCode, 'dateFormats' ); 00791 } 00792 00796 function getDefaultDateFormat() { 00797 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' ); 00798 if ( $df === 'dmy or mdy' ) { 00799 global $wgAmericanDates; 00800 return $wgAmericanDates ? 'mdy' : 'dmy'; 00801 } else { 00802 return $df; 00803 } 00804 } 00805 00809 function getDatePreferenceMigrationMap() { 00810 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' ); 00811 } 00812 00817 function getImageFile( $image ) { 00818 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image ); 00819 } 00820 00824 function getExtraUserToggles() { 00825 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' ); 00826 } 00827 00832 function getUserToggle( $tog ) { 00833 return $this->getMessageFromDB( "tog-$tog" ); 00834 } 00835 00846 public static function getLanguageNames( $customisedOnly = false ) { 00847 return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' ); 00848 } 00849 00859 public static function getTranslatedLanguageNames( $code ) { 00860 return self::fetchLanguageNames( $code, 'all' ); 00861 } 00862 00874 public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) { 00875 global $wgExtraLanguageNames; 00876 static $coreLanguageNames; 00877 00878 if ( $coreLanguageNames === null ) { 00879 global $IP; 00880 include "$IP/languages/Names.php"; 00881 } 00882 00883 $names = array(); 00884 00885 if ( $inLanguage ) { 00886 # TODO: also include when $inLanguage is null, when this code is more efficient 00887 wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) ); 00888 } 00889 00890 $mwNames = $wgExtraLanguageNames + $coreLanguageNames; 00891 foreach ( $mwNames as $mwCode => $mwName ) { 00892 # - Prefer own MediaWiki native name when not using the hook 00893 # - For other names just add if not added through the hook 00894 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) { 00895 $names[$mwCode] = $mwName; 00896 } 00897 } 00898 00899 if ( $include === 'all' ) { 00900 return $names; 00901 } 00902 00903 $returnMw = array(); 00904 $coreCodes = array_keys( $mwNames ); 00905 foreach ( $coreCodes as $coreCode ) { 00906 $returnMw[$coreCode] = $names[$coreCode]; 00907 } 00908 00909 if ( $include === 'mwfile' ) { 00910 $namesMwFile = array(); 00911 # We do this using a foreach over the codes instead of a directory 00912 # loop so that messages files in extensions will work correctly. 00913 foreach ( $returnMw as $code => $value ) { 00914 if ( is_readable( self::getMessagesFileName( $code ) ) ) { 00915 $namesMwFile[$code] = $names[$code]; 00916 } 00917 } 00918 return $namesMwFile; 00919 } 00920 # 'mw' option; default if it's not one of the other two options (all/mwfile) 00921 return $returnMw; 00922 } 00923 00931 public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) { 00932 $array = self::fetchLanguageNames( $inLanguage, $include ); 00933 return !array_key_exists( $code, $array ) ? '' : $array[$code]; 00934 } 00935 00942 function getMessageFromDB( $msg ) { 00943 return wfMessage( $msg )->inLanguage( $this )->text(); 00944 } 00945 00953 function getLanguageName( $code ) { 00954 return self::fetchLanguageName( $code ); 00955 } 00956 00961 function getMonthName( $key ) { 00962 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] ); 00963 } 00964 00968 function getMonthNamesArray() { 00969 $monthNames = array( '' ); 00970 for ( $i = 1; $i < 13; $i++ ) { 00971 $monthNames[] = $this->getMonthName( $i ); 00972 } 00973 return $monthNames; 00974 } 00975 00980 function getMonthNameGen( $key ) { 00981 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] ); 00982 } 00983 00988 function getMonthAbbreviation( $key ) { 00989 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] ); 00990 } 00991 00995 function getMonthAbbreviationsArray() { 00996 $monthNames = array( '' ); 00997 for ( $i = 1; $i < 13; $i++ ) { 00998 $monthNames[] = $this->getMonthAbbreviation( $i ); 00999 } 01000 return $monthNames; 01001 } 01002 01007 function getWeekdayName( $key ) { 01008 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] ); 01009 } 01010 01015 function getWeekdayAbbreviation( $key ) { 01016 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] ); 01017 } 01018 01023 function getIranianCalendarMonthName( $key ) { 01024 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] ); 01025 } 01026 01031 function getHebrewCalendarMonthName( $key ) { 01032 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] ); 01033 } 01034 01039 function getHebrewCalendarMonthNameGen( $key ) { 01040 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] ); 01041 } 01042 01047 function getHijriCalendarMonthName( $key ) { 01048 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] ); 01049 } 01050 01116 function sprintfDate( $format, $ts, DateTimeZone $zone = null ) { 01117 $s = ''; 01118 $raw = false; 01119 $roman = false; 01120 $hebrewNum = false; 01121 $dateTimeObj = false; 01122 $rawToggle = false; 01123 $iranian = false; 01124 $hebrew = false; 01125 $hijri = false; 01126 $thai = false; 01127 $minguo = false; 01128 $tenno = false; 01129 01130 if ( strlen( $ts ) !== 14 ) { 01131 throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" ); 01132 } 01133 01134 if ( !ctype_digit( $ts ) ) { 01135 throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" ); 01136 } 01137 01138 for ( $p = 0; $p < strlen( $format ); $p++ ) { 01139 $num = false; 01140 $code = $format[$p]; 01141 if ( $code == 'x' && $p < strlen( $format ) - 1 ) { 01142 $code .= $format[++$p]; 01143 } 01144 01145 if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' || $code == 'xo' || $code == 'xt' ) && $p < strlen( $format ) - 1 ) { 01146 $code .= $format[++$p]; 01147 } 01148 01149 switch ( $code ) { 01150 case 'xx': 01151 $s .= 'x'; 01152 break; 01153 case 'xn': 01154 $raw = true; 01155 break; 01156 case 'xN': 01157 $rawToggle = !$rawToggle; 01158 break; 01159 case 'xr': 01160 $roman = true; 01161 break; 01162 case 'xh': 01163 $hebrewNum = true; 01164 break; 01165 case 'xg': 01166 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) ); 01167 break; 01168 case 'xjx': 01169 if ( !$hebrew ) { 01170 $hebrew = self::tsToHebrew( $ts ); 01171 } 01172 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] ); 01173 break; 01174 case 'd': 01175 $num = substr( $ts, 6, 2 ); 01176 break; 01177 case 'D': 01178 if ( !$dateTimeObj ) { 01179 $dateTimeObj = DateTime::createFromFormat( 01180 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01181 ); 01182 } 01183 $s .= $this->getWeekdayAbbreviation( $dateTimeObj->format( 'w' ) + 1 ); 01184 break; 01185 case 'j': 01186 $num = intval( substr( $ts, 6, 2 ) ); 01187 break; 01188 case 'xij': 01189 if ( !$iranian ) { 01190 $iranian = self::tsToIranian( $ts ); 01191 } 01192 $num = $iranian[2]; 01193 break; 01194 case 'xmj': 01195 if ( !$hijri ) { 01196 $hijri = self::tsToHijri( $ts ); 01197 } 01198 $num = $hijri[2]; 01199 break; 01200 case 'xjj': 01201 if ( !$hebrew ) { 01202 $hebrew = self::tsToHebrew( $ts ); 01203 } 01204 $num = $hebrew[2]; 01205 break; 01206 case 'l': 01207 if ( !$dateTimeObj ) { 01208 $dateTimeObj = DateTime::createFromFormat( 01209 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01210 ); 01211 } 01212 $s .= $this->getWeekdayName( $dateTimeObj->format( 'w' ) + 1 ); 01213 break; 01214 case 'F': 01215 $s .= $this->getMonthName( substr( $ts, 4, 2 ) ); 01216 break; 01217 case 'xiF': 01218 if ( !$iranian ) { 01219 $iranian = self::tsToIranian( $ts ); 01220 } 01221 $s .= $this->getIranianCalendarMonthName( $iranian[1] ); 01222 break; 01223 case 'xmF': 01224 if ( !$hijri ) { 01225 $hijri = self::tsToHijri( $ts ); 01226 } 01227 $s .= $this->getHijriCalendarMonthName( $hijri[1] ); 01228 break; 01229 case 'xjF': 01230 if ( !$hebrew ) { 01231 $hebrew = self::tsToHebrew( $ts ); 01232 } 01233 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] ); 01234 break; 01235 case 'm': 01236 $num = substr( $ts, 4, 2 ); 01237 break; 01238 case 'M': 01239 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) ); 01240 break; 01241 case 'n': 01242 $num = intval( substr( $ts, 4, 2 ) ); 01243 break; 01244 case 'xin': 01245 if ( !$iranian ) { 01246 $iranian = self::tsToIranian( $ts ); 01247 } 01248 $num = $iranian[1]; 01249 break; 01250 case 'xmn': 01251 if ( !$hijri ) { 01252 $hijri = self::tsToHijri ( $ts ); 01253 } 01254 $num = $hijri[1]; 01255 break; 01256 case 'xjn': 01257 if ( !$hebrew ) { 01258 $hebrew = self::tsToHebrew( $ts ); 01259 } 01260 $num = $hebrew[1]; 01261 break; 01262 case 'xjt': 01263 if ( !$hebrew ) { 01264 $hebrew = self::tsToHebrew( $ts ); 01265 } 01266 $num = $hebrew[3]; 01267 break; 01268 case 'Y': 01269 $num = substr( $ts, 0, 4 ); 01270 break; 01271 case 'xiY': 01272 if ( !$iranian ) { 01273 $iranian = self::tsToIranian( $ts ); 01274 } 01275 $num = $iranian[0]; 01276 break; 01277 case 'xmY': 01278 if ( !$hijri ) { 01279 $hijri = self::tsToHijri( $ts ); 01280 } 01281 $num = $hijri[0]; 01282 break; 01283 case 'xjY': 01284 if ( !$hebrew ) { 01285 $hebrew = self::tsToHebrew( $ts ); 01286 } 01287 $num = $hebrew[0]; 01288 break; 01289 case 'xkY': 01290 if ( !$thai ) { 01291 $thai = self::tsToYear( $ts, 'thai' ); 01292 } 01293 $num = $thai[0]; 01294 break; 01295 case 'xoY': 01296 if ( !$minguo ) { 01297 $minguo = self::tsToYear( $ts, 'minguo' ); 01298 } 01299 $num = $minguo[0]; 01300 break; 01301 case 'xtY': 01302 if ( !$tenno ) { 01303 $tenno = self::tsToYear( $ts, 'tenno' ); 01304 } 01305 $num = $tenno[0]; 01306 break; 01307 case 'y': 01308 $num = substr( $ts, 2, 2 ); 01309 break; 01310 case 'xiy': 01311 if ( !$iranian ) { 01312 $iranian = self::tsToIranian( $ts ); 01313 } 01314 $num = substr( $iranian[0], -2 ); 01315 break; 01316 case 'a': 01317 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm'; 01318 break; 01319 case 'A': 01320 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM'; 01321 break; 01322 case 'g': 01323 $h = substr( $ts, 8, 2 ); 01324 $num = $h % 12 ? $h % 12 : 12; 01325 break; 01326 case 'G': 01327 $num = intval( substr( $ts, 8, 2 ) ); 01328 break; 01329 case 'h': 01330 $h = substr( $ts, 8, 2 ); 01331 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 ); 01332 break; 01333 case 'H': 01334 $num = substr( $ts, 8, 2 ); 01335 break; 01336 case 'i': 01337 $num = substr( $ts, 10, 2 ); 01338 break; 01339 case 's': 01340 $num = substr( $ts, 12, 2 ); 01341 break; 01342 case 'c': 01343 case 'r': 01344 case 'e': 01345 case 'O': 01346 case 'P': 01347 case 'T': 01348 // Pass through string from $dateTimeObj->format() 01349 if ( !$dateTimeObj ) { 01350 $dateTimeObj = DateTime::createFromFormat( 01351 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01352 ); 01353 } 01354 $s .= $dateTimeObj->format( $code ); 01355 break; 01356 case 'w': 01357 case 'N': 01358 case 'z': 01359 case 'W': 01360 case 't': 01361 case 'L': 01362 case 'o': 01363 case 'U': 01364 case 'I': 01365 case 'Z': 01366 // Pass through number from $dateTimeObj->format() 01367 if ( !$dateTimeObj ) { 01368 $dateTimeObj = DateTime::createFromFormat( 01369 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01370 ); 01371 } 01372 $num = $dateTimeObj->format( $code ); 01373 break; 01374 case '\\': 01375 # Backslash escaping 01376 if ( $p < strlen( $format ) - 1 ) { 01377 $s .= $format[++$p]; 01378 } else { 01379 $s .= '\\'; 01380 } 01381 break; 01382 case '"': 01383 # Quoted literal 01384 if ( $p < strlen( $format ) - 1 ) { 01385 $endQuote = strpos( $format, '"', $p + 1 ); 01386 if ( $endQuote === false ) { 01387 # No terminating quote, assume literal " 01388 $s .= '"'; 01389 } else { 01390 $s .= substr( $format, $p + 1, $endQuote - $p - 1 ); 01391 $p = $endQuote; 01392 } 01393 } else { 01394 # Quote at end of string, assume literal " 01395 $s .= '"'; 01396 } 01397 break; 01398 default: 01399 $s .= $format[$p]; 01400 } 01401 if ( $num !== false ) { 01402 if ( $rawToggle || $raw ) { 01403 $s .= $num; 01404 $raw = false; 01405 } elseif ( $roman ) { 01406 $s .= Language::romanNumeral( $num ); 01407 $roman = false; 01408 } elseif ( $hebrewNum ) { 01409 $s .= self::hebrewNumeral( $num ); 01410 $hebrewNum = false; 01411 } else { 01412 $s .= $this->formatNum( $num, true ); 01413 } 01414 } 01415 } 01416 return $s; 01417 } 01418 01419 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); 01420 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ); 01421 01434 private static function tsToIranian( $ts ) { 01435 $gy = substr( $ts, 0, 4 ) -1600; 01436 $gm = substr( $ts, 4, 2 ) -1; 01437 $gd = substr( $ts, 6, 2 ) -1; 01438 01439 # Days passed from the beginning (including leap years) 01440 $gDayNo = 365 * $gy 01441 + floor( ( $gy + 3 ) / 4 ) 01442 - floor( ( $gy + 99 ) / 100 ) 01443 + floor( ( $gy + 399 ) / 400 ); 01444 01445 // Add days of the past months of this year 01446 for ( $i = 0; $i < $gm; $i++ ) { 01447 $gDayNo += self::$GREG_DAYS[$i]; 01448 } 01449 01450 // Leap years 01451 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) { 01452 $gDayNo++; 01453 } 01454 01455 // Days passed in current month 01456 $gDayNo += (int)$gd; 01457 01458 $jDayNo = $gDayNo - 79; 01459 01460 $jNp = floor( $jDayNo / 12053 ); 01461 $jDayNo %= 12053; 01462 01463 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 ); 01464 $jDayNo %= 1461; 01465 01466 if ( $jDayNo >= 366 ) { 01467 $jy += floor( ( $jDayNo - 1 ) / 365 ); 01468 $jDayNo = floor( ( $jDayNo - 1 ) % 365 ); 01469 } 01470 01471 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) { 01472 $jDayNo -= self::$IRANIAN_DAYS[$i]; 01473 } 01474 01475 $jm = $i + 1; 01476 $jd = $jDayNo + 1; 01477 01478 return array( $jy, $jm, $jd ); 01479 } 01480 01492 private static function tsToHijri( $ts ) { 01493 $year = substr( $ts, 0, 4 ); 01494 $month = substr( $ts, 4, 2 ); 01495 $day = substr( $ts, 6, 2 ); 01496 01497 $zyr = $year; 01498 $zd = $day; 01499 $zm = $month; 01500 $zy = $zyr; 01501 01502 if ( 01503 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) || 01504 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) ) 01505 ) 01506 { 01507 $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) + 01508 (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) - 01509 (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) + 01510 $zd - 32075; 01511 } else { 01512 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) + 01513 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777; 01514 } 01515 01516 $zl = $zjd -1948440 + 10632; 01517 $zn = (int)( ( $zl - 1 ) / 10631 ); 01518 $zl = $zl - 10631 * $zn + 354; 01519 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) + ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) ); 01520 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) - ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29; 01521 $zm = (int)( ( 24 * $zl ) / 709 ); 01522 $zd = $zl - (int)( ( 709 * $zm ) / 24 ); 01523 $zy = 30 * $zn + $zj - 30; 01524 01525 return array( $zy, $zm, $zd ); 01526 } 01527 01543 private static function tsToHebrew( $ts ) { 01544 # Parse date 01545 $year = substr( $ts, 0, 4 ); 01546 $month = substr( $ts, 4, 2 ); 01547 $day = substr( $ts, 6, 2 ); 01548 01549 # Calculate Hebrew year 01550 $hebrewYear = $year + 3760; 01551 01552 # Month number when September = 1, August = 12 01553 $month += 4; 01554 if ( $month > 12 ) { 01555 # Next year 01556 $month -= 12; 01557 $year++; 01558 $hebrewYear++; 01559 } 01560 01561 # Calculate day of year from 1 September 01562 $dayOfYear = $day; 01563 for ( $i = 1; $i < $month; $i++ ) { 01564 if ( $i == 6 ) { 01565 # February 01566 $dayOfYear += 28; 01567 # Check if the year is leap 01568 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) { 01569 $dayOfYear++; 01570 } 01571 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) { 01572 $dayOfYear += 30; 01573 } else { 01574 $dayOfYear += 31; 01575 } 01576 } 01577 01578 # Calculate the start of the Hebrew year 01579 $start = self::hebrewYearStart( $hebrewYear ); 01580 01581 # Calculate next year's start 01582 if ( $dayOfYear <= $start ) { 01583 # Day is before the start of the year - it is the previous year 01584 # Next year's start 01585 $nextStart = $start; 01586 # Previous year 01587 $year--; 01588 $hebrewYear--; 01589 # Add days since previous year's 1 September 01590 $dayOfYear += 365; 01591 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) { 01592 # Leap year 01593 $dayOfYear++; 01594 } 01595 # Start of the new (previous) year 01596 $start = self::hebrewYearStart( $hebrewYear ); 01597 } else { 01598 # Next year's start 01599 $nextStart = self::hebrewYearStart( $hebrewYear + 1 ); 01600 } 01601 01602 # Calculate Hebrew day of year 01603 $hebrewDayOfYear = $dayOfYear - $start; 01604 01605 # Difference between year's days 01606 $diff = $nextStart - $start; 01607 # Add 12 (or 13 for leap years) days to ignore the difference between 01608 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the 01609 # difference is only about the year type 01610 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) { 01611 $diff += 13; 01612 } else { 01613 $diff += 12; 01614 } 01615 01616 # Check the year pattern, and is leap year 01617 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year 01618 # This is mod 30, to work on both leap years (which add 30 days of Adar I) 01619 # and non-leap years 01620 $yearPattern = $diff % 30; 01621 # Check if leap year 01622 $isLeap = $diff >= 30; 01623 01624 # Calculate day in the month from number of day in the Hebrew year 01625 # Don't check Adar - if the day is not in Adar, we will stop before; 01626 # if it is in Adar, we will use it to check if it is Adar I or Adar II 01627 $hebrewDay = $hebrewDayOfYear; 01628 $hebrewMonth = 1; 01629 $days = 0; 01630 while ( $hebrewMonth <= 12 ) { 01631 # Calculate days in this month 01632 if ( $isLeap && $hebrewMonth == 6 ) { 01633 # Adar in a leap year 01634 if ( $isLeap ) { 01635 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days 01636 $days = 30; 01637 if ( $hebrewDay <= $days ) { 01638 # Day in Adar I 01639 $hebrewMonth = 13; 01640 } else { 01641 # Subtract the days of Adar I 01642 $hebrewDay -= $days; 01643 # Try Adar II 01644 $days = 29; 01645 if ( $hebrewDay <= $days ) { 01646 # Day in Adar II 01647 $hebrewMonth = 14; 01648 } 01649 } 01650 } 01651 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) { 01652 # Cheshvan in a complete year (otherwise as the rule below) 01653 $days = 30; 01654 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) { 01655 # Kislev in an incomplete year (otherwise as the rule below) 01656 $days = 29; 01657 } else { 01658 # Odd months have 30 days, even have 29 01659 $days = 30 - ( $hebrewMonth - 1 ) % 2; 01660 } 01661 if ( $hebrewDay <= $days ) { 01662 # In the current month 01663 break; 01664 } else { 01665 # Subtract the days of the current month 01666 $hebrewDay -= $days; 01667 # Try in the next month 01668 $hebrewMonth++; 01669 } 01670 } 01671 01672 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days ); 01673 } 01674 01684 private static function hebrewYearStart( $year ) { 01685 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 ); 01686 $b = intval( ( $year - 1 ) % 4 ); 01687 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 ); 01688 if ( $m < 0 ) { 01689 $m--; 01690 } 01691 $Mar = intval( $m ); 01692 if ( $m < 0 ) { 01693 $m++; 01694 } 01695 $m -= $Mar; 01696 01697 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 ); 01698 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) { 01699 $Mar++; 01700 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) { 01701 $Mar += 2; 01702 } elseif ( $c == 2 || $c == 4 || $c == 6 ) { 01703 $Mar++; 01704 } 01705 01706 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24; 01707 return $Mar; 01708 } 01709 01722 private static function tsToYear( $ts, $cName ) { 01723 $gy = substr( $ts, 0, 4 ); 01724 $gm = substr( $ts, 4, 2 ); 01725 $gd = substr( $ts, 6, 2 ); 01726 01727 if ( !strcmp( $cName, 'thai' ) ) { 01728 # Thai solar dates 01729 # Add 543 years to the Gregorian calendar 01730 # Months and days are identical 01731 $gy_offset = $gy + 543; 01732 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) { 01733 # Minguo dates 01734 # Deduct 1911 years from the Gregorian calendar 01735 # Months and days are identical 01736 $gy_offset = $gy - 1911; 01737 } elseif ( !strcmp( $cName, 'tenno' ) ) { 01738 # Nengō dates up to Meiji period 01739 # Deduct years from the Gregorian calendar 01740 # depending on the nengo periods 01741 # Months and days are identical 01742 if ( ( $gy < 1912 ) || ( ( $gy == 1912 ) && ( $gm < 7 ) ) || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) ) ) { 01743 # Meiji period 01744 $gy_gannen = $gy - 1868 + 1; 01745 $gy_offset = $gy_gannen; 01746 if ( $gy_gannen == 1 ) { 01747 $gy_offset = '元'; 01748 } 01749 $gy_offset = '明治' . $gy_offset; 01750 } elseif ( 01751 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) || 01752 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) || 01753 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) || 01754 ( ( $gy == 1926 ) && ( $gm < 12 ) ) || 01755 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) ) 01756 ) 01757 { 01758 # Taishō period 01759 $gy_gannen = $gy - 1912 + 1; 01760 $gy_offset = $gy_gannen; 01761 if ( $gy_gannen == 1 ) { 01762 $gy_offset = '元'; 01763 } 01764 $gy_offset = '大正' . $gy_offset; 01765 } elseif ( 01766 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) || 01767 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) || 01768 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) ) 01769 ) 01770 { 01771 # Shōwa period 01772 $gy_gannen = $gy - 1926 + 1; 01773 $gy_offset = $gy_gannen; 01774 if ( $gy_gannen == 1 ) { 01775 $gy_offset = '元'; 01776 } 01777 $gy_offset = '昭和' . $gy_offset; 01778 } else { 01779 # Heisei period 01780 $gy_gannen = $gy - 1989 + 1; 01781 $gy_offset = $gy_gannen; 01782 if ( $gy_gannen == 1 ) { 01783 $gy_offset = '元'; 01784 } 01785 $gy_offset = '平成' . $gy_offset; 01786 } 01787 } else { 01788 $gy_offset = $gy; 01789 } 01790 01791 return array( $gy_offset, $gm, $gd ); 01792 } 01793 01801 static function romanNumeral( $num ) { 01802 static $table = array( 01803 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ), 01804 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ), 01805 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ), 01806 array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM', 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ) 01807 ); 01808 01809 $num = intval( $num ); 01810 if ( $num > 10000 || $num <= 0 ) { 01811 return $num; 01812 } 01813 01814 $s = ''; 01815 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) { 01816 if ( $num >= $pow10 ) { 01817 $s .= $table[$i][(int)floor( $num / $pow10 )]; 01818 } 01819 $num = $num % $pow10; 01820 } 01821 return $s; 01822 } 01823 01831 static function hebrewNumeral( $num ) { 01832 static $table = array( 01833 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ), 01834 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ), 01835 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ), 01836 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ) 01837 ); 01838 01839 $num = intval( $num ); 01840 if ( $num > 9999 || $num <= 0 ) { 01841 return $num; 01842 } 01843 01844 $s = ''; 01845 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) { 01846 if ( $num >= $pow10 ) { 01847 if ( $num == 15 || $num == 16 ) { 01848 $s .= $table[0][9] . $table[0][$num - 9]; 01849 $num = 0; 01850 } else { 01851 $s .= $table[$i][intval( ( $num / $pow10 ) )]; 01852 if ( $pow10 == 1000 ) { 01853 $s .= "'"; 01854 } 01855 } 01856 } 01857 $num = $num % $pow10; 01858 } 01859 if ( strlen( $s ) == 2 ) { 01860 $str = $s . "'"; 01861 } else { 01862 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"'; 01863 $str .= substr( $s, strlen( $s ) - 2, 2 ); 01864 } 01865 $start = substr( $str, 0, strlen( $str ) - 2 ); 01866 $end = substr( $str, strlen( $str ) - 2 ); 01867 switch ( $end ) { 01868 case 'כ': 01869 $str = $start . 'ך'; 01870 break; 01871 case 'מ': 01872 $str = $start . 'ם'; 01873 break; 01874 case 'נ': 01875 $str = $start . 'ן'; 01876 break; 01877 case 'פ': 01878 $str = $start . 'ף'; 01879 break; 01880 case 'צ': 01881 $str = $start . 'ץ'; 01882 break; 01883 } 01884 return $str; 01885 } 01886 01895 function userAdjust( $ts, $tz = false ) { 01896 global $wgUser, $wgLocalTZoffset; 01897 01898 if ( $tz === false ) { 01899 $tz = $wgUser->getOption( 'timecorrection' ); 01900 } 01901 01902 $data = explode( '|', $tz, 3 ); 01903 01904 if ( $data[0] == 'ZoneInfo' ) { 01905 wfSuppressWarnings(); 01906 $userTZ = timezone_open( $data[2] ); 01907 wfRestoreWarnings(); 01908 if ( $userTZ !== false ) { 01909 $date = date_create( $ts, timezone_open( 'UTC' ) ); 01910 date_timezone_set( $date, $userTZ ); 01911 $date = date_format( $date, 'YmdHis' ); 01912 return $date; 01913 } 01914 # Unrecognized timezone, default to 'Offset' with the stored offset. 01915 $data[0] = 'Offset'; 01916 } 01917 01918 $minDiff = 0; 01919 if ( $data[0] == 'System' || $tz == '' ) { 01920 # Global offset in minutes. 01921 if ( isset( $wgLocalTZoffset ) ) { 01922 $minDiff = $wgLocalTZoffset; 01923 } 01924 } elseif ( $data[0] == 'Offset' ) { 01925 $minDiff = intval( $data[1] ); 01926 } else { 01927 $data = explode( ':', $tz ); 01928 if ( count( $data ) == 2 ) { 01929 $data[0] = intval( $data[0] ); 01930 $data[1] = intval( $data[1] ); 01931 $minDiff = abs( $data[0] ) * 60 + $data[1]; 01932 if ( $data[0] < 0 ) { 01933 $minDiff = -$minDiff; 01934 } 01935 } else { 01936 $minDiff = intval( $data[0] ) * 60; 01937 } 01938 } 01939 01940 # No difference ? Return time unchanged 01941 if ( 0 == $minDiff ) { 01942 return $ts; 01943 } 01944 01945 wfSuppressWarnings(); // E_STRICT system time bitching 01946 # Generate an adjusted date; take advantage of the fact that mktime 01947 # will normalize out-of-range values so we don't have to split $minDiff 01948 # into hours and minutes. 01949 $t = mktime( ( 01950 (int)substr( $ts, 8, 2 ) ), # Hours 01951 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes 01952 (int)substr( $ts, 12, 2 ), # Seconds 01953 (int)substr( $ts, 4, 2 ), # Month 01954 (int)substr( $ts, 6, 2 ), # Day 01955 (int)substr( $ts, 0, 4 ) ); # Year 01956 01957 $date = date( 'YmdHis', $t ); 01958 wfRestoreWarnings(); 01959 01960 return $date; 01961 } 01962 01980 function dateFormat( $usePrefs = true ) { 01981 global $wgUser; 01982 01983 if ( is_bool( $usePrefs ) ) { 01984 if ( $usePrefs ) { 01985 $datePreference = $wgUser->getDatePreference(); 01986 } else { 01987 $datePreference = (string)User::getDefaultOption( 'date' ); 01988 } 01989 } else { 01990 $datePreference = (string)$usePrefs; 01991 } 01992 01993 // return int 01994 if ( $datePreference == '' ) { 01995 return 'default'; 01996 } 01997 01998 return $datePreference; 01999 } 02000 02010 function getDateFormatString( $type, $pref ) { 02011 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) { 02012 if ( $pref == 'default' ) { 02013 $pref = $this->getDefaultDateFormat(); 02014 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02015 } else { 02016 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02017 02018 if ( $type === 'pretty' && $df === null ) { 02019 $df = $this->getDateFormatString( 'date', $pref ); 02020 } 02021 02022 if ( $df === null ) { 02023 $pref = $this->getDefaultDateFormat(); 02024 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02025 } 02026 } 02027 $this->dateFormatStrings[$type][$pref] = $df; 02028 } 02029 return $this->dateFormatStrings[$type][$pref]; 02030 } 02031 02042 function date( $ts, $adj = false, $format = true, $timecorrection = false ) { 02043 $ts = wfTimestamp( TS_MW, $ts ); 02044 if ( $adj ) { 02045 $ts = $this->userAdjust( $ts, $timecorrection ); 02046 } 02047 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) ); 02048 return $this->sprintfDate( $df, $ts ); 02049 } 02050 02061 function time( $ts, $adj = false, $format = true, $timecorrection = false ) { 02062 $ts = wfTimestamp( TS_MW, $ts ); 02063 if ( $adj ) { 02064 $ts = $this->userAdjust( $ts, $timecorrection ); 02065 } 02066 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) ); 02067 return $this->sprintfDate( $df, $ts ); 02068 } 02069 02081 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) { 02082 $ts = wfTimestamp( TS_MW, $ts ); 02083 if ( $adj ) { 02084 $ts = $this->userAdjust( $ts, $timecorrection ); 02085 } 02086 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) ); 02087 return $this->sprintfDate( $df, $ts ); 02088 } 02089 02100 public function formatDuration( $seconds, array $chosenIntervals = array() ) { 02101 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals ); 02102 02103 $segments = array(); 02104 02105 foreach ( $intervals as $intervalName => $intervalValue ) { 02106 // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks, 02107 // duration-years, duration-decades, duration-centuries, duration-millennia 02108 $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue ); 02109 $segments[] = $message->inLanguage( $this )->escaped(); 02110 } 02111 02112 return $this->listToText( $segments ); 02113 } 02114 02126 public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) { 02127 if ( empty( $chosenIntervals ) ) { 02128 $chosenIntervals = array( 'millennia', 'centuries', 'decades', 'years', 'days', 'hours', 'minutes', 'seconds' ); 02129 } 02130 02131 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) ); 02132 $sortedNames = array_keys( $intervals ); 02133 $smallestInterval = array_pop( $sortedNames ); 02134 02135 $segments = array(); 02136 02137 foreach ( $intervals as $name => $length ) { 02138 $value = floor( $seconds / $length ); 02139 02140 if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) { 02141 $seconds -= $value * $length; 02142 $segments[$name] = $value; 02143 } 02144 } 02145 02146 return $segments; 02147 } 02148 02168 private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) { 02169 $ts = wfTimestamp( TS_MW, $ts ); 02170 $options += array( 'timecorrection' => true, 'format' => true ); 02171 if ( $options['timecorrection'] !== false ) { 02172 if ( $options['timecorrection'] === true ) { 02173 $offset = $user->getOption( 'timecorrection' ); 02174 } else { 02175 $offset = $options['timecorrection']; 02176 } 02177 $ts = $this->userAdjust( $ts, $offset ); 02178 } 02179 if ( $options['format'] === true ) { 02180 $format = $user->getDatePreference(); 02181 } else { 02182 $format = $options['format']; 02183 } 02184 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) ); 02185 return $this->sprintfDate( $df, $ts ); 02186 } 02187 02207 public function userDate( $ts, User $user, array $options = array() ) { 02208 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options ); 02209 } 02210 02230 public function userTime( $ts, User $user, array $options = array() ) { 02231 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options ); 02232 } 02233 02253 public function userTimeAndDate( $ts, User $user, array $options = array() ) { 02254 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options ); 02255 } 02256 02272 public function getHumanTimestamp( MWTimestamp $ts, MWTimestamp $relativeTo, User $user ) { 02273 $diff = $ts->diff( $relativeTo ); 02274 $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) - (int)$relativeTo->timestamp->format( 'w' ) ); 02275 $days = $diff->days ?: (int)$diffDay; 02276 if ( $diff->invert || $days > 5 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' ) ) { 02277 // Timestamps are in different years: use full timestamp 02278 // Also do full timestamp for future dates 02282 $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' ); 02283 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ); 02284 } elseif ( $days > 5 ) { 02285 // Timestamps are in same year, but more than 5 days ago: show day and month only. 02286 $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' ); 02287 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ); 02288 } elseif ( $days > 1 ) { 02289 // Timestamp within the past week: show the day of the week and time 02290 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02291 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )]; 02292 // Messages: 02293 // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at 02294 $ts = wfMessage( "$weekday-at" ) 02295 ->inLanguage( $this ) 02296 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02297 ->text(); 02298 } elseif ( $days == 1 ) { 02299 // Timestamp was yesterday: say 'yesterday' and the time. 02300 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02301 $ts = wfMessage( 'yesterday-at' ) 02302 ->inLanguage( $this ) 02303 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02304 ->text(); 02305 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) { 02306 // Timestamp was today, but more than 90 minutes ago: say 'today' and the time. 02307 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02308 $ts = wfMessage( 'today-at' ) 02309 ->inLanguage( $this ) 02310 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02311 ->text(); 02312 02313 // From here on in, the timestamp was soon enough ago so that we can simply say 02314 // XX units ago, e.g., "2 hours ago" or "5 minutes ago" 02315 } elseif ( $diff->h == 1 ) { 02316 // Less than 90 minutes, but more than an hour ago. 02317 $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text(); 02318 } elseif ( $diff->i >= 1 ) { 02319 // A few minutes ago. 02320 $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text(); 02321 } elseif ( $diff->s >= 30 ) { 02322 // Less than a minute, but more than 30 sec ago. 02323 $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text(); 02324 } else { 02325 // Less than 30 seconds ago. 02326 $ts = wfMessage( 'just-now' )->text(); 02327 } 02328 02329 return $ts; 02330 } 02331 02336 function getMessage( $key ) { 02337 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key ); 02338 } 02339 02343 function getAllMessages() { 02344 return self::$dataCache->getItem( $this->mCode, 'messages' ); 02345 } 02346 02353 function iconv( $in, $out, $string ) { 02354 # This is a wrapper for iconv in all languages except esperanto, 02355 # which does some nasty x-conversions beforehand 02356 02357 # Even with //IGNORE iconv can whine about illegal characters in 02358 # *input* string. We just ignore those too. 02359 # REF: http://bugs.php.net/bug.php?id=37166 02360 # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885 02361 wfSuppressWarnings(); 02362 $text = iconv( $in, $out . '//IGNORE', $string ); 02363 wfRestoreWarnings(); 02364 return $text; 02365 } 02366 02367 // callback functions for uc(), lc(), ucwords(), ucwordbreaks() 02368 02373 function ucwordbreaksCallbackAscii( $matches ) { 02374 return $this->ucfirst( $matches[1] ); 02375 } 02376 02381 function ucwordbreaksCallbackMB( $matches ) { 02382 return mb_strtoupper( $matches[0] ); 02383 } 02384 02389 function ucCallback( $matches ) { 02390 list( $wikiUpperChars ) = self::getCaseMaps(); 02391 return strtr( $matches[1], $wikiUpperChars ); 02392 } 02393 02398 function lcCallback( $matches ) { 02399 list( , $wikiLowerChars ) = self::getCaseMaps(); 02400 return strtr( $matches[1], $wikiLowerChars ); 02401 } 02402 02407 function ucwordsCallbackMB( $matches ) { 02408 return mb_strtoupper( $matches[0] ); 02409 } 02410 02415 function ucwordsCallbackWiki( $matches ) { 02416 list( $wikiUpperChars ) = self::getCaseMaps(); 02417 return strtr( $matches[0], $wikiUpperChars ); 02418 } 02419 02427 function ucfirst( $str ) { 02428 $o = ord( $str ); 02429 if ( $o < 96 ) { // if already uppercase... 02430 return $str; 02431 } elseif ( $o < 128 ) { 02432 return ucfirst( $str ); // use PHP's ucfirst() 02433 } else { 02434 // fall back to more complex logic in case of multibyte strings 02435 return $this->uc( $str, true ); 02436 } 02437 } 02438 02447 function uc( $str, $first = false ) { 02448 if ( function_exists( 'mb_strtoupper' ) ) { 02449 if ( $first ) { 02450 if ( $this->isMultibyte( $str ) ) { 02451 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 ); 02452 } else { 02453 return ucfirst( $str ); 02454 } 02455 } else { 02456 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str ); 02457 } 02458 } else { 02459 if ( $this->isMultibyte( $str ) ) { 02460 $x = $first ? '^' : ''; 02461 return preg_replace_callback( 02462 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/", 02463 array( $this, 'ucCallback' ), 02464 $str 02465 ); 02466 } else { 02467 return $first ? ucfirst( $str ) : strtoupper( $str ); 02468 } 02469 } 02470 } 02471 02476 function lcfirst( $str ) { 02477 $o = ord( $str ); 02478 if ( !$o ) { 02479 return strval( $str ); 02480 } elseif ( $o >= 128 ) { 02481 return $this->lc( $str, true ); 02482 } elseif ( $o > 96 ) { 02483 return $str; 02484 } else { 02485 $str[0] = strtolower( $str[0] ); 02486 return $str; 02487 } 02488 } 02489 02495 function lc( $str, $first = false ) { 02496 if ( function_exists( 'mb_strtolower' ) ) { 02497 if ( $first ) { 02498 if ( $this->isMultibyte( $str ) ) { 02499 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 ); 02500 } else { 02501 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ); 02502 } 02503 } else { 02504 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str ); 02505 } 02506 } else { 02507 if ( $this->isMultibyte( $str ) ) { 02508 $x = $first ? '^' : ''; 02509 return preg_replace_callback( 02510 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/", 02511 array( $this, 'lcCallback' ), 02512 $str 02513 ); 02514 } else { 02515 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str ); 02516 } 02517 } 02518 } 02519 02524 function isMultibyte( $str ) { 02525 return (bool)preg_match( '/[\x80-\xff]/', $str ); 02526 } 02527 02532 function ucwords( $str ) { 02533 if ( $this->isMultibyte( $str ) ) { 02534 $str = $this->lc( $str ); 02535 02536 // regexp to find first letter in each word (i.e. after each space) 02537 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/"; 02538 02539 // function to use to capitalize a single char 02540 if ( function_exists( 'mb_strtoupper' ) ) { 02541 return preg_replace_callback( 02542 $replaceRegexp, 02543 array( $this, 'ucwordsCallbackMB' ), 02544 $str 02545 ); 02546 } else { 02547 return preg_replace_callback( 02548 $replaceRegexp, 02549 array( $this, 'ucwordsCallbackWiki' ), 02550 $str 02551 ); 02552 } 02553 } else { 02554 return ucwords( strtolower( $str ) ); 02555 } 02556 } 02557 02564 function ucwordbreaks( $str ) { 02565 if ( $this->isMultibyte( $str ) ) { 02566 $str = $this->lc( $str ); 02567 02568 // since \b doesn't work for UTF-8, we explicitely define word break chars 02569 $breaks = "[ \-\(\)\}\{\.,\?!]"; 02570 02571 // find first letter after word break 02572 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/"; 02573 02574 if ( function_exists( 'mb_strtoupper' ) ) { 02575 return preg_replace_callback( 02576 $replaceRegexp, 02577 array( $this, 'ucwordbreaksCallbackMB' ), 02578 $str 02579 ); 02580 } else { 02581 return preg_replace_callback( 02582 $replaceRegexp, 02583 array( $this, 'ucwordsCallbackWiki' ), 02584 $str 02585 ); 02586 } 02587 } else { 02588 return preg_replace_callback( 02589 '/\b([\w\x80-\xff]+)\b/', 02590 array( $this, 'ucwordbreaksCallbackAscii' ), 02591 $str 02592 ); 02593 } 02594 } 02595 02611 function caseFold( $s ) { 02612 return $this->uc( $s ); 02613 } 02614 02619 function checkTitleEncoding( $s ) { 02620 if ( is_array( $s ) ) { 02621 throw new MWException( 'Given array to checkTitleEncoding.' ); 02622 } 02623 if ( StringUtils::isUtf8( $s ) ) { 02624 return $s; 02625 } 02626 02627 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s ); 02628 } 02629 02633 function fallback8bitEncoding() { 02634 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' ); 02635 } 02636 02645 function hasWordBreaks() { 02646 return true; 02647 } 02648 02656 function segmentByWord( $string ) { 02657 return $string; 02658 } 02659 02667 function normalizeForSearch( $string ) { 02668 return self::convertDoubleWidth( $string ); 02669 } 02670 02679 protected static function convertDoubleWidth( $string ) { 02680 static $full = null; 02681 static $half = null; 02682 02683 if ( $full === null ) { 02684 $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 02685 $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 02686 $full = str_split( $fullWidth, 3 ); 02687 $half = str_split( $halfWidth ); 02688 } 02689 02690 $string = str_replace( $full, $half, $string ); 02691 return $string; 02692 } 02693 02699 protected static function insertSpace( $string, $pattern ) { 02700 $string = preg_replace( $pattern, " $1 ", $string ); 02701 $string = preg_replace( '/ +/', ' ', $string ); 02702 return $string; 02703 } 02704 02709 function convertForSearchResult( $termsArray ) { 02710 # some languages, e.g. Chinese, need to do a conversion 02711 # in order for search results to be displayed correctly 02712 return $termsArray; 02713 } 02714 02721 function firstChar( $s ) { 02722 $matches = array(); 02723 preg_match( 02724 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' . 02725 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', 02726 $s, 02727 $matches 02728 ); 02729 02730 if ( isset( $matches[1] ) ) { 02731 if ( strlen( $matches[1] ) != 3 ) { 02732 return $matches[1]; 02733 } 02734 02735 // Break down Hangul syllables to grab the first jamo 02736 $code = utf8ToCodepoint( $matches[1] ); 02737 if ( $code < 0xac00 || 0xd7a4 <= $code ) { 02738 return $matches[1]; 02739 } elseif ( $code < 0xb098 ) { 02740 return "\xe3\x84\xb1"; 02741 } elseif ( $code < 0xb2e4 ) { 02742 return "\xe3\x84\xb4"; 02743 } elseif ( $code < 0xb77c ) { 02744 return "\xe3\x84\xb7"; 02745 } elseif ( $code < 0xb9c8 ) { 02746 return "\xe3\x84\xb9"; 02747 } elseif ( $code < 0xbc14 ) { 02748 return "\xe3\x85\x81"; 02749 } elseif ( $code < 0xc0ac ) { 02750 return "\xe3\x85\x82"; 02751 } elseif ( $code < 0xc544 ) { 02752 return "\xe3\x85\x85"; 02753 } elseif ( $code < 0xc790 ) { 02754 return "\xe3\x85\x87"; 02755 } elseif ( $code < 0xcc28 ) { 02756 return "\xe3\x85\x88"; 02757 } elseif ( $code < 0xce74 ) { 02758 return "\xe3\x85\x8a"; 02759 } elseif ( $code < 0xd0c0 ) { 02760 return "\xe3\x85\x8b"; 02761 } elseif ( $code < 0xd30c ) { 02762 return "\xe3\x85\x8c"; 02763 } elseif ( $code < 0xd558 ) { 02764 return "\xe3\x85\x8d"; 02765 } else { 02766 return "\xe3\x85\x8e"; 02767 } 02768 } else { 02769 return ''; 02770 } 02771 } 02772 02773 function initEncoding() { 02774 # Some languages may have an alternate char encoding option 02775 # (Esperanto X-coding, Japanese furigana conversion, etc) 02776 # If this language is used as the primary content language, 02777 # an override to the defaults can be set here on startup. 02778 } 02779 02784 function recodeForEdit( $s ) { 02785 # For some languages we'll want to explicitly specify 02786 # which characters make it into the edit box raw 02787 # or are converted in some way or another. 02788 global $wgEditEncoding; 02789 if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) { 02790 return $s; 02791 } else { 02792 return $this->iconv( 'UTF-8', $wgEditEncoding, $s ); 02793 } 02794 } 02795 02800 function recodeInput( $s ) { 02801 # Take the previous into account. 02802 global $wgEditEncoding; 02803 if ( $wgEditEncoding != '' ) { 02804 $enc = $wgEditEncoding; 02805 } else { 02806 $enc = 'UTF-8'; 02807 } 02808 if ( $enc == 'UTF-8' ) { 02809 return $s; 02810 } else { 02811 return $this->iconv( $enc, 'UTF-8', $s ); 02812 } 02813 } 02814 02826 function normalize( $s ) { 02827 global $wgAllUnicodeFixes; 02828 $s = UtfNormal::cleanUp( $s ); 02829 if ( $wgAllUnicodeFixes ) { 02830 $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s ); 02831 $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s ); 02832 } 02833 02834 return $s; 02835 } 02836 02851 function transformUsingPairFile( $file, $string ) { 02852 if ( !isset( $this->transformData[$file] ) ) { 02853 $data = wfGetPrecompiledData( $file ); 02854 if ( $data === false ) { 02855 throw new MWException( __METHOD__ . ": The transformation file $file is missing" ); 02856 } 02857 $this->transformData[$file] = new ReplacementArray( $data ); 02858 } 02859 return $this->transformData[$file]->replace( $string ); 02860 } 02861 02867 function isRTL() { 02868 return self::$dataCache->getItem( $this->mCode, 'rtl' ); 02869 } 02870 02875 function getDir() { 02876 return $this->isRTL() ? 'rtl' : 'ltr'; 02877 } 02878 02887 function alignStart() { 02888 return $this->isRTL() ? 'right' : 'left'; 02889 } 02890 02899 function alignEnd() { 02900 return $this->isRTL() ? 'left' : 'right'; 02901 } 02902 02914 function getDirMarkEntity( $opposite = false ) { 02915 if ( $opposite ) { 02916 return $this->isRTL() ? '‎' : '‏'; 02917 } 02918 return $this->isRTL() ? '‏' : '‎'; 02919 } 02920 02931 function getDirMark( $opposite = false ) { 02932 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM 02933 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM 02934 if ( $opposite ) { 02935 return $this->isRTL() ? $lrm : $rlm; 02936 } 02937 return $this->isRTL() ? $rlm : $lrm; 02938 } 02939 02943 function capitalizeAllNouns() { 02944 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' ); 02945 } 02946 02953 function getArrow( $direction = 'forwards' ) { 02954 switch ( $direction ) { 02955 case 'forwards': 02956 return $this->isRTL() ? '←' : '→'; 02957 case 'backwards': 02958 return $this->isRTL() ? '→' : '←'; 02959 case 'left': 02960 return '←'; 02961 case 'right': 02962 return '→'; 02963 case 'up': 02964 return '↑'; 02965 case 'down': 02966 return '↓'; 02967 } 02968 } 02969 02975 function linkPrefixExtension() { 02976 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' ); 02977 } 02978 02983 function getMagicWords() { 02984 return self::$dataCache->getItem( $this->mCode, 'magicWords' ); 02985 } 02986 02990 protected function doMagicHook() { 02991 if ( $this->mMagicHookDone ) { 02992 return; 02993 } 02994 $this->mMagicHookDone = true; 02995 wfProfileIn( 'LanguageGetMagic' ); 02996 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) ); 02997 wfProfileOut( 'LanguageGetMagic' ); 02998 } 02999 03005 function getMagic( $mw ) { 03006 // Saves a function call 03007 if ( ! $this->mMagicHookDone ) { 03008 $this->doMagicHook(); 03009 } 03010 03011 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) { 03012 $rawEntry = $this->mMagicExtensions[$mw->mId]; 03013 } else { 03014 $rawEntry = self::$dataCache->getSubitem( 03015 $this->mCode, 'magicWords', $mw->mId ); 03016 } 03017 03018 if ( !is_array( $rawEntry ) ) { 03019 error_log( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" ); 03020 } else { 03021 $mw->mCaseSensitive = $rawEntry[0]; 03022 $mw->mSynonyms = array_slice( $rawEntry, 1 ); 03023 } 03024 } 03025 03031 function addMagicWordsByLang( $newWords ) { 03032 $fallbackChain = $this->getFallbackLanguages(); 03033 $fallbackChain = array_reverse( $fallbackChain ); 03034 foreach ( $fallbackChain as $code ) { 03035 if ( isset( $newWords[$code] ) ) { 03036 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions; 03037 } 03038 } 03039 } 03040 03045 function getSpecialPageAliases() { 03046 // Cache aliases because it may be slow to load them 03047 if ( is_null( $this->mExtendedSpecialPageAliases ) ) { 03048 // Initialise array 03049 $this->mExtendedSpecialPageAliases = 03050 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' ); 03051 wfRunHooks( 'LanguageGetSpecialPageAliases', 03052 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) ); 03053 } 03054 03055 return $this->mExtendedSpecialPageAliases; 03056 } 03057 03064 function emphasize( $text ) { 03065 return "<em>$text</em>"; 03066 } 03067 03092 public function formatNum( $number, $nocommafy = false ) { 03093 global $wgTranslateNumerals; 03094 if ( !$nocommafy ) { 03095 $number = $this->commafy( $number ); 03096 $s = $this->separatorTransformTable(); 03097 if ( $s ) { 03098 $number = strtr( $number, $s ); 03099 } 03100 } 03101 03102 if ( $wgTranslateNumerals ) { 03103 $s = $this->digitTransformTable(); 03104 if ( $s ) { 03105 $number = strtr( $number, $s ); 03106 } 03107 } 03108 03109 return $number; 03110 } 03111 03120 public function formatNumNoSeparators( $number ) { 03121 return $this->formatNum( $number, true ); 03122 } 03123 03128 function parseFormattedNumber( $number ) { 03129 $s = $this->digitTransformTable(); 03130 if ( $s ) { 03131 $number = strtr( $number, array_flip( $s ) ); 03132 } 03133 03134 $s = $this->separatorTransformTable(); 03135 if ( $s ) { 03136 $number = strtr( $number, array_flip( $s ) ); 03137 } 03138 03139 $number = strtr( $number, array( ',' => '' ) ); 03140 return $number; 03141 } 03142 03149 function commafy( $number ) { 03150 $digitGroupingPattern = $this->digitGroupingPattern(); 03151 if ( $number === null ) { 03152 return ''; 03153 } 03154 03155 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) { 03156 // default grouping is at thousands, use the same for ###,###,### pattern too. 03157 return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) ); 03158 } else { 03159 // Ref: http://cldr.unicode.org/translation/number-patterns 03160 $sign = ""; 03161 if ( intval( $number ) < 0 ) { 03162 // For negative numbers apply the algorithm like positive number and add sign. 03163 $sign = "-"; 03164 $number = substr( $number, 1 ); 03165 } 03166 $integerPart = array(); 03167 $decimalPart = array(); 03168 $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches ); 03169 preg_match( "/\d+/", $number, $integerPart ); 03170 preg_match( "/\.\d*/", $number, $decimalPart ); 03171 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : ""; 03172 if ( $groupedNumber === $number ) { 03173 // the string does not have any number part. Eg: .12345 03174 return $sign . $groupedNumber; 03175 } 03176 $start = $end = strlen( $integerPart[0] ); 03177 while ( $start > 0 ) { 03178 $match = $matches[0][$numMatches - 1]; 03179 $matchLen = strlen( $match ); 03180 $start = $end - $matchLen; 03181 if ( $start < 0 ) { 03182 $start = 0; 03183 } 03184 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber; 03185 $end = $start; 03186 if ( $numMatches > 1 ) { 03187 // use the last pattern for the rest of the number 03188 $numMatches--; 03189 } 03190 if ( $start > 0 ) { 03191 $groupedNumber = "," . $groupedNumber; 03192 } 03193 } 03194 return $sign . $groupedNumber; 03195 } 03196 } 03197 03201 function digitGroupingPattern() { 03202 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' ); 03203 } 03204 03208 function digitTransformTable() { 03209 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' ); 03210 } 03211 03215 function separatorTransformTable() { 03216 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' ); 03217 } 03218 03228 function listToText( array $l ) { 03229 $m = count( $l ) - 1; 03230 if ( $m < 0 ) { 03231 return ''; 03232 } 03233 if ( $m > 0 ) { 03234 $and = $this->getMessageFromDB( 'and' ); 03235 $space = $this->getMessageFromDB( 'word-separator' ); 03236 if ( $m > 1 ) { 03237 $comma = $this->getMessageFromDB( 'comma-separator' ); 03238 } 03239 } 03240 $s = $l[$m]; 03241 for ( $i = $m - 1; $i >= 0; $i-- ) { 03242 if ( $i == $m - 1 ) { 03243 $s = $l[$i] . $and . $space . $s; 03244 } else { 03245 $s = $l[$i] . $comma . $s; 03246 } 03247 } 03248 return $s; 03249 } 03250 03257 function commaList( array $list ) { 03258 return implode( 03259 wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(), 03260 $list 03261 ); 03262 } 03263 03270 function semicolonList( array $list ) { 03271 return implode( 03272 wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(), 03273 $list 03274 ); 03275 } 03276 03282 function pipeList( array $list ) { 03283 return implode( 03284 wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(), 03285 $list 03286 ); 03287 } 03288 03306 function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) { 03307 # Use the localized ellipsis character 03308 if ( $ellipsis == '...' ) { 03309 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); 03310 } 03311 # Check if there is no need to truncate 03312 if ( $length == 0 ) { 03313 return $ellipsis; // convention 03314 } elseif ( strlen( $string ) <= abs( $length ) ) { 03315 return $string; // no need to truncate 03316 } 03317 $stringOriginal = $string; 03318 # If ellipsis length is >= $length then we can't apply $adjustLength 03319 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) { 03320 $string = $ellipsis; // this can be slightly unexpected 03321 # Otherwise, truncate and add ellipsis... 03322 } else { 03323 $eLength = $adjustLength ? strlen( $ellipsis ) : 0; 03324 if ( $length > 0 ) { 03325 $length -= $eLength; 03326 $string = substr( $string, 0, $length ); // xyz... 03327 $string = $this->removeBadCharLast( $string ); 03328 $string = $string . $ellipsis; 03329 } else { 03330 $length += $eLength; 03331 $string = substr( $string, $length ); // ...xyz 03332 $string = $this->removeBadCharFirst( $string ); 03333 $string = $ellipsis . $string; 03334 } 03335 } 03336 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181). 03337 # This check is *not* redundant if $adjustLength, due to the single case where 03338 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string. 03339 if ( strlen( $string ) < strlen( $stringOriginal ) ) { 03340 return $string; 03341 } else { 03342 return $stringOriginal; 03343 } 03344 } 03345 03353 protected function removeBadCharLast( $string ) { 03354 if ( $string != '' ) { 03355 $char = ord( $string[strlen( $string ) - 1] ); 03356 $m = array(); 03357 if ( $char >= 0xc0 ) { 03358 # We got the first byte only of a multibyte char; remove it. 03359 $string = substr( $string, 0, -1 ); 03360 } elseif ( $char >= 0x80 && 03361 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' . 03362 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) 03363 ) { 03364 # We chopped in the middle of a character; remove it 03365 $string = $m[1]; 03366 } 03367 } 03368 return $string; 03369 } 03370 03378 protected function removeBadCharFirst( $string ) { 03379 if ( $string != '' ) { 03380 $char = ord( $string[0] ); 03381 if ( $char >= 0x80 && $char < 0xc0 ) { 03382 # We chopped in the middle of a character; remove the whole thing 03383 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string ); 03384 } 03385 } 03386 return $string; 03387 } 03388 03404 function truncateHtml( $text, $length, $ellipsis = '...' ) { 03405 # Use the localized ellipsis character 03406 if ( $ellipsis == '...' ) { 03407 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); 03408 } 03409 # Check if there is clearly no need to truncate 03410 if ( $length <= 0 ) { 03411 return $ellipsis; // no text shown, nothing to format (convention) 03412 } elseif ( strlen( $text ) <= $length ) { 03413 return $text; // string short enough even *with* HTML (short-circuit) 03414 } 03415 03416 $dispLen = 0; // innerHTML legth so far 03417 $testingEllipsis = false; // checking if ellipses will make string longer/equal? 03418 $tagType = 0; // 0-open, 1-close 03419 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither 03420 $entityState = 0; // 0-not entity, 1-entity 03421 $tag = $ret = ''; // accumulated tag name, accumulated result string 03422 $openTags = array(); // open tag stack 03423 $maybeState = null; // possible truncation state 03424 03425 $textLen = strlen( $text ); 03426 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated 03427 for ( $pos = 0; true; ++$pos ) { 03428 # Consider truncation once the display length has reached the maximim. 03429 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case. 03430 # Check that we're not in the middle of a bracket/entity... 03431 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) { 03432 if ( !$testingEllipsis ) { 03433 $testingEllipsis = true; 03434 # Save where we are; we will truncate here unless there turn out to 03435 # be so few remaining characters that truncation is not necessary. 03436 if ( !$maybeState ) { // already saved? ($neLength = 0 case) 03437 $maybeState = array( $ret, $openTags ); // save state 03438 } 03439 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) { 03440 # String in fact does need truncation, the truncation point was OK. 03441 list( $ret, $openTags ) = $maybeState; // reload state 03442 $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix 03443 $ret .= $ellipsis; // add ellipsis 03444 break; 03445 } 03446 } 03447 if ( $pos >= $textLen ) { 03448 break; // extra iteration just for above checks 03449 } 03450 03451 # Read the next char... 03452 $ch = $text[$pos]; 03453 $lastCh = $pos ? $text[$pos - 1] : ''; 03454 $ret .= $ch; // add to result string 03455 if ( $ch == '<' ) { 03456 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML 03457 $entityState = 0; // for bad HTML 03458 $bracketState = 1; // tag started (checking for backslash) 03459 } elseif ( $ch == '>' ) { 03460 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); 03461 $entityState = 0; // for bad HTML 03462 $bracketState = 0; // out of brackets 03463 } elseif ( $bracketState == 1 ) { 03464 if ( $ch == '/' ) { 03465 $tagType = 1; // close tag (e.g. "</span>") 03466 } else { 03467 $tagType = 0; // open tag (e.g. "<span>") 03468 $tag .= $ch; 03469 } 03470 $bracketState = 2; // building tag name 03471 } elseif ( $bracketState == 2 ) { 03472 if ( $ch != ' ' ) { 03473 $tag .= $ch; 03474 } else { 03475 // Name found (e.g. "<a href=..."), add on tag attributes... 03476 $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 ); 03477 } 03478 } elseif ( $bracketState == 0 ) { 03479 if ( $entityState ) { 03480 if ( $ch == ';' ) { 03481 $entityState = 0; 03482 $dispLen++; // entity is one displayed char 03483 } 03484 } else { 03485 if ( $neLength == 0 && !$maybeState ) { 03486 // Save state without $ch. We want to *hit* the first 03487 // display char (to get tags) but not *use* it if truncating. 03488 $maybeState = array( substr( $ret, 0, -1 ), $openTags ); 03489 } 03490 if ( $ch == '&' ) { 03491 $entityState = 1; // entity found, (e.g. " ") 03492 } else { 03493 $dispLen++; // this char is displayed 03494 // Add the next $max display text chars after this in one swoop... 03495 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen; 03496 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max ); 03497 $dispLen += $skipped; 03498 $pos += $skipped; 03499 } 03500 } 03501 } 03502 } 03503 // Close the last tag if left unclosed by bad HTML 03504 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags ); 03505 while ( count( $openTags ) > 0 ) { 03506 $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags 03507 } 03508 return $ret; 03509 } 03510 03522 private function truncate_skip( &$ret, $text, $search, $start, $len = null ) { 03523 if ( $len === null ) { 03524 $len = -1; // -1 means "no limit" for strcspn 03525 } elseif ( $len < 0 ) { 03526 $len = 0; // sanity 03527 } 03528 $skipCount = 0; 03529 if ( $start < strlen( $text ) ) { 03530 $skipCount = strcspn( $text, $search, $start, $len ); 03531 $ret .= substr( $text, $start, $skipCount ); 03532 } 03533 return $skipCount; 03534 } 03535 03545 private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) { 03546 $tag = ltrim( $tag ); 03547 if ( $tag != '' ) { 03548 if ( $tagType == 0 && $lastCh != '/' ) { 03549 $openTags[] = $tag; // tag opened (didn't close itself) 03550 } elseif ( $tagType == 1 ) { 03551 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) { 03552 array_pop( $openTags ); // tag closed 03553 } 03554 } 03555 $tag = ''; 03556 } 03557 } 03558 03567 function convertGrammar( $word, $case ) { 03568 global $wgGrammarForms; 03569 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) { 03570 return $wgGrammarForms[$this->getCode()][$case][$word]; 03571 } 03572 return $word; 03573 } 03579 function getGrammarForms() { 03580 global $wgGrammarForms; 03581 if ( isset( $wgGrammarForms[$this->getCode()] ) && is_array( $wgGrammarForms[$this->getCode()] ) ) { 03582 return $wgGrammarForms[$this->getCode()]; 03583 } 03584 return array(); 03585 } 03605 function gender( $gender, $forms ) { 03606 if ( !count( $forms ) ) { 03607 return ''; 03608 } 03609 $forms = $this->preConvertPlural( $forms, 2 ); 03610 if ( $gender === 'male' ) { 03611 return $forms[0]; 03612 } 03613 if ( $gender === 'female' ) { 03614 return $forms[1]; 03615 } 03616 return isset( $forms[2] ) ? $forms[2] : $forms[0]; 03617 } 03618 03634 function convertPlural( $count, $forms ) { 03635 // Handle explicit n=pluralform cases 03636 $forms = $this->handleExplicitPluralForms( $count, $forms ); 03637 if ( is_string( $forms ) ) { 03638 return $forms; 03639 } 03640 if ( !count( $forms ) ) { 03641 return ''; 03642 } 03643 03644 $pluralForm = $this->getPluralRuleIndexNumber( $count ); 03645 $pluralForm = min( $pluralForm, count( $forms ) - 1 ); 03646 return $forms[$pluralForm]; 03647 } 03648 03664 protected function handleExplicitPluralForms( $count, array $forms ) { 03665 foreach ( $forms as $index => $form ) { 03666 if ( preg_match( '/\d+=/i', $form ) ) { 03667 $pos = strpos( $form, '=' ); 03668 if ( substr( $form, 0, $pos ) === (string) $count ) { 03669 return substr( $form, $pos + 1 ); 03670 } 03671 unset( $forms[$index] ); 03672 } 03673 } 03674 return array_values( $forms ); 03675 } 03676 03685 protected function preConvertPlural( /* Array */ $forms, $count ) { 03686 while ( count( $forms ) < $count ) { 03687 $forms[] = $forms[count( $forms ) - 1]; 03688 } 03689 return $forms; 03690 } 03691 03703 function translateBlockExpiry( $str ) { 03704 $duration = SpecialBlock::getSuggestedDurations( $this ); 03705 foreach ( $duration as $show => $value ) { 03706 if ( strcmp( $str, $value ) == 0 ) { 03707 return htmlspecialchars( trim( $show ) ); 03708 } 03709 } 03710 03711 // Since usually only infinite or indefinite is only on list, so try 03712 // equivalents if still here. 03713 $indefs = array( 'infinite', 'infinity', 'indefinite' ); 03714 if ( in_array( $str, $indefs ) ) { 03715 foreach ( $indefs as $val ) { 03716 $show = array_search( $val, $duration, true ); 03717 if ( $show !== false ) { 03718 return htmlspecialchars( trim( $show ) ); 03719 } 03720 } 03721 } 03722 03723 // If all else fails, return a standard duration or timestamp description. 03724 $time = strtotime( $str, 0 ); 03725 if ( $time === false ) { // Unknown format. Return it as-is in case. 03726 return $str; 03727 } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp. 03728 // $time is relative to 0 so it's a duration length. 03729 return $this->formatDuration( $time ); 03730 } else { // It's an absolute timestamp. 03731 if ( $time === 0 ) { 03732 // wfTimestamp() handles 0 as current time instead of epoch. 03733 return $this->timeanddate( '19700101000000' ); 03734 } else { 03735 return $this->timeanddate( $time ); 03736 } 03737 } 03738 } 03739 03747 public function segmentForDiff( $text ) { 03748 return $text; 03749 } 03750 03757 public function unsegmentForDiff( $text ) { 03758 return $text; 03759 } 03760 03767 public function getConverter() { 03768 return $this->mConverter; 03769 } 03770 03777 public function autoConvertToAllVariants( $text ) { 03778 return $this->mConverter->autoConvertToAllVariants( $text ); 03779 } 03780 03787 public function convert( $text ) { 03788 return $this->mConverter->convert( $text ); 03789 } 03790 03797 public function convertTitle( $title ) { 03798 return $this->mConverter->convertTitle( $title ); 03799 } 03800 03807 public function convertNamespace( $ns ) { 03808 return $this->mConverter->convertNamespace( $ns ); 03809 } 03810 03816 public function hasVariants() { 03817 return count( $this->getVariants() ) > 1; 03818 } 03819 03827 public function hasVariant( $variant ) { 03828 return (bool)$this->mConverter->validateVariant( $variant ); 03829 } 03830 03838 public function armourMath( $text ) { 03839 return $this->mConverter->armourMath( $text ); 03840 } 03841 03849 public function convertHtml( $text, $isTitle = false ) { 03850 return htmlspecialchars( $this->convert( $text, $isTitle ) ); 03851 } 03852 03857 public function convertCategoryKey( $key ) { 03858 return $this->mConverter->convertCategoryKey( $key ); 03859 } 03860 03867 public function getVariants() { 03868 return $this->mConverter->getVariants(); 03869 } 03870 03874 public function getPreferredVariant() { 03875 return $this->mConverter->getPreferredVariant(); 03876 } 03877 03881 public function getDefaultVariant() { 03882 return $this->mConverter->getDefaultVariant(); 03883 } 03884 03888 public function getURLVariant() { 03889 return $this->mConverter->getURLVariant(); 03890 } 03891 03904 public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) { 03905 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond ); 03906 } 03907 03919 public function convertLinkToAllVariants( $text ) { 03920 return $this->mConverter->convertLinkToAllVariants( $text ); 03921 } 03922 03929 function getExtraHashOptions() { 03930 return $this->mConverter->getExtraHashOptions(); 03931 } 03932 03940 public function getParsedTitle() { 03941 return $this->mConverter->getParsedTitle(); 03942 } 03943 03956 public function markNoConversion( $text, $noParse = false ) { 03957 // Excluding protocal-relative URLs may avoid many false positives. 03958 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) { 03959 return $this->mConverter->markNoConversion( $text ); 03960 } else { 03961 return $text; 03962 } 03963 } 03964 03971 public function linkTrail() { 03972 return self::$dataCache->getItem( $this->mCode, 'linkTrail' ); 03973 } 03974 03978 function getLangObj() { 03979 return $this; 03980 } 03981 03989 public function getParentLanguage() { 03990 if ( $this->mParentLanguage !== false ) { 03991 return $this->mParentLanguage; 03992 } 03993 03994 $pieces = explode( '-', $this->getCode() ); 03995 $code = $pieces[0]; 03996 if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) { 03997 $this->mParentLanguage = null; 03998 return null; 03999 } 04000 $lang = Language::factory( $code ); 04001 if ( !$lang->hasVariant( $this->getCode() ) ) { 04002 $this->mParentLanguage = null; 04003 return null; 04004 } 04005 04006 $this->mParentLanguage = $lang; 04007 return $lang; 04008 } 04009 04018 public function getCode() { 04019 return $this->mCode; 04020 } 04021 04032 public function getHtmlCode() { 04033 if ( is_null( $this->mHtmlCode ) ) { 04034 $this->mHtmlCode = wfBCP47( $this->getCode() ); 04035 } 04036 return $this->mHtmlCode; 04037 } 04038 04042 public function setCode( $code ) { 04043 $this->mCode = $code; 04044 // Ensure we don't leave incorrect cached data lying around 04045 $this->mHtmlCode = null; 04046 $this->mParentLanguage = false; 04047 } 04048 04057 public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) { 04058 // Protect against path traversal 04059 if ( !Language::isValidCode( $code ) 04060 || strcspn( $code, ":/\\\000" ) !== strlen( $code ) ) 04061 { 04062 throw new MWException( "Invalid language code \"$code\"" ); 04063 } 04064 04065 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix; 04066 } 04067 04075 public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) { 04076 $m = null; 04077 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' . 04078 preg_quote( $suffix, '/' ) . '/', $filename, $m ); 04079 if ( !count( $m ) ) { 04080 return false; 04081 } 04082 return str_replace( '_', '-', strtolower( $m[1] ) ); 04083 } 04084 04089 public static function getMessagesFileName( $code ) { 04090 global $IP; 04091 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' ); 04092 wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) ); 04093 return $file; 04094 } 04095 04100 public static function getClassFileName( $code ) { 04101 global $IP; 04102 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' ); 04103 } 04104 04112 public static function getFallbackFor( $code ) { 04113 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { 04114 return false; 04115 } else { 04116 $fallbacks = self::getFallbacksFor( $code ); 04117 $first = array_shift( $fallbacks ); 04118 return $first; 04119 } 04120 } 04121 04129 public static function getFallbacksFor( $code ) { 04130 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { 04131 return array(); 04132 } else { 04133 $v = self::getLocalisationCache()->getItem( $code, 'fallback' ); 04134 $v = array_map( 'trim', explode( ',', $v ) ); 04135 if ( $v[count( $v ) - 1] !== 'en' ) { 04136 $v[] = 'en'; 04137 } 04138 return $v; 04139 } 04140 } 04141 04150 public static function getFallbacksIncludingSiteLanguage( $code ) { 04151 global $wgLanguageCode; 04152 04153 // Usually, we will only store a tiny number of fallback chains, so we 04154 // keep them in static memory. 04155 $cacheKey = "{$code}-{$wgLanguageCode}"; 04156 04157 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) { 04158 $fallbacks = self::getFallbacksFor( $code ); 04159 04160 // Append the site's fallback chain, including the site language itself 04161 $siteFallbacks = self::getFallbacksFor( $wgLanguageCode ); 04162 array_unshift( $siteFallbacks, $wgLanguageCode ); 04163 04164 // Eliminate any languages already included in the chain 04165 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks ); 04166 04167 self::$fallbackLanguageCache[$cacheKey] = array( $fallbacks, $siteFallbacks ); 04168 } 04169 return self::$fallbackLanguageCache[$cacheKey]; 04170 } 04171 04181 public static function getMessagesFor( $code ) { 04182 return self::getLocalisationCache()->getItem( $code, 'messages' ); 04183 } 04184 04193 public static function getMessageFor( $key, $code ) { 04194 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key ); 04195 } 04196 04205 public static function getMessageKeysFor( $code ) { 04206 return self::getLocalisationCache()->getSubItemList( $code, 'messages' ); 04207 } 04208 04213 function fixVariableInNamespace( $talk ) { 04214 if ( strpos( $talk, '$1' ) === false ) { 04215 return $talk; 04216 } 04217 04218 global $wgMetaNamespace; 04219 $talk = str_replace( '$1', $wgMetaNamespace, $talk ); 04220 04221 # Allow grammar transformations 04222 # Allowing full message-style parsing would make simple requests 04223 # such as action=raw much more expensive than they need to be. 04224 # This will hopefully cover most cases. 04225 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i', 04226 array( &$this, 'replaceGrammarInNamespace' ), $talk ); 04227 return str_replace( ' ', '_', $talk ); 04228 } 04229 04234 function replaceGrammarInNamespace( $m ) { 04235 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) ); 04236 } 04237 04242 static function getCaseMaps() { 04243 static $wikiUpperChars, $wikiLowerChars; 04244 if ( isset( $wikiUpperChars ) ) { 04245 return array( $wikiUpperChars, $wikiLowerChars ); 04246 } 04247 04248 wfProfileIn( __METHOD__ ); 04249 $arr = wfGetPrecompiledData( 'Utf8Case.ser' ); 04250 if ( $arr === false ) { 04251 throw new MWException( 04252 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" ); 04253 } 04254 $wikiUpperChars = $arr['wikiUpperChars']; 04255 $wikiLowerChars = $arr['wikiLowerChars']; 04256 wfProfileOut( __METHOD__ ); 04257 return array( $wikiUpperChars, $wikiLowerChars ); 04258 } 04259 04271 public function formatExpiry( $expiry, $format = true ) { 04272 static $infinity; 04273 if ( $infinity === null ) { 04274 $infinity = wfGetDB( DB_SLAVE )->getInfinity(); 04275 } 04276 04277 if ( $expiry == '' || $expiry == $infinity ) { 04278 return $format === true 04279 ? $this->getMessageFromDB( 'infiniteblock' ) 04280 : $infinity; 04281 } else { 04282 return $format === true 04283 ? $this->timeanddate( $expiry, /* User preference timezone */ true ) 04284 : wfTimestamp( $format, $expiry ); 04285 } 04286 } 04287 04298 function formatTimePeriod( $seconds, $format = array() ) { 04299 if ( !is_array( $format ) ) { 04300 $format = array( 'avoid' => $format ); // For backwards compatibility 04301 } 04302 if ( !isset( $format['avoid'] ) ) { 04303 $format['avoid'] = false; 04304 } 04305 if ( !isset( $format['noabbrevs' ] ) ) { 04306 $format['noabbrevs'] = false; 04307 } 04308 $secondsMsg = wfMessage( 04309 $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this ); 04310 $minutesMsg = wfMessage( 04311 $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this ); 04312 $hoursMsg = wfMessage( 04313 $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this ); 04314 $daysMsg = wfMessage( 04315 $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this ); 04316 04317 if ( round( $seconds * 10 ) < 100 ) { 04318 $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) ); 04319 $s = $secondsMsg->params( $s )->text(); 04320 } elseif ( round( $seconds ) < 60 ) { 04321 $s = $this->formatNum( round( $seconds ) ); 04322 $s = $secondsMsg->params( $s )->text(); 04323 } elseif ( round( $seconds ) < 3600 ) { 04324 $minutes = floor( $seconds / 60 ); 04325 $secondsPart = round( fmod( $seconds, 60 ) ); 04326 if ( $secondsPart == 60 ) { 04327 $secondsPart = 0; 04328 $minutes++; 04329 } 04330 $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04331 $s .= ' '; 04332 $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text(); 04333 } elseif ( round( $seconds ) <= 2 * 86400 ) { 04334 $hours = floor( $seconds / 3600 ); 04335 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 ); 04336 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 ); 04337 if ( $secondsPart == 60 ) { 04338 $secondsPart = 0; 04339 $minutes++; 04340 } 04341 if ( $minutes == 60 ) { 04342 $minutes = 0; 04343 $hours++; 04344 } 04345 $s = $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04346 $s .= ' '; 04347 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04348 if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) { 04349 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text(); 04350 } 04351 } else { 04352 $days = floor( $seconds / 86400 ); 04353 if ( $format['avoid'] === 'avoidminutes' ) { 04354 $hours = round( ( $seconds - $days * 86400 ) / 3600 ); 04355 if ( $hours == 24 ) { 04356 $hours = 0; 04357 $days++; 04358 } 04359 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04360 $s .= ' '; 04361 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04362 } elseif ( $format['avoid'] === 'avoidseconds' ) { 04363 $hours = floor( ( $seconds - $days * 86400 ) / 3600 ); 04364 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 ); 04365 if ( $minutes == 60 ) { 04366 $minutes = 0; 04367 $hours++; 04368 } 04369 if ( $hours == 24 ) { 04370 $hours = 0; 04371 $days++; 04372 } 04373 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04374 $s .= ' '; 04375 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04376 $s .= ' '; 04377 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04378 } else { 04379 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04380 $s .= ' '; 04381 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format ); 04382 } 04383 } 04384 return $s; 04385 } 04386 04397 function formatBitrate( $bps ) { 04398 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" ); 04399 } 04400 04407 function formatComputingNumbers( $size, $boundary, $messageKey ) { 04408 if ( $size <= 0 ) { 04409 return str_replace( '$1', $this->formatNum( $size ), 04410 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) ) 04411 ); 04412 } 04413 $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ); 04414 $index = 0; 04415 04416 $maxIndex = count( $sizes ) - 1; 04417 while ( $size >= $boundary && $index < $maxIndex ) { 04418 $index++; 04419 $size /= $boundary; 04420 } 04421 04422 // For small sizes no decimal places necessary 04423 $round = 0; 04424 if ( $index > 1 ) { 04425 // For MB and bigger two decimal places are smarter 04426 $round = 2; 04427 } 04428 $msg = str_replace( '$1', $sizes[$index], $messageKey ); 04429 04430 $size = round( $size, $round ); 04431 $text = $this->getMessageFromDB( $msg ); 04432 return str_replace( '$1', $this->formatNum( $size ), $text ); 04433 } 04434 04445 function formatSize( $size ) { 04446 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" ); 04447 } 04448 04458 function specialList( $page, $details, $oppositedm = true ) { 04459 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . 04460 $this->getDirMark(); 04461 $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) . 04462 wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : ''; 04463 return $page . $details; 04464 } 04465 04476 public function viewPrevNext( Title $title, $offset, $limit, array $query = array(), $atend = false ) { 04477 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip? 04478 04479 # Make 'previous' link 04480 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); 04481 if ( $offset > 0 ) { 04482 $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit, 04483 $query, $prev, 'prevn-title', 'mw-prevlink' ); 04484 } else { 04485 $plink = htmlspecialchars( $prev ); 04486 } 04487 04488 # Make 'next' link 04489 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); 04490 if ( $atend ) { 04491 $nlink = htmlspecialchars( $next ); 04492 } else { 04493 $nlink = $this->numLink( $title, $offset + $limit, $limit, 04494 $query, $next, 'nextn-title', 'mw-nextlink' ); 04495 } 04496 04497 # Make links to set number of items per page 04498 $numLinks = array(); 04499 foreach ( array( 20, 50, 100, 250, 500 ) as $num ) { 04500 $numLinks[] = $this->numLink( $title, $offset, $num, 04501 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' ); 04502 } 04503 04504 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title 04505 )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped(); 04506 } 04507 04520 private function numLink( Title $title, $offset, $limit, array $query, $link, $tooltipMsg, $class ) { 04521 $query = array( 'limit' => $limit, 'offset' => $offset ) + $query; 04522 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); 04523 return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ), 04524 'title' => $tooltip, 'class' => $class ), $link ); 04525 } 04526 04532 public function getConvRuleTitle() { 04533 return $this->mConverter->getConvRuleTitle(); 04534 } 04535 04541 public function getCompiledPluralRules() { 04542 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' ); 04543 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04544 if ( !$pluralRules ) { 04545 foreach ( $fallbacks as $fallbackCode ) { 04546 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' ); 04547 if ( $pluralRules ) { 04548 break; 04549 } 04550 } 04551 } 04552 return $pluralRules; 04553 } 04554 04560 public function getPluralRules() { 04561 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' ); 04562 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04563 if ( !$pluralRules ) { 04564 foreach ( $fallbacks as $fallbackCode ) { 04565 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' ); 04566 if ( $pluralRules ) { 04567 break; 04568 } 04569 } 04570 } 04571 return $pluralRules; 04572 } 04573 04579 public function getPluralRuleTypes() { 04580 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' ); 04581 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04582 if ( !$pluralRuleTypes ) { 04583 foreach ( $fallbacks as $fallbackCode ) { 04584 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' ); 04585 if ( $pluralRuleTypes ) { 04586 break; 04587 } 04588 } 04589 } 04590 return $pluralRuleTypes; 04591 } 04592 04597 public function getPluralRuleIndexNumber( $number ) { 04598 $pluralRules = $this->getCompiledPluralRules(); 04599 $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules ); 04600 return $form; 04601 } 04602 04610 public function getPluralRuleType( $number ) { 04611 $index = $this->getPluralRuleIndexNumber( $number ); 04612 $pluralRuleTypes = $this->getPluralRuleTypes(); 04613 if ( isset( $pluralRuleTypes[$index] ) ) { 04614 return $pluralRuleTypes[$index]; 04615 } else { 04616 return 'other'; 04617 } 04618 } 04619 }