MediaWiki
REL1_24
|
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 00041 class Language { 00045 public $mConverter; 00046 00047 public $mVariants, $mCode, $mLoaded = false; 00048 public $mMagicExtensions = array(), $mMagicHookDone = false; 00049 private $mHtmlCode = null, $mParentLanguage = false; 00050 00051 public $dateFormatStrings = array(); 00052 public $mExtendedSpecialPageAliases; 00053 00054 protected $namespaceNames, $mNamespaceIds, $namespaceAliases; 00055 00059 public $transformData = array(); 00060 00064 static public $dataCache; 00065 00066 static public $mLangObjCache = array(); 00067 00068 static public $mWeekdayMsgs = array( 00069 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 00070 'friday', 'saturday' 00071 ); 00072 00073 static public $mWeekdayAbbrevMsgs = array( 00074 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' 00075 ); 00076 00077 static public $mMonthMsgs = array( 00078 'january', 'february', 'march', 'april', 'may_long', 'june', 00079 'july', 'august', 'september', 'october', 'november', 00080 'december' 00081 ); 00082 static public $mMonthGenMsgs = array( 00083 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', 00084 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 00085 'december-gen' 00086 ); 00087 static public $mMonthAbbrevMsgs = array( 00088 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 00089 'sep', 'oct', 'nov', 'dec' 00090 ); 00091 00092 static public $mIranianCalendarMonthMsgs = array( 00093 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3', 00094 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6', 00095 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9', 00096 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12' 00097 ); 00098 00099 static public $mHebrewCalendarMonthMsgs = array( 00100 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3', 00101 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6', 00102 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9', 00103 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12', 00104 'hebrew-calendar-m6a', 'hebrew-calendar-m6b' 00105 ); 00106 00107 static public $mHebrewCalendarMonthGenMsgs = array( 00108 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen', 00109 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen', 00110 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen', 00111 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen', 00112 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen' 00113 ); 00114 00115 static public $mHijriCalendarMonthMsgs = array( 00116 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3', 00117 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6', 00118 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9', 00119 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12' 00120 ); 00121 00126 static public $durationIntervals = array( 00127 'millennia' => 31556952000, 00128 'centuries' => 3155695200, 00129 'decades' => 315569520, 00130 'years' => 31556952, // 86400 * ( 365 + ( 24 * 3 + 25 ) / 400 ) 00131 'weeks' => 604800, 00132 'days' => 86400, 00133 'hours' => 3600, 00134 'minutes' => 60, 00135 'seconds' => 1, 00136 ); 00137 00144 static private $fallbackLanguageCache = array(); 00145 00151 static function factory( $code ) { 00152 global $wgDummyLanguageCodes, $wgLangObjCacheSize; 00153 00154 if ( isset( $wgDummyLanguageCodes[$code] ) ) { 00155 $code = $wgDummyLanguageCodes[$code]; 00156 } 00157 00158 // get the language object to process 00159 $langObj = isset( self::$mLangObjCache[$code] ) 00160 ? self::$mLangObjCache[$code] 00161 : self::newFromCode( $code ); 00162 00163 // merge the language object in to get it up front in the cache 00164 self::$mLangObjCache = array_merge( array( $code => $langObj ), self::$mLangObjCache ); 00165 // get rid of the oldest ones in case we have an overflow 00166 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize, true ); 00167 00168 return $langObj; 00169 } 00170 00177 protected static function newFromCode( $code ) { 00178 // Protect against path traversal below 00179 if ( !Language::isValidCode( $code ) 00180 || strcspn( $code, ":/\\\000" ) !== strlen( $code ) 00181 ) { 00182 throw new MWException( "Invalid language code \"$code\"" ); 00183 } 00184 00185 if ( !Language::isValidBuiltInCode( $code ) ) { 00186 // It's not possible to customise this code with class files, so 00187 // just return a Language object. This is to support uselang= hacks. 00188 $lang = new Language; 00189 $lang->setCode( $code ); 00190 return $lang; 00191 } 00192 00193 // Check if there is a language class for the code 00194 $class = self::classFromCode( $code ); 00195 self::preloadLanguageClass( $class ); 00196 if ( class_exists( $class ) ) { 00197 $lang = new $class; 00198 return $lang; 00199 } 00200 00201 // Keep trying the fallback list until we find an existing class 00202 $fallbacks = Language::getFallbacksFor( $code ); 00203 foreach ( $fallbacks as $fallbackCode ) { 00204 if ( !Language::isValidBuiltInCode( $fallbackCode ) ) { 00205 throw new MWException( "Invalid fallback '$fallbackCode' in fallback sequence for '$code'" ); 00206 } 00207 00208 $class = self::classFromCode( $fallbackCode ); 00209 self::preloadLanguageClass( $class ); 00210 if ( class_exists( $class ) ) { 00211 $lang = Language::newFromCode( $fallbackCode ); 00212 $lang->setCode( $code ); 00213 return $lang; 00214 } 00215 } 00216 00217 throw new MWException( "Invalid fallback sequence for language '$code'" ); 00218 } 00219 00228 public static function isSupportedLanguage( $code ) { 00229 return self::isValidBuiltInCode( $code ) 00230 && ( is_readable( self::getMessagesFileName( $code ) ) 00231 || is_readable( self::getJsonMessagesFileName( $code ) ) 00232 ); 00233 } 00234 00250 public static function isWellFormedLanguageTag( $code, $lenient = false ) { 00251 $alpha = '[a-z]'; 00252 $digit = '[0-9]'; 00253 $alphanum = '[a-z0-9]'; 00254 $x = 'x'; # private use singleton 00255 $singleton = '[a-wy-z]'; # other singleton 00256 $s = $lenient ? '[-_]' : '-'; 00257 00258 $language = "$alpha{2,8}|$alpha{2,3}$s$alpha{3}"; 00259 $script = "$alpha{4}"; # ISO 15924 00260 $region = "(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49 00261 $variant = "(?:$alphanum{5,8}|$digit$alphanum{3})"; 00262 $extension = "$singleton(?:$s$alphanum{2,8})+"; 00263 $privateUse = "$x(?:$s$alphanum{1,8})+"; 00264 00265 # Define certain grandfathered codes, since otherwise the regex is pretty useless. 00266 # Since these are limited, this is safe even later changes to the registry -- 00267 # the only oddity is that it might change the type of the tag, and thus 00268 # the results from the capturing groups. 00269 # http://www.iana.org/assignments/language-subtag-registry 00270 00271 $grandfathered = "en{$s}GB{$s}oed" 00272 . "|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)" 00273 . "|no{$s}(?:bok|nyn)" 00274 . "|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)" 00275 . "|zh{$s}min{$s}nan"; 00276 00277 $variantList = "$variant(?:$s$variant)*"; 00278 $extensionList = "$extension(?:$s$extension)*"; 00279 00280 $langtag = "(?:($language)" 00281 . "(?:$s$script)?" 00282 . "(?:$s$region)?" 00283 . "(?:$s$variantList)?" 00284 . "(?:$s$extensionList)?" 00285 . "(?:$s$privateUse)?)"; 00286 00287 # The final breakdown, with capturing groups for each of these components 00288 # The variants, extensions, grandfathered, and private-use may have interior '-' 00289 00290 $root = "^(?:$langtag|$privateUse|$grandfathered)$"; 00291 00292 return (bool)preg_match( "/$root/", strtolower( $code ) ); 00293 } 00294 00304 public static function isValidCode( $code ) { 00305 static $cache = array(); 00306 if ( isset( $cache[$code] ) ) { 00307 return $cache[$code]; 00308 } 00309 // People think language codes are html safe, so enforce it. 00310 // Ideally we should only allow a-zA-Z0-9- 00311 // but, .+ and other chars are often used for {{int:}} hacks 00312 // see bugs 37564, 37587, 36938 00313 $cache[$code] = 00314 strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code ) 00315 && !preg_match( Title::getTitleInvalidRegex(), $code ); 00316 00317 return $cache[$code]; 00318 } 00319 00330 public static function isValidBuiltInCode( $code ) { 00331 00332 if ( !is_string( $code ) ) { 00333 if ( is_object( $code ) ) { 00334 $addmsg = " of class " . get_class( $code ); 00335 } else { 00336 $addmsg = ''; 00337 } 00338 $type = gettype( $code ); 00339 throw new MWException( __METHOD__ . " must be passed a string, $type given$addmsg" ); 00340 } 00341 00342 return (bool)preg_match( '/^[a-z0-9-]{2,}$/', $code ); 00343 } 00344 00353 public static function isKnownLanguageTag( $tag ) { 00354 static $coreLanguageNames; 00355 00356 // Quick escape for invalid input to avoid exceptions down the line 00357 // when code tries to process tags which are not valid at all. 00358 if ( !self::isValidBuiltInCode( $tag ) ) { 00359 return false; 00360 } 00361 00362 if ( $coreLanguageNames === null ) { 00363 global $IP; 00364 include "$IP/languages/Names.php"; 00365 } 00366 00367 if ( isset( $coreLanguageNames[$tag] ) 00368 || self::fetchLanguageName( $tag, $tag ) !== '' 00369 ) { 00370 return true; 00371 } 00372 00373 return false; 00374 } 00375 00380 public static function classFromCode( $code ) { 00381 if ( $code == 'en' ) { 00382 return 'Language'; 00383 } else { 00384 return 'Language' . str_replace( '-', '_', ucfirst( $code ) ); 00385 } 00386 } 00387 00393 public static function preloadLanguageClass( $class ) { 00394 global $IP; 00395 00396 if ( $class === 'Language' ) { 00397 return; 00398 } 00399 00400 if ( file_exists( "$IP/languages/classes/$class.php" ) ) { 00401 include_once "$IP/languages/classes/$class.php"; 00402 } 00403 } 00404 00410 public static function getLocalisationCache() { 00411 if ( is_null( self::$dataCache ) ) { 00412 global $wgLocalisationCacheConf; 00413 $class = $wgLocalisationCacheConf['class']; 00414 self::$dataCache = new $class( $wgLocalisationCacheConf ); 00415 } 00416 return self::$dataCache; 00417 } 00418 00419 function __construct() { 00420 $this->mConverter = new FakeConverter( $this ); 00421 // Set the code to the name of the descendant 00422 if ( get_class( $this ) == 'Language' ) { 00423 $this->mCode = 'en'; 00424 } else { 00425 $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) ); 00426 } 00427 self::getLocalisationCache(); 00428 } 00429 00433 function __destruct() { 00434 foreach ( $this as $name => $value ) { 00435 unset( $this->$name ); 00436 } 00437 } 00438 00443 function initContLang() { 00444 } 00445 00450 function getFallbackLanguages() { 00451 return self::getFallbacksFor( $this->mCode ); 00452 } 00453 00458 function getBookstoreList() { 00459 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' ); 00460 } 00461 00468 public function getNamespaces() { 00469 if ( is_null( $this->namespaceNames ) ) { 00470 global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces; 00471 00472 $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' ); 00473 $validNamespaces = MWNamespace::getCanonicalNamespaces(); 00474 00475 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces; 00476 00477 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace; 00478 if ( $wgMetaNamespaceTalk ) { 00479 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk; 00480 } else { 00481 $talk = $this->namespaceNames[NS_PROJECT_TALK]; 00482 $this->namespaceNames[NS_PROJECT_TALK] = 00483 $this->fixVariableInNamespace( $talk ); 00484 } 00485 00486 # Sometimes a language will be localised but not actually exist on this wiki. 00487 foreach ( $this->namespaceNames as $key => $text ) { 00488 if ( !isset( $validNamespaces[$key] ) ) { 00489 unset( $this->namespaceNames[$key] ); 00490 } 00491 } 00492 00493 # The above mixing may leave namespaces out of canonical order. 00494 # Re-order by namespace ID number... 00495 ksort( $this->namespaceNames ); 00496 00497 wfRunHooks( 'LanguageGetNamespaces', array( &$this->namespaceNames ) ); 00498 } 00499 00500 return $this->namespaceNames; 00501 } 00502 00507 public function setNamespaces( array $namespaces ) { 00508 $this->namespaceNames = $namespaces; 00509 $this->mNamespaceIds = null; 00510 } 00511 00515 public function resetNamespaces() { 00516 $this->namespaceNames = null; 00517 $this->mNamespaceIds = null; 00518 $this->namespaceAliases = null; 00519 } 00520 00529 function getFormattedNamespaces() { 00530 $ns = $this->getNamespaces(); 00531 foreach ( $ns as $k => $v ) { 00532 $ns[$k] = strtr( $v, '_', ' ' ); 00533 } 00534 return $ns; 00535 } 00536 00547 function getNsText( $index ) { 00548 $ns = $this->getNamespaces(); 00549 00550 return isset( $ns[$index] ) ? $ns[$index] : false; 00551 } 00552 00566 function getFormattedNsText( $index ) { 00567 $ns = $this->getNsText( $index ); 00568 00569 return strtr( $ns, '_', ' ' ); 00570 } 00571 00580 function getGenderNsText( $index, $gender ) { 00581 global $wgExtraGenderNamespaces; 00582 00583 $ns = $wgExtraGenderNamespaces + 00584 self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00585 00586 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->getNsText( $index ); 00587 } 00588 00595 function needsGenderDistinction() { 00596 global $wgExtraGenderNamespaces, $wgExtraNamespaces; 00597 if ( count( $wgExtraGenderNamespaces ) > 0 ) { 00598 // $wgExtraGenderNamespaces overrides everything 00599 return true; 00600 } elseif ( isset( $wgExtraNamespaces[NS_USER] ) && isset( $wgExtraNamespaces[NS_USER_TALK] ) ) { 00602 // $wgExtraNamespaces overrides any gender aliases specified in i18n files 00603 return false; 00604 } else { 00605 // Check what is in i18n files 00606 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00607 return count( $aliases ) > 0; 00608 } 00609 } 00610 00619 function getLocalNsIndex( $text ) { 00620 $lctext = $this->lc( $text ); 00621 $ids = $this->getNamespaceIds(); 00622 return isset( $ids[$lctext] ) ? $ids[$lctext] : false; 00623 } 00624 00628 function getNamespaceAliases() { 00629 if ( is_null( $this->namespaceAliases ) ) { 00630 $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' ); 00631 if ( !$aliases ) { 00632 $aliases = array(); 00633 } else { 00634 foreach ( $aliases as $name => $index ) { 00635 if ( $index === NS_PROJECT_TALK ) { 00636 unset( $aliases[$name] ); 00637 $name = $this->fixVariableInNamespace( $name ); 00638 $aliases[$name] = $index; 00639 } 00640 } 00641 } 00642 00643 global $wgExtraGenderNamespaces; 00644 $genders = $wgExtraGenderNamespaces + 00645 (array)self::$dataCache->getItem( $this->mCode, 'namespaceGenderAliases' ); 00646 foreach ( $genders as $index => $forms ) { 00647 foreach ( $forms as $alias ) { 00648 $aliases[$alias] = $index; 00649 } 00650 } 00651 00652 # Also add converted namespace names as aliases, to avoid confusion. 00653 $convertedNames = array(); 00654 foreach ( $this->getVariants() as $variant ) { 00655 if ( $variant === $this->mCode ) { 00656 continue; 00657 } 00658 foreach ( $this->getNamespaces() as $ns => $_ ) { 00659 $convertedNames[$this->getConverter()->convertNamespace( $ns, $variant )] = $ns; 00660 } 00661 } 00662 00663 $this->namespaceAliases = $aliases + $convertedNames; 00664 } 00665 00666 return $this->namespaceAliases; 00667 } 00668 00672 function getNamespaceIds() { 00673 if ( is_null( $this->mNamespaceIds ) ) { 00674 global $wgNamespaceAliases; 00675 # Put namespace names and aliases into a hashtable. 00676 # If this is too slow, then we should arrange it so that it is done 00677 # before caching. The catch is that at pre-cache time, the above 00678 # class-specific fixup hasn't been done. 00679 $this->mNamespaceIds = array(); 00680 foreach ( $this->getNamespaces() as $index => $name ) { 00681 $this->mNamespaceIds[$this->lc( $name )] = $index; 00682 } 00683 foreach ( $this->getNamespaceAliases() as $name => $index ) { 00684 $this->mNamespaceIds[$this->lc( $name )] = $index; 00685 } 00686 if ( $wgNamespaceAliases ) { 00687 foreach ( $wgNamespaceAliases as $name => $index ) { 00688 $this->mNamespaceIds[$this->lc( $name )] = $index; 00689 } 00690 } 00691 } 00692 return $this->mNamespaceIds; 00693 } 00694 00702 function getNsIndex( $text ) { 00703 $lctext = $this->lc( $text ); 00704 $ns = MWNamespace::getCanonicalIndex( $lctext ); 00705 if ( $ns !== null ) { 00706 return $ns; 00707 } 00708 $ids = $this->getNamespaceIds(); 00709 return isset( $ids[$lctext] ) ? $ids[$lctext] : false; 00710 } 00711 00719 function getVariantname( $code, $usemsg = true ) { 00720 $msg = "variantname-$code"; 00721 if ( $usemsg && wfMessage( $msg )->exists() ) { 00722 return $this->getMessageFromDB( $msg ); 00723 } 00724 $name = self::fetchLanguageName( $code ); 00725 if ( $name ) { 00726 return $name; # if it's defined as a language name, show that 00727 } else { 00728 # otherwise, output the language code 00729 return $code; 00730 } 00731 } 00732 00737 function specialPage( $name ) { 00738 $aliases = $this->getSpecialPageAliases(); 00739 if ( isset( $aliases[$name][0] ) ) { 00740 $name = $aliases[$name][0]; 00741 } 00742 return $this->getNsText( NS_SPECIAL ) . ':' . $name; 00743 } 00744 00748 function getDatePreferences() { 00749 return self::$dataCache->getItem( $this->mCode, 'datePreferences' ); 00750 } 00751 00755 function getDateFormats() { 00756 return self::$dataCache->getItem( $this->mCode, 'dateFormats' ); 00757 } 00758 00762 function getDefaultDateFormat() { 00763 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' ); 00764 if ( $df === 'dmy or mdy' ) { 00765 global $wgAmericanDates; 00766 return $wgAmericanDates ? 'mdy' : 'dmy'; 00767 } else { 00768 return $df; 00769 } 00770 } 00771 00775 function getDatePreferenceMigrationMap() { 00776 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' ); 00777 } 00778 00783 function getImageFile( $image ) { 00784 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image ); 00785 } 00786 00791 function getImageFiles() { 00792 return self::$dataCache->getItem( $this->mCode, 'imageFiles' ); 00793 } 00794 00798 function getExtraUserToggles() { 00799 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' ); 00800 } 00801 00806 function getUserToggle( $tog ) { 00807 return $this->getMessageFromDB( "tog-$tog" ); 00808 } 00809 00820 public static function getLanguageNames( $customisedOnly = false ) { 00821 return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile' : 'mw' ); 00822 } 00823 00833 public static function getTranslatedLanguageNames( $code ) { 00834 return self::fetchLanguageNames( $code, 'all' ); 00835 } 00836 00848 public static function fetchLanguageNames( $inLanguage = null, $include = 'mw' ) { 00849 global $wgExtraLanguageNames; 00850 static $coreLanguageNames; 00851 00852 if ( $coreLanguageNames === null ) { 00853 global $IP; 00854 include "$IP/languages/Names.php"; 00855 } 00856 00857 // If passed an invalid language code to use, fallback to en 00858 if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) { 00859 $inLanguage = 'en'; 00860 } 00861 00862 $names = array(); 00863 00864 if ( $inLanguage ) { 00865 # TODO: also include when $inLanguage is null, when this code is more efficient 00866 wfRunHooks( 'LanguageGetTranslatedLanguageNames', array( &$names, $inLanguage ) ); 00867 } 00868 00869 $mwNames = $wgExtraLanguageNames + $coreLanguageNames; 00870 foreach ( $mwNames as $mwCode => $mwName ) { 00871 # - Prefer own MediaWiki native name when not using the hook 00872 # - For other names just add if not added through the hook 00873 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) { 00874 $names[$mwCode] = $mwName; 00875 } 00876 } 00877 00878 if ( $include === 'all' ) { 00879 return $names; 00880 } 00881 00882 $returnMw = array(); 00883 $coreCodes = array_keys( $mwNames ); 00884 foreach ( $coreCodes as $coreCode ) { 00885 $returnMw[$coreCode] = $names[$coreCode]; 00886 } 00887 00888 if ( $include === 'mwfile' ) { 00889 $namesMwFile = array(); 00890 # We do this using a foreach over the codes instead of a directory 00891 # loop so that messages files in extensions will work correctly. 00892 foreach ( $returnMw as $code => $value ) { 00893 if ( is_readable( self::getMessagesFileName( $code ) ) 00894 || is_readable( self::getJsonMessagesFileName( $code ) ) 00895 ) { 00896 $namesMwFile[$code] = $names[$code]; 00897 } 00898 } 00899 00900 return $namesMwFile; 00901 } 00902 00903 # 'mw' option; default if it's not one of the other two options (all/mwfile) 00904 return $returnMw; 00905 } 00906 00914 public static function fetchLanguageName( $code, $inLanguage = null, $include = 'all' ) { 00915 $code = strtolower( $code ); 00916 $array = self::fetchLanguageNames( $inLanguage, $include ); 00917 return !array_key_exists( $code, $array ) ? '' : $array[$code]; 00918 } 00919 00926 function getMessageFromDB( $msg ) { 00927 return wfMessage( $msg )->inLanguage( $this )->text(); 00928 } 00929 00937 function getLanguageName( $code ) { 00938 return self::fetchLanguageName( $code ); 00939 } 00940 00945 function getMonthName( $key ) { 00946 return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] ); 00947 } 00948 00952 function getMonthNamesArray() { 00953 $monthNames = array( '' ); 00954 for ( $i = 1; $i < 13; $i++ ) { 00955 $monthNames[] = $this->getMonthName( $i ); 00956 } 00957 return $monthNames; 00958 } 00959 00964 function getMonthNameGen( $key ) { 00965 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] ); 00966 } 00967 00972 function getMonthAbbreviation( $key ) { 00973 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key - 1] ); 00974 } 00975 00979 function getMonthAbbreviationsArray() { 00980 $monthNames = array( '' ); 00981 for ( $i = 1; $i < 13; $i++ ) { 00982 $monthNames[] = $this->getMonthAbbreviation( $i ); 00983 } 00984 return $monthNames; 00985 } 00986 00991 function getWeekdayName( $key ) { 00992 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key - 1] ); 00993 } 00994 00999 function getWeekdayAbbreviation( $key ) { 01000 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key - 1] ); 01001 } 01002 01007 function getIranianCalendarMonthName( $key ) { 01008 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] ); 01009 } 01010 01015 function getHebrewCalendarMonthName( $key ) { 01016 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] ); 01017 } 01018 01023 function getHebrewCalendarMonthNameGen( $key ) { 01024 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] ); 01025 } 01026 01031 function getHijriCalendarMonthName( $key ) { 01032 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] ); 01033 } 01034 01043 private static function dateTimeObjFormat( &$dateTimeObj, $ts, $zone, $code ) { 01044 if ( !$dateTimeObj ) { 01045 $dateTimeObj = DateTime::createFromFormat( 01046 'YmdHis', $ts, $zone ?: new DateTimeZone( 'UTC' ) 01047 ); 01048 } 01049 return $dateTimeObj->format( $code ); 01050 } 01051 01119 function sprintfDate( $format, $ts, DateTimeZone $zone = null, &$ttl = null ) { 01120 $s = ''; 01121 $raw = false; 01122 $roman = false; 01123 $hebrewNum = false; 01124 $dateTimeObj = false; 01125 $rawToggle = false; 01126 $iranian = false; 01127 $hebrew = false; 01128 $hijri = false; 01129 $thai = false; 01130 $minguo = false; 01131 $tenno = false; 01132 01133 $usedSecond = false; 01134 $usedMinute = false; 01135 $usedHour = false; 01136 $usedAMPM = false; 01137 $usedDay = false; 01138 $usedWeek = false; 01139 $usedMonth = false; 01140 $usedYear = false; 01141 $usedISOYear = false; 01142 $usedIsLeapYear = false; 01143 01144 $usedHebrewMonth = false; 01145 $usedIranianMonth = false; 01146 $usedHijriMonth = false; 01147 $usedHebrewYear = false; 01148 $usedIranianYear = false; 01149 $usedHijriYear = false; 01150 $usedTennoYear = false; 01151 01152 if ( strlen( $ts ) !== 14 ) { 01153 throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" ); 01154 } 01155 01156 if ( !ctype_digit( $ts ) ) { 01157 throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" ); 01158 } 01159 01160 $formatLength = strlen( $format ); 01161 for ( $p = 0; $p < $formatLength; $p++ ) { 01162 $num = false; 01163 $code = $format[$p]; 01164 if ( $code == 'x' && $p < $formatLength - 1 ) { 01165 $code .= $format[++$p]; 01166 } 01167 01168 if ( ( $code === 'xi' 01169 || $code === 'xj' 01170 || $code === 'xk' 01171 || $code === 'xm' 01172 || $code === 'xo' 01173 || $code === 'xt' ) 01174 && $p < $formatLength - 1 ) { 01175 $code .= $format[++$p]; 01176 } 01177 01178 switch ( $code ) { 01179 case 'xx': 01180 $s .= 'x'; 01181 break; 01182 case 'xn': 01183 $raw = true; 01184 break; 01185 case 'xN': 01186 $rawToggle = !$rawToggle; 01187 break; 01188 case 'xr': 01189 $roman = true; 01190 break; 01191 case 'xh': 01192 $hebrewNum = true; 01193 break; 01194 case 'xg': 01195 $usedMonth = true; 01196 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) ); 01197 break; 01198 case 'xjx': 01199 $usedHebrewMonth = true; 01200 if ( !$hebrew ) { 01201 $hebrew = self::tsToHebrew( $ts ); 01202 } 01203 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] ); 01204 break; 01205 case 'd': 01206 $usedDay = true; 01207 $num = substr( $ts, 6, 2 ); 01208 break; 01209 case 'D': 01210 $usedDay = true; 01211 $s .= $this->getWeekdayAbbreviation( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1 ); 01212 break; 01213 case 'j': 01214 $usedDay = true; 01215 $num = intval( substr( $ts, 6, 2 ) ); 01216 break; 01217 case 'xij': 01218 $usedDay = true; 01219 if ( !$iranian ) { 01220 $iranian = self::tsToIranian( $ts ); 01221 } 01222 $num = $iranian[2]; 01223 break; 01224 case 'xmj': 01225 $usedDay = true; 01226 if ( !$hijri ) { 01227 $hijri = self::tsToHijri( $ts ); 01228 } 01229 $num = $hijri[2]; 01230 break; 01231 case 'xjj': 01232 $usedDay = true; 01233 if ( !$hebrew ) { 01234 $hebrew = self::tsToHebrew( $ts ); 01235 } 01236 $num = $hebrew[2]; 01237 break; 01238 case 'l': 01239 $usedDay = true; 01240 $s .= $this->getWeekdayName( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'w' ) + 1 ); 01241 break; 01242 case 'F': 01243 $usedMonth = true; 01244 $s .= $this->getMonthName( substr( $ts, 4, 2 ) ); 01245 break; 01246 case 'xiF': 01247 $usedIranianMonth = true; 01248 if ( !$iranian ) { 01249 $iranian = self::tsToIranian( $ts ); 01250 } 01251 $s .= $this->getIranianCalendarMonthName( $iranian[1] ); 01252 break; 01253 case 'xmF': 01254 $usedHijriMonth = true; 01255 if ( !$hijri ) { 01256 $hijri = self::tsToHijri( $ts ); 01257 } 01258 $s .= $this->getHijriCalendarMonthName( $hijri[1] ); 01259 break; 01260 case 'xjF': 01261 $usedHebrewMonth = true; 01262 if ( !$hebrew ) { 01263 $hebrew = self::tsToHebrew( $ts ); 01264 } 01265 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] ); 01266 break; 01267 case 'm': 01268 $usedMonth = true; 01269 $num = substr( $ts, 4, 2 ); 01270 break; 01271 case 'M': 01272 $usedMonth = true; 01273 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) ); 01274 break; 01275 case 'n': 01276 $usedMonth = true; 01277 $num = intval( substr( $ts, 4, 2 ) ); 01278 break; 01279 case 'xin': 01280 $usedIranianMonth = true; 01281 if ( !$iranian ) { 01282 $iranian = self::tsToIranian( $ts ); 01283 } 01284 $num = $iranian[1]; 01285 break; 01286 case 'xmn': 01287 $usedHijriMonth = true; 01288 if ( !$hijri ) { 01289 $hijri = self::tsToHijri ( $ts ); 01290 } 01291 $num = $hijri[1]; 01292 break; 01293 case 'xjn': 01294 $usedHebrewMonth = true; 01295 if ( !$hebrew ) { 01296 $hebrew = self::tsToHebrew( $ts ); 01297 } 01298 $num = $hebrew[1]; 01299 break; 01300 case 'xjt': 01301 $usedHebrewMonth = true; 01302 if ( !$hebrew ) { 01303 $hebrew = self::tsToHebrew( $ts ); 01304 } 01305 $num = $hebrew[3]; 01306 break; 01307 case 'Y': 01308 $usedYear = true; 01309 $num = substr( $ts, 0, 4 ); 01310 break; 01311 case 'xiY': 01312 $usedIranianYear = true; 01313 if ( !$iranian ) { 01314 $iranian = self::tsToIranian( $ts ); 01315 } 01316 $num = $iranian[0]; 01317 break; 01318 case 'xmY': 01319 $usedHijriYear = true; 01320 if ( !$hijri ) { 01321 $hijri = self::tsToHijri( $ts ); 01322 } 01323 $num = $hijri[0]; 01324 break; 01325 case 'xjY': 01326 $usedHebrewYear = true; 01327 if ( !$hebrew ) { 01328 $hebrew = self::tsToHebrew( $ts ); 01329 } 01330 $num = $hebrew[0]; 01331 break; 01332 case 'xkY': 01333 $usedYear = true; 01334 if ( !$thai ) { 01335 $thai = self::tsToYear( $ts, 'thai' ); 01336 } 01337 $num = $thai[0]; 01338 break; 01339 case 'xoY': 01340 $usedYear = true; 01341 if ( !$minguo ) { 01342 $minguo = self::tsToYear( $ts, 'minguo' ); 01343 } 01344 $num = $minguo[0]; 01345 break; 01346 case 'xtY': 01347 $usedTennoYear = true; 01348 if ( !$tenno ) { 01349 $tenno = self::tsToYear( $ts, 'tenno' ); 01350 } 01351 $num = $tenno[0]; 01352 break; 01353 case 'y': 01354 $usedYear = true; 01355 $num = substr( $ts, 2, 2 ); 01356 break; 01357 case 'xiy': 01358 $usedIranianYear = true; 01359 if ( !$iranian ) { 01360 $iranian = self::tsToIranian( $ts ); 01361 } 01362 $num = substr( $iranian[0], -2 ); 01363 break; 01364 case 'a': 01365 $usedAMPM = true; 01366 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm'; 01367 break; 01368 case 'A': 01369 $usedAMPM = true; 01370 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM'; 01371 break; 01372 case 'g': 01373 $usedHour = true; 01374 $h = substr( $ts, 8, 2 ); 01375 $num = $h % 12 ? $h % 12 : 12; 01376 break; 01377 case 'G': 01378 $usedHour = true; 01379 $num = intval( substr( $ts, 8, 2 ) ); 01380 break; 01381 case 'h': 01382 $usedHour = true; 01383 $h = substr( $ts, 8, 2 ); 01384 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 ); 01385 break; 01386 case 'H': 01387 $usedHour = true; 01388 $num = substr( $ts, 8, 2 ); 01389 break; 01390 case 'i': 01391 $usedMinute = true; 01392 $num = substr( $ts, 10, 2 ); 01393 break; 01394 case 's': 01395 $usedSecond = true; 01396 $num = substr( $ts, 12, 2 ); 01397 break; 01398 case 'c': 01399 case 'r': 01400 $usedSecond = true; 01401 // fall through 01402 case 'e': 01403 case 'O': 01404 case 'P': 01405 case 'T': 01406 $s .= Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code ); 01407 break; 01408 case 'w': 01409 case 'N': 01410 case 'z': 01411 $usedDay = true; 01412 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code ); 01413 break; 01414 case 'W': 01415 $usedWeek = true; 01416 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code ); 01417 break; 01418 case 't': 01419 $usedMonth = true; 01420 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code ); 01421 break; 01422 case 'L': 01423 $usedIsLeapYear = true; 01424 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code ); 01425 break; 01426 case 'o': 01427 $usedISOYear = true; 01428 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code ); 01429 break; 01430 case 'U': 01431 $usedSecond = true; 01432 // fall through 01433 case 'I': 01434 case 'Z': 01435 $num = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, $code ); 01436 break; 01437 case '\\': 01438 # Backslash escaping 01439 if ( $p < $formatLength - 1 ) { 01440 $s .= $format[++$p]; 01441 } else { 01442 $s .= '\\'; 01443 } 01444 break; 01445 case '"': 01446 # Quoted literal 01447 if ( $p < $formatLength - 1 ) { 01448 $endQuote = strpos( $format, '"', $p + 1 ); 01449 if ( $endQuote === false ) { 01450 # No terminating quote, assume literal " 01451 $s .= '"'; 01452 } else { 01453 $s .= substr( $format, $p + 1, $endQuote - $p - 1 ); 01454 $p = $endQuote; 01455 } 01456 } else { 01457 # Quote at end of string, assume literal " 01458 $s .= '"'; 01459 } 01460 break; 01461 default: 01462 $s .= $format[$p]; 01463 } 01464 if ( $num !== false ) { 01465 if ( $rawToggle || $raw ) { 01466 $s .= $num; 01467 $raw = false; 01468 } elseif ( $roman ) { 01469 $s .= Language::romanNumeral( $num ); 01470 $roman = false; 01471 } elseif ( $hebrewNum ) { 01472 $s .= self::hebrewNumeral( $num ); 01473 $hebrewNum = false; 01474 } else { 01475 $s .= $this->formatNum( $num, true ); 01476 } 01477 } 01478 } 01479 01480 if ( $usedSecond ) { 01481 $ttl = 1; 01482 } elseif ( $usedMinute ) { 01483 $ttl = 60 - substr( $ts, 12, 2 ); 01484 } elseif ( $usedHour ) { 01485 $ttl = 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 ); 01486 } elseif ( $usedAMPM ) { 01487 $ttl = 43200 - ( substr( $ts, 8, 2 ) % 12 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 ); 01488 } elseif ( $usedDay || $usedHebrewMonth || $usedIranianMonth || $usedHijriMonth || $usedHebrewYear || $usedIranianYear || $usedHijriYear || $usedTennoYear ) { 01489 // @todo Someone who understands the non-Gregorian calendars should write proper logic for them 01490 // so that they don't need purged every day. 01491 $ttl = 86400 - substr( $ts, 8, 2 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 ); 01492 } else { 01493 $possibleTtls = array(); 01494 $timeRemainingInDay = 86400 - substr( $ts, 8, 2 ) * 3600 - substr( $ts, 10, 2 ) * 60 - substr( $ts, 12, 2 ); 01495 if ( $usedWeek ) { 01496 $possibleTtls[] = ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 + $timeRemainingInDay; 01497 } elseif ( $usedISOYear ) { 01498 // December 28th falls on the last ISO week of the year, every year. 01499 // The last ISO week of a year can be 52 or 53. 01500 $lastWeekOfISOYear = DateTime::createFromFormat( 'Ymd', substr( $ts, 0, 4 ) . '1228', $zone ?: new DateTimeZone( 'UTC' ) )->format( 'W' ); 01501 $currentISOWeek = Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'W' ); 01502 $weeksRemaining = $lastWeekOfISOYear - $currentISOWeek; 01503 $timeRemainingInWeek = ( 7 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'N' ) ) * 86400 + $timeRemainingInDay; 01504 $possibleTtls[] = $weeksRemaining * 604800 + $timeRemainingInWeek; 01505 } 01506 01507 if ( $usedMonth ) { 01508 $possibleTtls[] = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 't' ) - substr( $ts, 6, 2 ) ) * 86400 + $timeRemainingInDay; 01509 } elseif ( $usedYear ) { 01510 $possibleTtls[] = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400 01511 + $timeRemainingInDay; 01512 } elseif ( $usedIsLeapYear ) { 01513 $year = substr( $ts, 0, 4 ); 01514 $timeRemainingInYear = ( Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'L' ) + 364 - Language::dateTimeObjFormat( $dateTimeObj, $ts, $zone, 'z' ) ) * 86400 01515 + $timeRemainingInDay; 01516 $mod = $year % 4; 01517 if ( $mod || ( !( $year % 100 ) && $year % 400 ) ) { 01518 // this isn't a leap year. see when the next one starts 01519 $nextCandidate = $year - $mod + 4; 01520 if ( $nextCandidate % 100 || !( $nextCandidate % 400 ) ) { 01521 $possibleTtls[] = ( $nextCandidate - $year - 1 ) * 365 * 86400 + $timeRemainingInYear; 01522 } else { 01523 $possibleTtls[] = ( $nextCandidate - $year + 3 ) * 365 * 86400 + $timeRemainingInYear; 01524 } 01525 } else { 01526 // this is a leap year, so the next year isn't 01527 $possibleTtls[] = $timeRemainingInYear; 01528 } 01529 } 01530 01531 if ( $possibleTtls ) { 01532 $ttl = min( $possibleTtls ); 01533 } 01534 } 01535 01536 return $s; 01537 } 01538 01539 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ); 01540 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 ); 01541 01554 private static function tsToIranian( $ts ) { 01555 $gy = substr( $ts, 0, 4 ) -1600; 01556 $gm = substr( $ts, 4, 2 ) -1; 01557 $gd = substr( $ts, 6, 2 ) -1; 01558 01559 # Days passed from the beginning (including leap years) 01560 $gDayNo = 365 * $gy 01561 + floor( ( $gy + 3 ) / 4 ) 01562 - floor( ( $gy + 99 ) / 100 ) 01563 + floor( ( $gy + 399 ) / 400 ); 01564 01565 // Add days of the past months of this year 01566 for ( $i = 0; $i < $gm; $i++ ) { 01567 $gDayNo += self::$GREG_DAYS[$i]; 01568 } 01569 01570 // Leap years 01571 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) { 01572 $gDayNo++; 01573 } 01574 01575 // Days passed in current month 01576 $gDayNo += (int)$gd; 01577 01578 $jDayNo = $gDayNo - 79; 01579 01580 $jNp = floor( $jDayNo / 12053 ); 01581 $jDayNo %= 12053; 01582 01583 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 ); 01584 $jDayNo %= 1461; 01585 01586 if ( $jDayNo >= 366 ) { 01587 $jy += floor( ( $jDayNo - 1 ) / 365 ); 01588 $jDayNo = floor( ( $jDayNo - 1 ) % 365 ); 01589 } 01590 01591 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) { 01592 $jDayNo -= self::$IRANIAN_DAYS[$i]; 01593 } 01594 01595 $jm = $i + 1; 01596 $jd = $jDayNo + 1; 01597 01598 return array( $jy, $jm, $jd ); 01599 } 01600 01612 private static function tsToHijri( $ts ) { 01613 $year = substr( $ts, 0, 4 ); 01614 $month = substr( $ts, 4, 2 ); 01615 $day = substr( $ts, 6, 2 ); 01616 01617 $zyr = $year; 01618 $zd = $day; 01619 $zm = $month; 01620 $zy = $zyr; 01621 01622 if ( 01623 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) || 01624 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) ) 01625 ) { 01626 $zjd = (int)( ( 1461 * ( $zy + 4800 + (int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) + 01627 (int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) - 01628 (int)( ( 3 * (int)( ( ( $zy + 4900 + (int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) + 01629 $zd - 32075; 01630 } else { 01631 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) + 01632 (int)( ( 275 * $zm ) / 9 ) + $zd + 1729777; 01633 } 01634 01635 $zl = $zjd -1948440 + 10632; 01636 $zn = (int)( ( $zl - 1 ) / 10631 ); 01637 $zl = $zl - 10631 * $zn + 354; 01638 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) + 01639 ( (int)( $zl / 5670 ) ) * ( (int)( ( 43 * $zl ) / 15238 ) ); 01640 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) - 01641 ( (int)( $zj / 16 ) ) * ( (int)( ( 15238 * $zj ) / 43 ) ) + 29; 01642 $zm = (int)( ( 24 * $zl ) / 709 ); 01643 $zd = $zl - (int)( ( 709 * $zm ) / 24 ); 01644 $zy = 30 * $zn + $zj - 30; 01645 01646 return array( $zy, $zm, $zd ); 01647 } 01648 01664 private static function tsToHebrew( $ts ) { 01665 # Parse date 01666 $year = substr( $ts, 0, 4 ); 01667 $month = substr( $ts, 4, 2 ); 01668 $day = substr( $ts, 6, 2 ); 01669 01670 # Calculate Hebrew year 01671 $hebrewYear = $year + 3760; 01672 01673 # Month number when September = 1, August = 12 01674 $month += 4; 01675 if ( $month > 12 ) { 01676 # Next year 01677 $month -= 12; 01678 $year++; 01679 $hebrewYear++; 01680 } 01681 01682 # Calculate day of year from 1 September 01683 $dayOfYear = $day; 01684 for ( $i = 1; $i < $month; $i++ ) { 01685 if ( $i == 6 ) { 01686 # February 01687 $dayOfYear += 28; 01688 # Check if the year is leap 01689 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) { 01690 $dayOfYear++; 01691 } 01692 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) { 01693 $dayOfYear += 30; 01694 } else { 01695 $dayOfYear += 31; 01696 } 01697 } 01698 01699 # Calculate the start of the Hebrew year 01700 $start = self::hebrewYearStart( $hebrewYear ); 01701 01702 # Calculate next year's start 01703 if ( $dayOfYear <= $start ) { 01704 # Day is before the start of the year - it is the previous year 01705 # Next year's start 01706 $nextStart = $start; 01707 # Previous year 01708 $year--; 01709 $hebrewYear--; 01710 # Add days since previous year's 1 September 01711 $dayOfYear += 365; 01712 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) { 01713 # Leap year 01714 $dayOfYear++; 01715 } 01716 # Start of the new (previous) year 01717 $start = self::hebrewYearStart( $hebrewYear ); 01718 } else { 01719 # Next year's start 01720 $nextStart = self::hebrewYearStart( $hebrewYear + 1 ); 01721 } 01722 01723 # Calculate Hebrew day of year 01724 $hebrewDayOfYear = $dayOfYear - $start; 01725 01726 # Difference between year's days 01727 $diff = $nextStart - $start; 01728 # Add 12 (or 13 for leap years) days to ignore the difference between 01729 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the 01730 # difference is only about the year type 01731 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) { 01732 $diff += 13; 01733 } else { 01734 $diff += 12; 01735 } 01736 01737 # Check the year pattern, and is leap year 01738 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year 01739 # This is mod 30, to work on both leap years (which add 30 days of Adar I) 01740 # and non-leap years 01741 $yearPattern = $diff % 30; 01742 # Check if leap year 01743 $isLeap = $diff >= 30; 01744 01745 # Calculate day in the month from number of day in the Hebrew year 01746 # Don't check Adar - if the day is not in Adar, we will stop before; 01747 # if it is in Adar, we will use it to check if it is Adar I or Adar II 01748 $hebrewDay = $hebrewDayOfYear; 01749 $hebrewMonth = 1; 01750 $days = 0; 01751 while ( $hebrewMonth <= 12 ) { 01752 # Calculate days in this month 01753 if ( $isLeap && $hebrewMonth == 6 ) { 01754 # Adar in a leap year 01755 if ( $isLeap ) { 01756 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days 01757 $days = 30; 01758 if ( $hebrewDay <= $days ) { 01759 # Day in Adar I 01760 $hebrewMonth = 13; 01761 } else { 01762 # Subtract the days of Adar I 01763 $hebrewDay -= $days; 01764 # Try Adar II 01765 $days = 29; 01766 if ( $hebrewDay <= $days ) { 01767 # Day in Adar II 01768 $hebrewMonth = 14; 01769 } 01770 } 01771 } 01772 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) { 01773 # Cheshvan in a complete year (otherwise as the rule below) 01774 $days = 30; 01775 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) { 01776 # Kislev in an incomplete year (otherwise as the rule below) 01777 $days = 29; 01778 } else { 01779 # Odd months have 30 days, even have 29 01780 $days = 30 - ( $hebrewMonth - 1 ) % 2; 01781 } 01782 if ( $hebrewDay <= $days ) { 01783 # In the current month 01784 break; 01785 } else { 01786 # Subtract the days of the current month 01787 $hebrewDay -= $days; 01788 # Try in the next month 01789 $hebrewMonth++; 01790 } 01791 } 01792 01793 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days ); 01794 } 01795 01805 private static function hebrewYearStart( $year ) { 01806 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 ); 01807 $b = intval( ( $year - 1 ) % 4 ); 01808 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 ); 01809 if ( $m < 0 ) { 01810 $m--; 01811 } 01812 $Mar = intval( $m ); 01813 if ( $m < 0 ) { 01814 $m++; 01815 } 01816 $m -= $Mar; 01817 01818 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 ); 01819 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) { 01820 $Mar++; 01821 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) { 01822 $Mar += 2; 01823 } elseif ( $c == 2 || $c == 4 || $c == 6 ) { 01824 $Mar++; 01825 } 01826 01827 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24; 01828 return $Mar; 01829 } 01830 01843 private static function tsToYear( $ts, $cName ) { 01844 $gy = substr( $ts, 0, 4 ); 01845 $gm = substr( $ts, 4, 2 ); 01846 $gd = substr( $ts, 6, 2 ); 01847 01848 if ( !strcmp( $cName, 'thai' ) ) { 01849 # Thai solar dates 01850 # Add 543 years to the Gregorian calendar 01851 # Months and days are identical 01852 $gy_offset = $gy + 543; 01853 } elseif ( ( !strcmp( $cName, 'minguo' ) ) || !strcmp( $cName, 'juche' ) ) { 01854 # Minguo dates 01855 # Deduct 1911 years from the Gregorian calendar 01856 # Months and days are identical 01857 $gy_offset = $gy - 1911; 01858 } elseif ( !strcmp( $cName, 'tenno' ) ) { 01859 # Nengō dates up to Meiji period 01860 # Deduct years from the Gregorian calendar 01861 # depending on the nengo periods 01862 # Months and days are identical 01863 if ( ( $gy < 1912 ) 01864 || ( ( $gy == 1912 ) && ( $gm < 7 ) ) 01865 || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) ) 01866 ) { 01867 # Meiji period 01868 $gy_gannen = $gy - 1868 + 1; 01869 $gy_offset = $gy_gannen; 01870 if ( $gy_gannen == 1 ) { 01871 $gy_offset = '元'; 01872 } 01873 $gy_offset = '明治' . $gy_offset; 01874 } elseif ( 01875 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) || 01876 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) || 01877 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) || 01878 ( ( $gy == 1926 ) && ( $gm < 12 ) ) || 01879 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) ) 01880 ) { 01881 # Taishō period 01882 $gy_gannen = $gy - 1912 + 1; 01883 $gy_offset = $gy_gannen; 01884 if ( $gy_gannen == 1 ) { 01885 $gy_offset = '元'; 01886 } 01887 $gy_offset = '大正' . $gy_offset; 01888 } elseif ( 01889 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) || 01890 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) || 01891 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) ) 01892 ) { 01893 # Shōwa period 01894 $gy_gannen = $gy - 1926 + 1; 01895 $gy_offset = $gy_gannen; 01896 if ( $gy_gannen == 1 ) { 01897 $gy_offset = '元'; 01898 } 01899 $gy_offset = '昭和' . $gy_offset; 01900 } else { 01901 # Heisei period 01902 $gy_gannen = $gy - 1989 + 1; 01903 $gy_offset = $gy_gannen; 01904 if ( $gy_gannen == 1 ) { 01905 $gy_offset = '元'; 01906 } 01907 $gy_offset = '平成' . $gy_offset; 01908 } 01909 } else { 01910 $gy_offset = $gy; 01911 } 01912 01913 return array( $gy_offset, $gm, $gd ); 01914 } 01915 01923 static function romanNumeral( $num ) { 01924 static $table = array( 01925 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ), 01926 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ), 01927 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ), 01928 array( '', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM', 01929 'MMMMMMMM', 'MMMMMMMMM', 'MMMMMMMMMM' ) 01930 ); 01931 01932 $num = intval( $num ); 01933 if ( $num > 10000 || $num <= 0 ) { 01934 return $num; 01935 } 01936 01937 $s = ''; 01938 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) { 01939 if ( $num >= $pow10 ) { 01940 $s .= $table[$i][(int)floor( $num / $pow10 )]; 01941 } 01942 $num = $num % $pow10; 01943 } 01944 return $s; 01945 } 01946 01954 static function hebrewNumeral( $num ) { 01955 static $table = array( 01956 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ), 01957 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ), 01958 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ), 01959 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ) 01960 ); 01961 01962 $num = intval( $num ); 01963 if ( $num > 9999 || $num <= 0 ) { 01964 return $num; 01965 } 01966 01967 $s = ''; 01968 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) { 01969 if ( $num >= $pow10 ) { 01970 if ( $num == 15 || $num == 16 ) { 01971 $s .= $table[0][9] . $table[0][$num - 9]; 01972 $num = 0; 01973 } else { 01974 $s .= $table[$i][intval( ( $num / $pow10 ) )]; 01975 if ( $pow10 == 1000 ) { 01976 $s .= "'"; 01977 } 01978 } 01979 } 01980 $num = $num % $pow10; 01981 } 01982 if ( strlen( $s ) == 2 ) { 01983 $str = $s . "'"; 01984 } else { 01985 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"'; 01986 $str .= substr( $s, strlen( $s ) - 2, 2 ); 01987 } 01988 $start = substr( $str, 0, strlen( $str ) - 2 ); 01989 $end = substr( $str, strlen( $str ) - 2 ); 01990 switch ( $end ) { 01991 case 'כ': 01992 $str = $start . 'ך'; 01993 break; 01994 case 'מ': 01995 $str = $start . 'ם'; 01996 break; 01997 case 'נ': 01998 $str = $start . 'ן'; 01999 break; 02000 case 'פ': 02001 $str = $start . 'ף'; 02002 break; 02003 case 'צ': 02004 $str = $start . 'ץ'; 02005 break; 02006 } 02007 return $str; 02008 } 02009 02018 function userAdjust( $ts, $tz = false ) { 02019 global $wgUser, $wgLocalTZoffset; 02020 02021 if ( $tz === false ) { 02022 $tz = $wgUser->getOption( 'timecorrection' ); 02023 } 02024 02025 $data = explode( '|', $tz, 3 ); 02026 02027 if ( $data[0] == 'ZoneInfo' ) { 02028 wfSuppressWarnings(); 02029 $userTZ = timezone_open( $data[2] ); 02030 wfRestoreWarnings(); 02031 if ( $userTZ !== false ) { 02032 $date = date_create( $ts, timezone_open( 'UTC' ) ); 02033 date_timezone_set( $date, $userTZ ); 02034 $date = date_format( $date, 'YmdHis' ); 02035 return $date; 02036 } 02037 # Unrecognized timezone, default to 'Offset' with the stored offset. 02038 $data[0] = 'Offset'; 02039 } 02040 02041 if ( $data[0] == 'System' || $tz == '' ) { 02042 # Global offset in minutes. 02043 $minDiff = $wgLocalTZoffset; 02044 } elseif ( $data[0] == 'Offset' ) { 02045 $minDiff = intval( $data[1] ); 02046 } else { 02047 $data = explode( ':', $tz ); 02048 if ( count( $data ) == 2 ) { 02049 $data[0] = intval( $data[0] ); 02050 $data[1] = intval( $data[1] ); 02051 $minDiff = abs( $data[0] ) * 60 + $data[1]; 02052 if ( $data[0] < 0 ) { 02053 $minDiff = -$minDiff; 02054 } 02055 } else { 02056 $minDiff = intval( $data[0] ) * 60; 02057 } 02058 } 02059 02060 # No difference ? Return time unchanged 02061 if ( 0 == $minDiff ) { 02062 return $ts; 02063 } 02064 02065 wfSuppressWarnings(); // E_STRICT system time bitching 02066 # Generate an adjusted date; take advantage of the fact that mktime 02067 # will normalize out-of-range values so we don't have to split $minDiff 02068 # into hours and minutes. 02069 $t = mktime( ( 02070 (int)substr( $ts, 8, 2 ) ), # Hours 02071 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes 02072 (int)substr( $ts, 12, 2 ), # Seconds 02073 (int)substr( $ts, 4, 2 ), # Month 02074 (int)substr( $ts, 6, 2 ), # Day 02075 (int)substr( $ts, 0, 4 ) ); # Year 02076 02077 $date = date( 'YmdHis', $t ); 02078 wfRestoreWarnings(); 02079 02080 return $date; 02081 } 02082 02100 function dateFormat( $usePrefs = true ) { 02101 global $wgUser; 02102 02103 if ( is_bool( $usePrefs ) ) { 02104 if ( $usePrefs ) { 02105 $datePreference = $wgUser->getDatePreference(); 02106 } else { 02107 $datePreference = (string)User::getDefaultOption( 'date' ); 02108 } 02109 } else { 02110 $datePreference = (string)$usePrefs; 02111 } 02112 02113 // return int 02114 if ( $datePreference == '' ) { 02115 return 'default'; 02116 } 02117 02118 return $datePreference; 02119 } 02120 02130 function getDateFormatString( $type, $pref ) { 02131 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) { 02132 if ( $pref == 'default' ) { 02133 $pref = $this->getDefaultDateFormat(); 02134 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02135 } else { 02136 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02137 02138 if ( $type === 'pretty' && $df === null ) { 02139 $df = $this->getDateFormatString( 'date', $pref ); 02140 } 02141 02142 if ( $df === null ) { 02143 $pref = $this->getDefaultDateFormat(); 02144 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" ); 02145 } 02146 } 02147 $this->dateFormatStrings[$type][$pref] = $df; 02148 } 02149 return $this->dateFormatStrings[$type][$pref]; 02150 } 02151 02162 function date( $ts, $adj = false, $format = true, $timecorrection = false ) { 02163 $ts = wfTimestamp( TS_MW, $ts ); 02164 if ( $adj ) { 02165 $ts = $this->userAdjust( $ts, $timecorrection ); 02166 } 02167 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) ); 02168 return $this->sprintfDate( $df, $ts ); 02169 } 02170 02181 function time( $ts, $adj = false, $format = true, $timecorrection = false ) { 02182 $ts = wfTimestamp( TS_MW, $ts ); 02183 if ( $adj ) { 02184 $ts = $this->userAdjust( $ts, $timecorrection ); 02185 } 02186 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) ); 02187 return $this->sprintfDate( $df, $ts ); 02188 } 02189 02201 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false ) { 02202 $ts = wfTimestamp( TS_MW, $ts ); 02203 if ( $adj ) { 02204 $ts = $this->userAdjust( $ts, $timecorrection ); 02205 } 02206 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) ); 02207 return $this->sprintfDate( $df, $ts ); 02208 } 02209 02220 public function formatDuration( $seconds, array $chosenIntervals = array() ) { 02221 $intervals = $this->getDurationIntervals( $seconds, $chosenIntervals ); 02222 02223 $segments = array(); 02224 02225 foreach ( $intervals as $intervalName => $intervalValue ) { 02226 // Messages: duration-seconds, duration-minutes, duration-hours, duration-days, duration-weeks, 02227 // duration-years, duration-decades, duration-centuries, duration-millennia 02228 $message = wfMessage( 'duration-' . $intervalName )->numParams( $intervalValue ); 02229 $segments[] = $message->inLanguage( $this )->escaped(); 02230 } 02231 02232 return $this->listToText( $segments ); 02233 } 02234 02246 public function getDurationIntervals( $seconds, array $chosenIntervals = array() ) { 02247 if ( empty( $chosenIntervals ) ) { 02248 $chosenIntervals = array( 02249 'millennia', 02250 'centuries', 02251 'decades', 02252 'years', 02253 'days', 02254 'hours', 02255 'minutes', 02256 'seconds' 02257 ); 02258 } 02259 02260 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) ); 02261 $sortedNames = array_keys( $intervals ); 02262 $smallestInterval = array_pop( $sortedNames ); 02263 02264 $segments = array(); 02265 02266 foreach ( $intervals as $name => $length ) { 02267 $value = floor( $seconds / $length ); 02268 02269 if ( $value > 0 || ( $name == $smallestInterval && empty( $segments ) ) ) { 02270 $seconds -= $value * $length; 02271 $segments[$name] = $value; 02272 } 02273 } 02274 02275 return $segments; 02276 } 02277 02297 private function internalUserTimeAndDate( $type, $ts, User $user, array $options ) { 02298 $ts = wfTimestamp( TS_MW, $ts ); 02299 $options += array( 'timecorrection' => true, 'format' => true ); 02300 if ( $options['timecorrection'] !== false ) { 02301 if ( $options['timecorrection'] === true ) { 02302 $offset = $user->getOption( 'timecorrection' ); 02303 } else { 02304 $offset = $options['timecorrection']; 02305 } 02306 $ts = $this->userAdjust( $ts, $offset ); 02307 } 02308 if ( $options['format'] === true ) { 02309 $format = $user->getDatePreference(); 02310 } else { 02311 $format = $options['format']; 02312 } 02313 $df = $this->getDateFormatString( $type, $this->dateFormat( $format ) ); 02314 return $this->sprintfDate( $df, $ts ); 02315 } 02316 02336 public function userDate( $ts, User $user, array $options = array() ) { 02337 return $this->internalUserTimeAndDate( 'date', $ts, $user, $options ); 02338 } 02339 02359 public function userTime( $ts, User $user, array $options = array() ) { 02360 return $this->internalUserTimeAndDate( 'time', $ts, $user, $options ); 02361 } 02362 02382 public function userTimeAndDate( $ts, User $user, array $options = array() ) { 02383 return $this->internalUserTimeAndDate( 'both', $ts, $user, $options ); 02384 } 02385 02401 public function getHumanTimestamp( MWTimestamp $ts, MWTimestamp $relativeTo, User $user ) { 02402 $diff = $ts->diff( $relativeTo ); 02403 $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) - 02404 (int)$relativeTo->timestamp->format( 'w' ) ); 02405 $days = $diff->days ?: (int)$diffDay; 02406 if ( $diff->invert || $days > 5 02407 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' ) 02408 ) { 02409 // Timestamps are in different years: use full timestamp 02410 // Also do full timestamp for future dates 02414 $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' ); 02415 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ); 02416 } elseif ( $days > 5 ) { 02417 // Timestamps are in same year, but more than 5 days ago: show day and month only. 02418 $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' ); 02419 $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ); 02420 } elseif ( $days > 1 ) { 02421 // Timestamp within the past week: show the day of the week and time 02422 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02423 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )]; 02424 // Messages: 02425 // sunday-at, monday-at, tuesday-at, wednesday-at, thursday-at, friday-at, saturday-at 02426 $ts = wfMessage( "$weekday-at" ) 02427 ->inLanguage( $this ) 02428 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02429 ->text(); 02430 } elseif ( $days == 1 ) { 02431 // Timestamp was yesterday: say 'yesterday' and the time. 02432 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02433 $ts = wfMessage( 'yesterday-at' ) 02434 ->inLanguage( $this ) 02435 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02436 ->text(); 02437 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) { 02438 // Timestamp was today, but more than 90 minutes ago: say 'today' and the time. 02439 $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' ); 02440 $ts = wfMessage( 'today-at' ) 02441 ->inLanguage( $this ) 02442 ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) ) 02443 ->text(); 02444 02445 // From here on in, the timestamp was soon enough ago so that we can simply say 02446 // XX units ago, e.g., "2 hours ago" or "5 minutes ago" 02447 } elseif ( $diff->h == 1 ) { 02448 // Less than 90 minutes, but more than an hour ago. 02449 $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text(); 02450 } elseif ( $diff->i >= 1 ) { 02451 // A few minutes ago. 02452 $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text(); 02453 } elseif ( $diff->s >= 30 ) { 02454 // Less than a minute, but more than 30 sec ago. 02455 $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text(); 02456 } else { 02457 // Less than 30 seconds ago. 02458 $ts = wfMessage( 'just-now' )->text(); 02459 } 02460 02461 return $ts; 02462 } 02463 02468 function getMessage( $key ) { 02469 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key ); 02470 } 02471 02475 function getAllMessages() { 02476 return self::$dataCache->getItem( $this->mCode, 'messages' ); 02477 } 02478 02485 function iconv( $in, $out, $string ) { 02486 # This is a wrapper for iconv in all languages except esperanto, 02487 # which does some nasty x-conversions beforehand 02488 02489 # Even with //IGNORE iconv can whine about illegal characters in 02490 # *input* string. We just ignore those too. 02491 # REF: http://bugs.php.net/bug.php?id=37166 02492 # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885 02493 wfSuppressWarnings(); 02494 $text = iconv( $in, $out . '//IGNORE', $string ); 02495 wfRestoreWarnings(); 02496 return $text; 02497 } 02498 02499 // callback functions for uc(), lc(), ucwords(), ucwordbreaks() 02500 02505 function ucwordbreaksCallbackAscii( $matches ) { 02506 return $this->ucfirst( $matches[1] ); 02507 } 02508 02513 function ucwordbreaksCallbackMB( $matches ) { 02514 return mb_strtoupper( $matches[0] ); 02515 } 02516 02521 function ucCallback( $matches ) { 02522 list( $wikiUpperChars ) = self::getCaseMaps(); 02523 return strtr( $matches[1], $wikiUpperChars ); 02524 } 02525 02530 function lcCallback( $matches ) { 02531 list( , $wikiLowerChars ) = self::getCaseMaps(); 02532 return strtr( $matches[1], $wikiLowerChars ); 02533 } 02534 02539 function ucwordsCallbackMB( $matches ) { 02540 return mb_strtoupper( $matches[0] ); 02541 } 02542 02547 function ucwordsCallbackWiki( $matches ) { 02548 list( $wikiUpperChars ) = self::getCaseMaps(); 02549 return strtr( $matches[0], $wikiUpperChars ); 02550 } 02551 02559 function ucfirst( $str ) { 02560 $o = ord( $str ); 02561 if ( $o < 96 ) { // if already uppercase... 02562 return $str; 02563 } elseif ( $o < 128 ) { 02564 return ucfirst( $str ); // use PHP's ucfirst() 02565 } else { 02566 // fall back to more complex logic in case of multibyte strings 02567 return $this->uc( $str, true ); 02568 } 02569 } 02570 02579 function uc( $str, $first = false ) { 02580 if ( function_exists( 'mb_strtoupper' ) ) { 02581 if ( $first ) { 02582 if ( $this->isMultibyte( $str ) ) { 02583 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 ); 02584 } else { 02585 return ucfirst( $str ); 02586 } 02587 } else { 02588 return $this->isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str ); 02589 } 02590 } else { 02591 if ( $this->isMultibyte( $str ) ) { 02592 $x = $first ? '^' : ''; 02593 return preg_replace_callback( 02594 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/", 02595 array( $this, 'ucCallback' ), 02596 $str 02597 ); 02598 } else { 02599 return $first ? ucfirst( $str ) : strtoupper( $str ); 02600 } 02601 } 02602 } 02603 02608 function lcfirst( $str ) { 02609 $o = ord( $str ); 02610 if ( !$o ) { 02611 return strval( $str ); 02612 } elseif ( $o >= 128 ) { 02613 return $this->lc( $str, true ); 02614 } elseif ( $o > 96 ) { 02615 return $str; 02616 } else { 02617 $str[0] = strtolower( $str[0] ); 02618 return $str; 02619 } 02620 } 02621 02627 function lc( $str, $first = false ) { 02628 if ( function_exists( 'mb_strtolower' ) ) { 02629 if ( $first ) { 02630 if ( $this->isMultibyte( $str ) ) { 02631 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 ); 02632 } else { 02633 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ); 02634 } 02635 } else { 02636 return $this->isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str ); 02637 } 02638 } else { 02639 if ( $this->isMultibyte( $str ) ) { 02640 $x = $first ? '^' : ''; 02641 return preg_replace_callback( 02642 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/", 02643 array( $this, 'lcCallback' ), 02644 $str 02645 ); 02646 } else { 02647 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str ); 02648 } 02649 } 02650 } 02651 02656 function isMultibyte( $str ) { 02657 return (bool)preg_match( '/[\x80-\xff]/', $str ); 02658 } 02659 02664 function ucwords( $str ) { 02665 if ( $this->isMultibyte( $str ) ) { 02666 $str = $this->lc( $str ); 02667 02668 // regexp to find first letter in each word (i.e. after each space) 02669 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/"; 02670 02671 // function to use to capitalize a single char 02672 if ( function_exists( 'mb_strtoupper' ) ) { 02673 return preg_replace_callback( 02674 $replaceRegexp, 02675 array( $this, 'ucwordsCallbackMB' ), 02676 $str 02677 ); 02678 } else { 02679 return preg_replace_callback( 02680 $replaceRegexp, 02681 array( $this, 'ucwordsCallbackWiki' ), 02682 $str 02683 ); 02684 } 02685 } else { 02686 return ucwords( strtolower( $str ) ); 02687 } 02688 } 02689 02696 function ucwordbreaks( $str ) { 02697 if ( $this->isMultibyte( $str ) ) { 02698 $str = $this->lc( $str ); 02699 02700 // since \b doesn't work for UTF-8, we explicitely define word break chars 02701 $breaks = "[ \-\(\)\}\{\.,\?!]"; 02702 02703 // find first letter after word break 02704 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|" . 02705 "$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/"; 02706 02707 if ( function_exists( 'mb_strtoupper' ) ) { 02708 return preg_replace_callback( 02709 $replaceRegexp, 02710 array( $this, 'ucwordbreaksCallbackMB' ), 02711 $str 02712 ); 02713 } else { 02714 return preg_replace_callback( 02715 $replaceRegexp, 02716 array( $this, 'ucwordsCallbackWiki' ), 02717 $str 02718 ); 02719 } 02720 } else { 02721 return preg_replace_callback( 02722 '/\b([\w\x80-\xff]+)\b/', 02723 array( $this, 'ucwordbreaksCallbackAscii' ), 02724 $str 02725 ); 02726 } 02727 } 02728 02744 function caseFold( $s ) { 02745 return $this->uc( $s ); 02746 } 02747 02752 function checkTitleEncoding( $s ) { 02753 if ( is_array( $s ) ) { 02754 throw new MWException( 'Given array to checkTitleEncoding.' ); 02755 } 02756 if ( StringUtils::isUtf8( $s ) ) { 02757 return $s; 02758 } 02759 02760 return $this->iconv( $this->fallback8bitEncoding(), 'utf-8', $s ); 02761 } 02762 02766 function fallback8bitEncoding() { 02767 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' ); 02768 } 02769 02778 function hasWordBreaks() { 02779 return true; 02780 } 02781 02789 function segmentByWord( $string ) { 02790 return $string; 02791 } 02792 02800 function normalizeForSearch( $string ) { 02801 return self::convertDoubleWidth( $string ); 02802 } 02803 02812 protected static function convertDoubleWidth( $string ) { 02813 static $full = null; 02814 static $half = null; 02815 02816 if ( $full === null ) { 02817 $fullWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 02818 $halfWidth = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 02819 $full = str_split( $fullWidth, 3 ); 02820 $half = str_split( $halfWidth ); 02821 } 02822 02823 $string = str_replace( $full, $half, $string ); 02824 return $string; 02825 } 02826 02832 protected static function insertSpace( $string, $pattern ) { 02833 $string = preg_replace( $pattern, " $1 ", $string ); 02834 $string = preg_replace( '/ +/', ' ', $string ); 02835 return $string; 02836 } 02837 02842 function convertForSearchResult( $termsArray ) { 02843 # some languages, e.g. Chinese, need to do a conversion 02844 # in order for search results to be displayed correctly 02845 return $termsArray; 02846 } 02847 02854 function firstChar( $s ) { 02855 $matches = array(); 02856 preg_match( 02857 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' . 02858 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', 02859 $s, 02860 $matches 02861 ); 02862 02863 if ( isset( $matches[1] ) ) { 02864 if ( strlen( $matches[1] ) != 3 ) { 02865 return $matches[1]; 02866 } 02867 02868 // Break down Hangul syllables to grab the first jamo 02869 $code = utf8ToCodepoint( $matches[1] ); 02870 if ( $code < 0xac00 || 0xd7a4 <= $code ) { 02871 return $matches[1]; 02872 } elseif ( $code < 0xb098 ) { 02873 return "\xe3\x84\xb1"; 02874 } elseif ( $code < 0xb2e4 ) { 02875 return "\xe3\x84\xb4"; 02876 } elseif ( $code < 0xb77c ) { 02877 return "\xe3\x84\xb7"; 02878 } elseif ( $code < 0xb9c8 ) { 02879 return "\xe3\x84\xb9"; 02880 } elseif ( $code < 0xbc14 ) { 02881 return "\xe3\x85\x81"; 02882 } elseif ( $code < 0xc0ac ) { 02883 return "\xe3\x85\x82"; 02884 } elseif ( $code < 0xc544 ) { 02885 return "\xe3\x85\x85"; 02886 } elseif ( $code < 0xc790 ) { 02887 return "\xe3\x85\x87"; 02888 } elseif ( $code < 0xcc28 ) { 02889 return "\xe3\x85\x88"; 02890 } elseif ( $code < 0xce74 ) { 02891 return "\xe3\x85\x8a"; 02892 } elseif ( $code < 0xd0c0 ) { 02893 return "\xe3\x85\x8b"; 02894 } elseif ( $code < 0xd30c ) { 02895 return "\xe3\x85\x8c"; 02896 } elseif ( $code < 0xd558 ) { 02897 return "\xe3\x85\x8d"; 02898 } else { 02899 return "\xe3\x85\x8e"; 02900 } 02901 } else { 02902 return ''; 02903 } 02904 } 02905 02906 function initEncoding() { 02907 # Some languages may have an alternate char encoding option 02908 # (Esperanto X-coding, Japanese furigana conversion, etc) 02909 # If this language is used as the primary content language, 02910 # an override to the defaults can be set here on startup. 02911 } 02912 02917 function recodeForEdit( $s ) { 02918 # For some languages we'll want to explicitly specify 02919 # which characters make it into the edit box raw 02920 # or are converted in some way or another. 02921 global $wgEditEncoding; 02922 if ( $wgEditEncoding == '' || $wgEditEncoding == 'UTF-8' ) { 02923 return $s; 02924 } else { 02925 return $this->iconv( 'UTF-8', $wgEditEncoding, $s ); 02926 } 02927 } 02928 02933 function recodeInput( $s ) { 02934 # Take the previous into account. 02935 global $wgEditEncoding; 02936 if ( $wgEditEncoding != '' ) { 02937 $enc = $wgEditEncoding; 02938 } else { 02939 $enc = 'UTF-8'; 02940 } 02941 if ( $enc == 'UTF-8' ) { 02942 return $s; 02943 } else { 02944 return $this->iconv( $enc, 'UTF-8', $s ); 02945 } 02946 } 02947 02959 function normalize( $s ) { 02960 global $wgAllUnicodeFixes; 02961 $s = UtfNormal::cleanUp( $s ); 02962 if ( $wgAllUnicodeFixes ) { 02963 $s = $this->transformUsingPairFile( 'normalize-ar.ser', $s ); 02964 $s = $this->transformUsingPairFile( 'normalize-ml.ser', $s ); 02965 } 02966 02967 return $s; 02968 } 02969 02984 function transformUsingPairFile( $file, $string ) { 02985 if ( !isset( $this->transformData[$file] ) ) { 02986 $data = wfGetPrecompiledData( $file ); 02987 if ( $data === false ) { 02988 throw new MWException( __METHOD__ . ": The transformation file $file is missing" ); 02989 } 02990 $this->transformData[$file] = new ReplacementArray( $data ); 02991 } 02992 return $this->transformData[$file]->replace( $string ); 02993 } 02994 03000 function isRTL() { 03001 return self::$dataCache->getItem( $this->mCode, 'rtl' ); 03002 } 03003 03008 function getDir() { 03009 return $this->isRTL() ? 'rtl' : 'ltr'; 03010 } 03011 03020 function alignStart() { 03021 return $this->isRTL() ? 'right' : 'left'; 03022 } 03023 03032 function alignEnd() { 03033 return $this->isRTL() ? 'left' : 'right'; 03034 } 03035 03047 function getDirMarkEntity( $opposite = false ) { 03048 if ( $opposite ) { 03049 return $this->isRTL() ? '‎' : '‏'; 03050 } 03051 return $this->isRTL() ? '‏' : '‎'; 03052 } 03053 03064 function getDirMark( $opposite = false ) { 03065 $lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM 03066 $rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM 03067 if ( $opposite ) { 03068 return $this->isRTL() ? $lrm : $rlm; 03069 } 03070 return $this->isRTL() ? $rlm : $lrm; 03071 } 03072 03076 function capitalizeAllNouns() { 03077 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' ); 03078 } 03079 03087 function getArrow( $direction = 'forwards' ) { 03088 switch ( $direction ) { 03089 case 'forwards': 03090 return $this->isRTL() ? '←' : '→'; 03091 case 'backwards': 03092 return $this->isRTL() ? '→' : '←'; 03093 case 'left': 03094 return '←'; 03095 case 'right': 03096 return '→'; 03097 case 'up': 03098 return '↑'; 03099 case 'down': 03100 return '↓'; 03101 } 03102 } 03103 03109 function linkPrefixExtension() { 03110 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' ); 03111 } 03112 03117 function getMagicWords() { 03118 return self::$dataCache->getItem( $this->mCode, 'magicWords' ); 03119 } 03120 03124 protected function doMagicHook() { 03125 if ( $this->mMagicHookDone ) { 03126 return; 03127 } 03128 $this->mMagicHookDone = true; 03129 wfProfileIn( 'LanguageGetMagic' ); 03130 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) ); 03131 wfProfileOut( 'LanguageGetMagic' ); 03132 } 03133 03139 function getMagic( $mw ) { 03140 // Saves a function call 03141 if ( !$this->mMagicHookDone ) { 03142 $this->doMagicHook(); 03143 } 03144 03145 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) { 03146 $rawEntry = $this->mMagicExtensions[$mw->mId]; 03147 } else { 03148 $rawEntry = self::$dataCache->getSubitem( 03149 $this->mCode, 'magicWords', $mw->mId ); 03150 } 03151 03152 if ( !is_array( $rawEntry ) ) { 03153 wfWarn( "\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" ); 03154 } else { 03155 $mw->mCaseSensitive = $rawEntry[0]; 03156 $mw->mSynonyms = array_slice( $rawEntry, 1 ); 03157 } 03158 } 03159 03165 function addMagicWordsByLang( $newWords ) { 03166 $fallbackChain = $this->getFallbackLanguages(); 03167 $fallbackChain = array_reverse( $fallbackChain ); 03168 foreach ( $fallbackChain as $code ) { 03169 if ( isset( $newWords[$code] ) ) { 03170 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions; 03171 } 03172 } 03173 } 03174 03180 function getSpecialPageAliases() { 03181 // Cache aliases because it may be slow to load them 03182 if ( is_null( $this->mExtendedSpecialPageAliases ) ) { 03183 // Initialise array 03184 $this->mExtendedSpecialPageAliases = 03185 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' ); 03186 wfRunHooks( 'LanguageGetSpecialPageAliases', 03187 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) ); 03188 } 03189 03190 return $this->mExtendedSpecialPageAliases; 03191 } 03192 03199 function emphasize( $text ) { 03200 return "<em>$text</em>"; 03201 } 03202 03225 public function formatNum( $number, $nocommafy = false ) { 03226 global $wgTranslateNumerals; 03227 if ( !$nocommafy ) { 03228 $number = $this->commafy( $number ); 03229 $s = $this->separatorTransformTable(); 03230 if ( $s ) { 03231 $number = strtr( $number, $s ); 03232 } 03233 } 03234 03235 if ( $wgTranslateNumerals ) { 03236 $s = $this->digitTransformTable(); 03237 if ( $s ) { 03238 $number = strtr( $number, $s ); 03239 } 03240 } 03241 03242 return $number; 03243 } 03244 03253 public function formatNumNoSeparators( $number ) { 03254 return $this->formatNum( $number, true ); 03255 } 03256 03261 public function parseFormattedNumber( $number ) { 03262 $s = $this->digitTransformTable(); 03263 if ( $s ) { 03264 // eliminate empty array values such as ''. (bug 64347) 03265 $s = array_filter( $s ); 03266 $number = strtr( $number, array_flip( $s ) ); 03267 } 03268 03269 $s = $this->separatorTransformTable(); 03270 if ( $s ) { 03271 // eliminate empty array values such as ''. (bug 64347) 03272 $s = array_filter( $s ); 03273 $number = strtr( $number, array_flip( $s ) ); 03274 } 03275 03276 $number = strtr( $number, array( ',' => '' ) ); 03277 return $number; 03278 } 03279 03286 function commafy( $number ) { 03287 $digitGroupingPattern = $this->digitGroupingPattern(); 03288 if ( $number === null ) { 03289 return ''; 03290 } 03291 03292 if ( !$digitGroupingPattern || $digitGroupingPattern === "###,###,###" ) { 03293 // default grouping is at thousands, use the same for ###,###,### pattern too. 03294 return strrev( (string)preg_replace( '/(\d{3})(?=\d)(?!\d*\.)/', '$1,', strrev( $number ) ) ); 03295 } else { 03296 // Ref: http://cldr.unicode.org/translation/number-patterns 03297 $sign = ""; 03298 if ( intval( $number ) < 0 ) { 03299 // For negative numbers apply the algorithm like positive number and add sign. 03300 $sign = "-"; 03301 $number = substr( $number, 1 ); 03302 } 03303 $integerPart = array(); 03304 $decimalPart = array(); 03305 $numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches ); 03306 preg_match( "/\d+/", $number, $integerPart ); 03307 preg_match( "/\.\d*/", $number, $decimalPart ); 03308 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : ""; 03309 if ( $groupedNumber === $number ) { 03310 // the string does not have any number part. Eg: .12345 03311 return $sign . $groupedNumber; 03312 } 03313 $start = $end = strlen( $integerPart[0] ); 03314 while ( $start > 0 ) { 03315 $match = $matches[0][$numMatches - 1]; 03316 $matchLen = strlen( $match ); 03317 $start = $end - $matchLen; 03318 if ( $start < 0 ) { 03319 $start = 0; 03320 } 03321 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber; 03322 $end = $start; 03323 if ( $numMatches > 1 ) { 03324 // use the last pattern for the rest of the number 03325 $numMatches--; 03326 } 03327 if ( $start > 0 ) { 03328 $groupedNumber = "," . $groupedNumber; 03329 } 03330 } 03331 return $sign . $groupedNumber; 03332 } 03333 } 03334 03338 function digitGroupingPattern() { 03339 return self::$dataCache->getItem( $this->mCode, 'digitGroupingPattern' ); 03340 } 03341 03345 function digitTransformTable() { 03346 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' ); 03347 } 03348 03352 function separatorTransformTable() { 03353 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' ); 03354 } 03355 03365 function listToText( array $l ) { 03366 $m = count( $l ) - 1; 03367 if ( $m < 0 ) { 03368 return ''; 03369 } 03370 if ( $m > 0 ) { 03371 $and = $this->getMessageFromDB( 'and' ); 03372 $space = $this->getMessageFromDB( 'word-separator' ); 03373 if ( $m > 1 ) { 03374 $comma = $this->getMessageFromDB( 'comma-separator' ); 03375 } 03376 } 03377 $s = $l[$m]; 03378 for ( $i = $m - 1; $i >= 0; $i-- ) { 03379 if ( $i == $m - 1 ) { 03380 $s = $l[$i] . $and . $space . $s; 03381 } else { 03382 $s = $l[$i] . $comma . $s; 03383 } 03384 } 03385 return $s; 03386 } 03387 03394 function commaList( array $list ) { 03395 return implode( 03396 wfMessage( 'comma-separator' )->inLanguage( $this )->escaped(), 03397 $list 03398 ); 03399 } 03400 03407 function semicolonList( array $list ) { 03408 return implode( 03409 wfMessage( 'semicolon-separator' )->inLanguage( $this )->escaped(), 03410 $list 03411 ); 03412 } 03413 03419 function pipeList( array $list ) { 03420 return implode( 03421 wfMessage( 'pipe-separator' )->inLanguage( $this )->escaped(), 03422 $list 03423 ); 03424 } 03425 03443 function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) { 03444 # Use the localized ellipsis character 03445 if ( $ellipsis == '...' ) { 03446 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); 03447 } 03448 # Check if there is no need to truncate 03449 if ( $length == 0 ) { 03450 return $ellipsis; // convention 03451 } elseif ( strlen( $string ) <= abs( $length ) ) { 03452 return $string; // no need to truncate 03453 } 03454 $stringOriginal = $string; 03455 # If ellipsis length is >= $length then we can't apply $adjustLength 03456 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) { 03457 $string = $ellipsis; // this can be slightly unexpected 03458 # Otherwise, truncate and add ellipsis... 03459 } else { 03460 $eLength = $adjustLength ? strlen( $ellipsis ) : 0; 03461 if ( $length > 0 ) { 03462 $length -= $eLength; 03463 $string = substr( $string, 0, $length ); // xyz... 03464 $string = $this->removeBadCharLast( $string ); 03465 $string = rtrim( $string ); 03466 $string = $string . $ellipsis; 03467 } else { 03468 $length += $eLength; 03469 $string = substr( $string, $length ); // ...xyz 03470 $string = $this->removeBadCharFirst( $string ); 03471 $string = ltrim( $string ); 03472 $string = $ellipsis . $string; 03473 } 03474 } 03475 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181). 03476 # This check is *not* redundant if $adjustLength, due to the single case where 03477 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string. 03478 if ( strlen( $string ) < strlen( $stringOriginal ) ) { 03479 return $string; 03480 } else { 03481 return $stringOriginal; 03482 } 03483 } 03484 03492 protected function removeBadCharLast( $string ) { 03493 if ( $string != '' ) { 03494 $char = ord( $string[strlen( $string ) - 1] ); 03495 $m = array(); 03496 if ( $char >= 0xc0 ) { 03497 # We got the first byte only of a multibyte char; remove it. 03498 $string = substr( $string, 0, -1 ); 03499 } elseif ( $char >= 0x80 && 03500 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' . 03501 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) 03502 ) { 03503 # We chopped in the middle of a character; remove it 03504 $string = $m[1]; 03505 } 03506 } 03507 return $string; 03508 } 03509 03517 protected function removeBadCharFirst( $string ) { 03518 if ( $string != '' ) { 03519 $char = ord( $string[0] ); 03520 if ( $char >= 0x80 && $char < 0xc0 ) { 03521 # We chopped in the middle of a character; remove the whole thing 03522 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string ); 03523 } 03524 } 03525 return $string; 03526 } 03527 03543 function truncateHtml( $text, $length, $ellipsis = '...' ) { 03544 # Use the localized ellipsis character 03545 if ( $ellipsis == '...' ) { 03546 $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); 03547 } 03548 # Check if there is clearly no need to truncate 03549 if ( $length <= 0 ) { 03550 return $ellipsis; // no text shown, nothing to format (convention) 03551 } elseif ( strlen( $text ) <= $length ) { 03552 return $text; // string short enough even *with* HTML (short-circuit) 03553 } 03554 03555 $dispLen = 0; // innerHTML legth so far 03556 $testingEllipsis = false; // checking if ellipses will make string longer/equal? 03557 $tagType = 0; // 0-open, 1-close 03558 $bracketState = 0; // 1-tag start, 2-tag name, 0-neither 03559 $entityState = 0; // 0-not entity, 1-entity 03560 $tag = $ret = ''; // accumulated tag name, accumulated result string 03561 $openTags = array(); // open tag stack 03562 $maybeState = null; // possible truncation state 03563 03564 $textLen = strlen( $text ); 03565 $neLength = max( 0, $length - strlen( $ellipsis ) ); // non-ellipsis len if truncated 03566 for ( $pos = 0; true; ++$pos ) { 03567 # Consider truncation once the display length has reached the maximim. 03568 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case. 03569 # Check that we're not in the middle of a bracket/entity... 03570 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) { 03571 if ( !$testingEllipsis ) { 03572 $testingEllipsis = true; 03573 # Save where we are; we will truncate here unless there turn out to 03574 # be so few remaining characters that truncation is not necessary. 03575 if ( !$maybeState ) { // already saved? ($neLength = 0 case) 03576 $maybeState = array( $ret, $openTags ); // save state 03577 } 03578 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) { 03579 # String in fact does need truncation, the truncation point was OK. 03580 list( $ret, $openTags ) = $maybeState; // reload state 03581 $ret = $this->removeBadCharLast( $ret ); // multi-byte char fix 03582 $ret .= $ellipsis; // add ellipsis 03583 break; 03584 } 03585 } 03586 if ( $pos >= $textLen ) { 03587 break; // extra iteration just for above checks 03588 } 03589 03590 # Read the next char... 03591 $ch = $text[$pos]; 03592 $lastCh = $pos ? $text[$pos - 1] : ''; 03593 $ret .= $ch; // add to result string 03594 if ( $ch == '<' ) { 03595 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); // for bad HTML 03596 $entityState = 0; // for bad HTML 03597 $bracketState = 1; // tag started (checking for backslash) 03598 } elseif ( $ch == '>' ) { 03599 $this->truncate_endBracket( $tag, $tagType, $lastCh, $openTags ); 03600 $entityState = 0; // for bad HTML 03601 $bracketState = 0; // out of brackets 03602 } elseif ( $bracketState == 1 ) { 03603 if ( $ch == '/' ) { 03604 $tagType = 1; // close tag (e.g. "</span>") 03605 } else { 03606 $tagType = 0; // open tag (e.g. "<span>") 03607 $tag .= $ch; 03608 } 03609 $bracketState = 2; // building tag name 03610 } elseif ( $bracketState == 2 ) { 03611 if ( $ch != ' ' ) { 03612 $tag .= $ch; 03613 } else { 03614 // Name found (e.g. "<a href=..."), add on tag attributes... 03615 $pos += $this->truncate_skip( $ret, $text, "<>", $pos + 1 ); 03616 } 03617 } elseif ( $bracketState == 0 ) { 03618 if ( $entityState ) { 03619 if ( $ch == ';' ) { 03620 $entityState = 0; 03621 $dispLen++; // entity is one displayed char 03622 } 03623 } else { 03624 if ( $neLength == 0 && !$maybeState ) { 03625 // Save state without $ch. We want to *hit* the first 03626 // display char (to get tags) but not *use* it if truncating. 03627 $maybeState = array( substr( $ret, 0, -1 ), $openTags ); 03628 } 03629 if ( $ch == '&' ) { 03630 $entityState = 1; // entity found, (e.g. " ") 03631 } else { 03632 $dispLen++; // this char is displayed 03633 // Add the next $max display text chars after this in one swoop... 03634 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen; 03635 $skipped = $this->truncate_skip( $ret, $text, "<>&", $pos + 1, $max ); 03636 $dispLen += $skipped; 03637 $pos += $skipped; 03638 } 03639 } 03640 } 03641 } 03642 // Close the last tag if left unclosed by bad HTML 03643 $this->truncate_endBracket( $tag, $text[$textLen - 1], $tagType, $openTags ); 03644 while ( count( $openTags ) > 0 ) { 03645 $ret .= '</' . array_pop( $openTags ) . '>'; // close open tags 03646 } 03647 return $ret; 03648 } 03649 03661 private function truncate_skip( &$ret, $text, $search, $start, $len = null ) { 03662 if ( $len === null ) { 03663 $len = -1; // -1 means "no limit" for strcspn 03664 } elseif ( $len < 0 ) { 03665 $len = 0; // sanity 03666 } 03667 $skipCount = 0; 03668 if ( $start < strlen( $text ) ) { 03669 $skipCount = strcspn( $text, $search, $start, $len ); 03670 $ret .= substr( $text, $start, $skipCount ); 03671 } 03672 return $skipCount; 03673 } 03674 03684 private function truncate_endBracket( &$tag, $tagType, $lastCh, &$openTags ) { 03685 $tag = ltrim( $tag ); 03686 if ( $tag != '' ) { 03687 if ( $tagType == 0 && $lastCh != '/' ) { 03688 $openTags[] = $tag; // tag opened (didn't close itself) 03689 } elseif ( $tagType == 1 ) { 03690 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) { 03691 array_pop( $openTags ); // tag closed 03692 } 03693 } 03694 $tag = ''; 03695 } 03696 } 03697 03706 function convertGrammar( $word, $case ) { 03707 global $wgGrammarForms; 03708 if ( isset( $wgGrammarForms[$this->getCode()][$case][$word] ) ) { 03709 return $wgGrammarForms[$this->getCode()][$case][$word]; 03710 } 03711 03712 return $word; 03713 } 03719 function getGrammarForms() { 03720 global $wgGrammarForms; 03721 if ( isset( $wgGrammarForms[$this->getCode()] ) 03722 && is_array( $wgGrammarForms[$this->getCode()] ) 03723 ) { 03724 return $wgGrammarForms[$this->getCode()]; 03725 } 03726 03727 return array(); 03728 } 03748 function gender( $gender, $forms ) { 03749 if ( !count( $forms ) ) { 03750 return ''; 03751 } 03752 $forms = $this->preConvertPlural( $forms, 2 ); 03753 if ( $gender === 'male' ) { 03754 return $forms[0]; 03755 } 03756 if ( $gender === 'female' ) { 03757 return $forms[1]; 03758 } 03759 return isset( $forms[2] ) ? $forms[2] : $forms[0]; 03760 } 03761 03777 function convertPlural( $count, $forms ) { 03778 // Handle explicit n=pluralform cases 03779 $forms = $this->handleExplicitPluralForms( $count, $forms ); 03780 if ( is_string( $forms ) ) { 03781 return $forms; 03782 } 03783 if ( !count( $forms ) ) { 03784 return ''; 03785 } 03786 03787 $pluralForm = $this->getPluralRuleIndexNumber( $count ); 03788 $pluralForm = min( $pluralForm, count( $forms ) - 1 ); 03789 return $forms[$pluralForm]; 03790 } 03791 03807 protected function handleExplicitPluralForms( $count, array $forms ) { 03808 foreach ( $forms as $index => $form ) { 03809 if ( preg_match( '/\d+=/i', $form ) ) { 03810 $pos = strpos( $form, '=' ); 03811 if ( substr( $form, 0, $pos ) === (string)$count ) { 03812 return substr( $form, $pos + 1 ); 03813 } 03814 unset( $forms[$index] ); 03815 } 03816 } 03817 return array_values( $forms ); 03818 } 03819 03828 protected function preConvertPlural( /* Array */ $forms, $count ) { 03829 while ( count( $forms ) < $count ) { 03830 $forms[] = $forms[count( $forms ) - 1]; 03831 } 03832 return $forms; 03833 } 03834 03846 function translateBlockExpiry( $str ) { 03847 $duration = SpecialBlock::getSuggestedDurations( $this ); 03848 foreach ( $duration as $show => $value ) { 03849 if ( strcmp( $str, $value ) == 0 ) { 03850 return htmlspecialchars( trim( $show ) ); 03851 } 03852 } 03853 03854 // Since usually only infinite or indefinite is only on list, so try 03855 // equivalents if still here. 03856 $indefs = array( 'infinite', 'infinity', 'indefinite' ); 03857 if ( in_array( $str, $indefs ) ) { 03858 foreach ( $indefs as $val ) { 03859 $show = array_search( $val, $duration, true ); 03860 if ( $show !== false ) { 03861 return htmlspecialchars( trim( $show ) ); 03862 } 03863 } 03864 } 03865 03866 // If all else fails, return a standard duration or timestamp description. 03867 $time = strtotime( $str, 0 ); 03868 if ( $time === false ) { // Unknown format. Return it as-is in case. 03869 return $str; 03870 } elseif ( $time !== strtotime( $str, 1 ) ) { // It's a relative timestamp. 03871 // $time is relative to 0 so it's a duration length. 03872 return $this->formatDuration( $time ); 03873 } else { // It's an absolute timestamp. 03874 if ( $time === 0 ) { 03875 // wfTimestamp() handles 0 as current time instead of epoch. 03876 return $this->timeanddate( '19700101000000' ); 03877 } else { 03878 return $this->timeanddate( $time ); 03879 } 03880 } 03881 } 03882 03890 public function segmentForDiff( $text ) { 03891 return $text; 03892 } 03893 03900 public function unsegmentForDiff( $text ) { 03901 return $text; 03902 } 03903 03910 public function getConverter() { 03911 return $this->mConverter; 03912 } 03913 03920 public function autoConvertToAllVariants( $text ) { 03921 return $this->mConverter->autoConvertToAllVariants( $text ); 03922 } 03923 03930 public function convert( $text ) { 03931 return $this->mConverter->convert( $text ); 03932 } 03933 03940 public function convertTitle( $title ) { 03941 return $this->mConverter->convertTitle( $title ); 03942 } 03943 03950 public function convertNamespace( $ns ) { 03951 return $this->mConverter->convertNamespace( $ns ); 03952 } 03953 03959 public function hasVariants() { 03960 return count( $this->getVariants() ) > 1; 03961 } 03962 03970 public function hasVariant( $variant ) { 03971 return (bool)$this->mConverter->validateVariant( $variant ); 03972 } 03973 03981 public function armourMath( $text ) { 03982 return $this->mConverter->armourMath( $text ); 03983 } 03984 03992 public function convertHtml( $text, $isTitle = false ) { 03993 return htmlspecialchars( $this->convert( $text, $isTitle ) ); 03994 } 03995 04000 public function convertCategoryKey( $key ) { 04001 return $this->mConverter->convertCategoryKey( $key ); 04002 } 04003 04010 public function getVariants() { 04011 return $this->mConverter->getVariants(); 04012 } 04013 04017 public function getPreferredVariant() { 04018 return $this->mConverter->getPreferredVariant(); 04019 } 04020 04024 public function getDefaultVariant() { 04025 return $this->mConverter->getDefaultVariant(); 04026 } 04027 04031 public function getURLVariant() { 04032 return $this->mConverter->getURLVariant(); 04033 } 04034 04047 public function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) { 04048 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond ); 04049 } 04050 04057 function getExtraHashOptions() { 04058 return $this->mConverter->getExtraHashOptions(); 04059 } 04060 04068 public function getParsedTitle() { 04069 return $this->mConverter->getParsedTitle(); 04070 } 04071 04084 public function markNoConversion( $text, $noParse = false ) { 04085 // Excluding protocal-relative URLs may avoid many false positives. 04086 if ( $noParse || preg_match( '/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text ) ) { 04087 return $this->mConverter->markNoConversion( $text ); 04088 } else { 04089 return $text; 04090 } 04091 } 04092 04099 public function linkTrail() { 04100 return self::$dataCache->getItem( $this->mCode, 'linkTrail' ); 04101 } 04102 04109 public function linkPrefixCharset() { 04110 return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' ); 04111 } 04112 04117 function getLangObj() { 04118 wfDeprecated( __METHOD__, '1.24' ); 04119 return $this; 04120 } 04121 04129 public function getParentLanguage() { 04130 if ( $this->mParentLanguage !== false ) { 04131 return $this->mParentLanguage; 04132 } 04133 04134 $pieces = explode( '-', $this->getCode() ); 04135 $code = $pieces[0]; 04136 if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) { 04137 $this->mParentLanguage = null; 04138 return null; 04139 } 04140 $lang = Language::factory( $code ); 04141 if ( !$lang->hasVariant( $this->getCode() ) ) { 04142 $this->mParentLanguage = null; 04143 return null; 04144 } 04145 04146 $this->mParentLanguage = $lang; 04147 return $lang; 04148 } 04149 04158 public function getCode() { 04159 return $this->mCode; 04160 } 04161 04172 public function getHtmlCode() { 04173 if ( is_null( $this->mHtmlCode ) ) { 04174 $this->mHtmlCode = wfBCP47( $this->getCode() ); 04175 } 04176 return $this->mHtmlCode; 04177 } 04178 04182 public function setCode( $code ) { 04183 $this->mCode = $code; 04184 // Ensure we don't leave incorrect cached data lying around 04185 $this->mHtmlCode = null; 04186 $this->mParentLanguage = false; 04187 } 04188 04197 public static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) { 04198 if ( !self::isValidBuiltInCode( $code ) ) { 04199 throw new MWException( "Invalid language code \"$code\"" ); 04200 } 04201 04202 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix; 04203 } 04204 04212 public static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) { 04213 $m = null; 04214 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' . 04215 preg_quote( $suffix, '/' ) . '/', $filename, $m ); 04216 if ( !count( $m ) ) { 04217 return false; 04218 } 04219 return str_replace( '_', '-', strtolower( $m[1] ) ); 04220 } 04221 04226 public static function getMessagesFileName( $code ) { 04227 global $IP; 04228 $file = self::getFileName( "$IP/languages/messages/Messages", $code, '.php' ); 04229 wfRunHooks( 'Language::getMessagesFileName', array( $code, &$file ) ); 04230 return $file; 04231 } 04232 04238 public static function getJsonMessagesFileName( $code ) { 04239 global $IP; 04240 04241 if ( !self::isValidBuiltInCode( $code ) ) { 04242 throw new MWException( "Invalid language code \"$code\"" ); 04243 } 04244 04245 return "$IP/languages/i18n/$code.json"; 04246 } 04247 04252 public static function getClassFileName( $code ) { 04253 global $IP; 04254 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' ); 04255 } 04256 04264 public static function getFallbackFor( $code ) { 04265 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { 04266 return false; 04267 } else { 04268 $fallbacks = self::getFallbacksFor( $code ); 04269 $first = array_shift( $fallbacks ); 04270 return $first; 04271 } 04272 } 04273 04281 public static function getFallbacksFor( $code ) { 04282 if ( $code === 'en' || !Language::isValidBuiltInCode( $code ) ) { 04283 return array(); 04284 } else { 04285 $v = self::getLocalisationCache()->getItem( $code, 'fallback' ); 04286 $v = array_map( 'trim', explode( ',', $v ) ); 04287 if ( $v[count( $v ) - 1] !== 'en' ) { 04288 $v[] = 'en'; 04289 } 04290 return $v; 04291 } 04292 } 04293 04302 public static function getFallbacksIncludingSiteLanguage( $code ) { 04303 global $wgLanguageCode; 04304 04305 // Usually, we will only store a tiny number of fallback chains, so we 04306 // keep them in static memory. 04307 $cacheKey = "{$code}-{$wgLanguageCode}"; 04308 04309 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) { 04310 $fallbacks = self::getFallbacksFor( $code ); 04311 04312 // Append the site's fallback chain, including the site language itself 04313 $siteFallbacks = self::getFallbacksFor( $wgLanguageCode ); 04314 array_unshift( $siteFallbacks, $wgLanguageCode ); 04315 04316 // Eliminate any languages already included in the chain 04317 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks ); 04318 04319 self::$fallbackLanguageCache[$cacheKey] = array( $fallbacks, $siteFallbacks ); 04320 } 04321 return self::$fallbackLanguageCache[$cacheKey]; 04322 } 04323 04333 public static function getMessagesFor( $code ) { 04334 return self::getLocalisationCache()->getItem( $code, 'messages' ); 04335 } 04336 04345 public static function getMessageFor( $key, $code ) { 04346 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key ); 04347 } 04348 04357 public static function getMessageKeysFor( $code ) { 04358 return self::getLocalisationCache()->getSubItemList( $code, 'messages' ); 04359 } 04360 04365 function fixVariableInNamespace( $talk ) { 04366 if ( strpos( $talk, '$1' ) === false ) { 04367 return $talk; 04368 } 04369 04370 global $wgMetaNamespace; 04371 $talk = str_replace( '$1', $wgMetaNamespace, $talk ); 04372 04373 # Allow grammar transformations 04374 # Allowing full message-style parsing would make simple requests 04375 # such as action=raw much more expensive than they need to be. 04376 # This will hopefully cover most cases. 04377 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i', 04378 array( &$this, 'replaceGrammarInNamespace' ), $talk ); 04379 return str_replace( ' ', '_', $talk ); 04380 } 04381 04386 function replaceGrammarInNamespace( $m ) { 04387 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) ); 04388 } 04389 04394 static function getCaseMaps() { 04395 static $wikiUpperChars, $wikiLowerChars; 04396 if ( isset( $wikiUpperChars ) ) { 04397 return array( $wikiUpperChars, $wikiLowerChars ); 04398 } 04399 04400 wfProfileIn( __METHOD__ ); 04401 $arr = wfGetPrecompiledData( 'Utf8Case.ser' ); 04402 if ( $arr === false ) { 04403 throw new MWException( 04404 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" ); 04405 } 04406 $wikiUpperChars = $arr['wikiUpperChars']; 04407 $wikiLowerChars = $arr['wikiLowerChars']; 04408 wfProfileOut( __METHOD__ ); 04409 return array( $wikiUpperChars, $wikiLowerChars ); 04410 } 04411 04423 public function formatExpiry( $expiry, $format = true ) { 04424 static $infinity; 04425 if ( $infinity === null ) { 04426 $infinity = wfGetDB( DB_SLAVE )->getInfinity(); 04427 } 04428 04429 if ( $expiry == '' || $expiry == $infinity ) { 04430 return $format === true 04431 ? $this->getMessageFromDB( 'infiniteblock' ) 04432 : $infinity; 04433 } else { 04434 return $format === true 04435 ? $this->timeanddate( $expiry, /* User preference timezone */ true ) 04436 : wfTimestamp( $format, $expiry ); 04437 } 04438 } 04439 04452 function formatTimePeriod( $seconds, $format = array() ) { 04453 if ( !is_array( $format ) ) { 04454 $format = array( 'avoid' => $format ); // For backwards compatibility 04455 } 04456 if ( !isset( $format['avoid'] ) ) { 04457 $format['avoid'] = false; 04458 } 04459 if ( !isset( $format['noabbrevs'] ) ) { 04460 $format['noabbrevs'] = false; 04461 } 04462 $secondsMsg = wfMessage( 04463 $format['noabbrevs'] ? 'seconds' : 'seconds-abbrev' )->inLanguage( $this ); 04464 $minutesMsg = wfMessage( 04465 $format['noabbrevs'] ? 'minutes' : 'minutes-abbrev' )->inLanguage( $this ); 04466 $hoursMsg = wfMessage( 04467 $format['noabbrevs'] ? 'hours' : 'hours-abbrev' )->inLanguage( $this ); 04468 $daysMsg = wfMessage( 04469 $format['noabbrevs'] ? 'days' : 'days-abbrev' )->inLanguage( $this ); 04470 04471 if ( round( $seconds * 10 ) < 100 ) { 04472 $s = $this->formatNum( sprintf( "%.1f", round( $seconds * 10 ) / 10 ) ); 04473 $s = $secondsMsg->params( $s )->text(); 04474 } elseif ( round( $seconds ) < 60 ) { 04475 $s = $this->formatNum( round( $seconds ) ); 04476 $s = $secondsMsg->params( $s )->text(); 04477 } elseif ( round( $seconds ) < 3600 ) { 04478 $minutes = floor( $seconds / 60 ); 04479 $secondsPart = round( fmod( $seconds, 60 ) ); 04480 if ( $secondsPart == 60 ) { 04481 $secondsPart = 0; 04482 $minutes++; 04483 } 04484 $s = $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04485 $s .= ' '; 04486 $s .= $secondsMsg->params( $this->formatNum( $secondsPart ) )->text(); 04487 } elseif ( round( $seconds ) <= 2 * 86400 ) { 04488 $hours = floor( $seconds / 3600 ); 04489 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 ); 04490 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 ); 04491 if ( $secondsPart == 60 ) { 04492 $secondsPart = 0; 04493 $minutes++; 04494 } 04495 if ( $minutes == 60 ) { 04496 $minutes = 0; 04497 $hours++; 04498 } 04499 $s = $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04500 $s .= ' '; 04501 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04502 if ( !in_array( $format['avoid'], array( 'avoidseconds', 'avoidminutes' ) ) ) { 04503 $s .= ' ' . $secondsMsg->params( $this->formatNum( $secondsPart ) )->text(); 04504 } 04505 } else { 04506 $days = floor( $seconds / 86400 ); 04507 if ( $format['avoid'] === 'avoidminutes' ) { 04508 $hours = round( ( $seconds - $days * 86400 ) / 3600 ); 04509 if ( $hours == 24 ) { 04510 $hours = 0; 04511 $days++; 04512 } 04513 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04514 $s .= ' '; 04515 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04516 } elseif ( $format['avoid'] === 'avoidseconds' ) { 04517 $hours = floor( ( $seconds - $days * 86400 ) / 3600 ); 04518 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 ); 04519 if ( $minutes == 60 ) { 04520 $minutes = 0; 04521 $hours++; 04522 } 04523 if ( $hours == 24 ) { 04524 $hours = 0; 04525 $days++; 04526 } 04527 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04528 $s .= ' '; 04529 $s .= $hoursMsg->params( $this->formatNum( $hours ) )->text(); 04530 $s .= ' '; 04531 $s .= $minutesMsg->params( $this->formatNum( $minutes ) )->text(); 04532 } else { 04533 $s = $daysMsg->params( $this->formatNum( $days ) )->text(); 04534 $s .= ' '; 04535 $s .= $this->formatTimePeriod( $seconds - $days * 86400, $format ); 04536 } 04537 } 04538 return $s; 04539 } 04540 04552 function formatBitrate( $bps ) { 04553 return $this->formatComputingNumbers( $bps, 1000, "bitrate-$1bits" ); 04554 } 04555 04562 function formatComputingNumbers( $size, $boundary, $messageKey ) { 04563 if ( $size <= 0 ) { 04564 return str_replace( '$1', $this->formatNum( $size ), 04565 $this->getMessageFromDB( str_replace( '$1', '', $messageKey ) ) 04566 ); 04567 } 04568 $sizes = array( '', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 'zeta', 'yotta' ); 04569 $index = 0; 04570 04571 $maxIndex = count( $sizes ) - 1; 04572 while ( $size >= $boundary && $index < $maxIndex ) { 04573 $index++; 04574 $size /= $boundary; 04575 } 04576 04577 // For small sizes no decimal places necessary 04578 $round = 0; 04579 if ( $index > 1 ) { 04580 // For MB and bigger two decimal places are smarter 04581 $round = 2; 04582 } 04583 $msg = str_replace( '$1', $sizes[$index], $messageKey ); 04584 04585 $size = round( $size, $round ); 04586 $text = $this->getMessageFromDB( $msg ); 04587 return str_replace( '$1', $this->formatNum( $size ), $text ); 04588 } 04589 04600 function formatSize( $size ) { 04601 return $this->formatComputingNumbers( $size, 1024, "size-$1bytes" ); 04602 } 04603 04613 function specialList( $page, $details, $oppositedm = true ) { 04614 $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . 04615 $this->getDirMark(); 04616 $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) . 04617 wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : ''; 04618 return $page . $details; 04619 } 04620 04631 public function viewPrevNext( Title $title, $offset, $limit, 04632 array $query = array(), $atend = false 04633 ) { 04634 // @todo FIXME: Why on earth this needs one message for the text and another one for tooltip? 04635 04636 # Make 'previous' link 04637 $prev = wfMessage( 'prevn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); 04638 if ( $offset > 0 ) { 04639 $plink = $this->numLink( $title, max( $offset - $limit, 0 ), $limit, 04640 $query, $prev, 'prevn-title', 'mw-prevlink' ); 04641 } else { 04642 $plink = htmlspecialchars( $prev ); 04643 } 04644 04645 # Make 'next' link 04646 $next = wfMessage( 'nextn' )->inLanguage( $this )->title( $title )->numParams( $limit )->text(); 04647 if ( $atend ) { 04648 $nlink = htmlspecialchars( $next ); 04649 } else { 04650 $nlink = $this->numLink( $title, $offset + $limit, $limit, 04651 $query, $next, 'nextn-title', 'mw-nextlink' ); 04652 } 04653 04654 # Make links to set number of items per page 04655 $numLinks = array(); 04656 foreach ( array( 20, 50, 100, 250, 500 ) as $num ) { 04657 $numLinks[] = $this->numLink( $title, $offset, $num, 04658 $query, $this->formatNum( $num ), 'shown-title', 'mw-numlink' ); 04659 } 04660 04661 return wfMessage( 'viewprevnext' )->inLanguage( $this )->title( $title 04662 )->rawParams( $plink, $nlink, $this->pipeList( $numLinks ) )->escaped(); 04663 } 04664 04677 private function numLink( Title $title, $offset, $limit, array $query, $link, 04678 $tooltipMsg, $class 04679 ) { 04680 $query = array( 'limit' => $limit, 'offset' => $offset ) + $query; 04681 $tooltip = wfMessage( $tooltipMsg )->inLanguage( $this )->title( $title ) 04682 ->numParams( $limit )->text(); 04683 04684 return Html::element( 'a', array( 'href' => $title->getLocalURL( $query ), 04685 'title' => $tooltip, 'class' => $class ), $link ); 04686 } 04687 04693 public function getConvRuleTitle() { 04694 return $this->mConverter->getConvRuleTitle(); 04695 } 04696 04702 public function getCompiledPluralRules() { 04703 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'compiledPluralRules' ); 04704 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04705 if ( !$pluralRules ) { 04706 foreach ( $fallbacks as $fallbackCode ) { 04707 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'compiledPluralRules' ); 04708 if ( $pluralRules ) { 04709 break; 04710 } 04711 } 04712 } 04713 return $pluralRules; 04714 } 04715 04721 public function getPluralRules() { 04722 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRules' ); 04723 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04724 if ( !$pluralRules ) { 04725 foreach ( $fallbacks as $fallbackCode ) { 04726 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRules' ); 04727 if ( $pluralRules ) { 04728 break; 04729 } 04730 } 04731 } 04732 return $pluralRules; 04733 } 04734 04740 public function getPluralRuleTypes() { 04741 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ), 'pluralRuleTypes' ); 04742 $fallbacks = Language::getFallbacksFor( $this->mCode ); 04743 if ( !$pluralRuleTypes ) { 04744 foreach ( $fallbacks as $fallbackCode ) { 04745 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ), 'pluralRuleTypes' ); 04746 if ( $pluralRuleTypes ) { 04747 break; 04748 } 04749 } 04750 } 04751 return $pluralRuleTypes; 04752 } 04753 04759 public function getPluralRuleIndexNumber( $number ) { 04760 $pluralRules = $this->getCompiledPluralRules(); 04761 $form = CLDRPluralRuleEvaluator::evaluateCompiled( $number, $pluralRules ); 04762 return $form; 04763 } 04764 04773 public function getPluralRuleType( $number ) { 04774 $index = $this->getPluralRuleIndexNumber( $number ); 04775 $pluralRuleTypes = $this->getPluralRuleTypes(); 04776 if ( isset( $pluralRuleTypes[$index] ) ) { 04777 return $pluralRuleTypes[$index]; 04778 } else { 04779 return 'other'; 04780 } 04781 } 04782 }