28 if ( !defined(
'MEDIAWIKI' ) ) {
29 echo
"This file is part of MediaWiki, it is not a valid entry point.\n";
33 if ( function_exists(
'mb_strtoupper' ) ) {
34 mb_internal_encoding(
'UTF-8' );
48 function autoConvert( $text, $variant =
false ) {
return $text; }
51 function convertTo( $text, $variant ) {
return $text; }
67 function validateVariant( $variant =
null ) {
return $variant === $this->mLang->getCode() ? $variant :
null; }
68 function translate( $text, $variant ) {
return $text; }
104 'sunday',
'monday',
'tuesday',
'wednesday',
'thursday',
109 'sun',
'mon',
'tue',
'wed',
'thu',
'fri',
'sat'
113 'january',
'february',
'march',
'april',
'may_long',
'june',
114 'july',
'august',
'september',
'october',
'november',
118 'january-gen',
'february-gen',
'march-gen',
'april-gen',
'may-gen',
'june-gen',
119 'july-gen',
'august-gen',
'september-gen',
'october-gen',
'november-gen',
123 'jan',
'feb',
'mar',
'apr',
'may',
'jun',
'jul',
'aug',
124 'sep',
'oct',
'nov',
'dec'
128 'iranian-calendar-m1',
'iranian-calendar-m2',
'iranian-calendar-m3',
129 'iranian-calendar-m4',
'iranian-calendar-m5',
'iranian-calendar-m6',
130 'iranian-calendar-m7',
'iranian-calendar-m8',
'iranian-calendar-m9',
131 'iranian-calendar-m10',
'iranian-calendar-m11',
'iranian-calendar-m12'
135 'hebrew-calendar-m1',
'hebrew-calendar-m2',
'hebrew-calendar-m3',
136 'hebrew-calendar-m4',
'hebrew-calendar-m5',
'hebrew-calendar-m6',
137 'hebrew-calendar-m7',
'hebrew-calendar-m8',
'hebrew-calendar-m9',
138 'hebrew-calendar-m10',
'hebrew-calendar-m11',
'hebrew-calendar-m12',
139 'hebrew-calendar-m6a',
'hebrew-calendar-m6b'
143 'hebrew-calendar-m1-gen',
'hebrew-calendar-m2-gen',
'hebrew-calendar-m3-gen',
144 'hebrew-calendar-m4-gen',
'hebrew-calendar-m5-gen',
'hebrew-calendar-m6-gen',
145 'hebrew-calendar-m7-gen',
'hebrew-calendar-m8-gen',
'hebrew-calendar-m9-gen',
146 'hebrew-calendar-m10-gen',
'hebrew-calendar-m11-gen',
'hebrew-calendar-m12-gen',
147 'hebrew-calendar-m6a-gen',
'hebrew-calendar-m6b-gen'
151 'hijri-calendar-m1',
'hijri-calendar-m2',
'hijri-calendar-m3',
152 'hijri-calendar-m4',
'hijri-calendar-m5',
'hijri-calendar-m6',
153 'hijri-calendar-m7',
'hijri-calendar-m8',
'hijri-calendar-m9',
154 'hijri-calendar-m10',
'hijri-calendar-m11',
'hijri-calendar-m12'
162 'millennia' => 31556952000,
163 'centuries' => 3155695200,
164 'decades' => 315569520,
186 static function factory( $code ) {
187 global $wgDummyLanguageCodes, $wgLangObjCacheSize;
189 if ( isset( $wgDummyLanguageCodes[$code] ) ) {
190 $code = $wgDummyLanguageCodes[$code];
194 $langObj = isset( self::$mLangObjCache[$code] )
195 ? self::$mLangObjCache[$code]
199 self::$mLangObjCache = array_merge(
array( $code => $langObj ), self::$mLangObjCache );
201 self::$mLangObjCache = array_slice( self::$mLangObjCache, 0, $wgLangObjCacheSize,
true );
215 || strcspn( $code,
":/\\\000" ) !== strlen( $code )
217 throw new MWException(
"Invalid language code \"$code\"" );
231 if ( class_exists( $class ) ) {
238 foreach ( $fallbacks
as $fallbackCode ) {
240 throw new MWException(
"Invalid fallback '$fallbackCode' in fallback sequence for '$code'" );
245 if ( class_exists( $class ) ) {
247 $lang->setCode( $code );
252 throw new MWException(
"Invalid fallback sequence for language '$code'" );
265 && ( is_readable( self::getMessagesFileName( $code ) )
266 || is_readable( self::getJsonMessagesFileName( $code ) )
288 $alphanum =
'[a-z0-9]';
289 $x =
'x'; #
private use singleton
290 $singleton =
'[a-wy-z]'; # other singleton
291 $s = $lenient ?
'[-_]' :
'-';
293 $language =
"$alpha{2,8}|$alpha{2,3}$s$alpha{3}";
294 $script =
"$alpha{4}"; # ISO 15924
295 $region =
"(?:$alpha{2}|$digit{3})"; # ISO 3166-1 alpha-2 or UN M.49
296 $variant =
"(?:$alphanum{5,8}|$digit$alphanum{3})";
297 $extension =
"$singleton(?:$s$alphanum{2,8})+";
298 $privateUse =
"$x(?:$s$alphanum{1,8})+";
300 # Define certain grandfathered codes, since otherwise the regex is pretty useless.
301 # Since these are limited, this is safe even later changes to the registry --
302 # the only oddity is that it might change the type of the tag, and thus
303 # the results from the capturing groups.
304 # http://www.iana.org/assignments/language-subtag-registry
306 $grandfathered =
"en{$s}GB{$s}oed"
307 .
"|i{$s}(?:ami|bnn|default|enochian|hak|klingon|lux|mingo|navajo|pwn|tao|tay|tsu)"
308 .
"|no{$s}(?:bok|nyn)"
309 .
"|sgn{$s}(?:BE{$s}(?:fr|nl)|CH{$s}de)"
310 .
"|zh{$s}min{$s}nan";
312 $variantList =
"$variant(?:$s$variant)*";
313 $extensionList =
"$extension(?:$s$extension)*";
315 $langtag =
"(?:($language)"
318 .
"(?:$s$variantList)?"
319 .
"(?:$s$extensionList)?"
320 .
"(?:$s$privateUse)?)";
322 # The final breakdown, with capturing groups for each of these components
323 # The variants, extensions, grandfathered, and private-use may have interior '-'
325 $root =
"^(?:$langtag|$privateUse|$grandfathered)$";
327 return (
bool)preg_match(
"/$root/", strtolower( $code ) );
341 if ( isset(
$cache[$code] ) ) {
349 strcspn( $code,
":/\\\000&<>'\"" ) === strlen( $code )
367 if ( !is_string( $code ) ) {
368 if ( is_object( $code ) ) {
369 $addmsg =
" of class " . get_class( $code );
373 $type = gettype( $code );
374 throw new MWException( __METHOD__ .
" must be passed a string, $type given$addmsg" );
377 return (
bool)preg_match(
'/^[a-z0-9-]{2,}$/i', $code );
393 if ( !self::isValidBuiltInCode( $tag ) ) {
399 include
"$IP/languages/Names.php";
403 || self::fetchLanguageName( $tag, $tag ) !==
''
416 if ( $code ==
'en' ) {
419 return 'Language' . str_replace(
'-',
'_',
ucfirst( $code ) );
431 if ( $class ===
'Language' ) {
435 if ( file_exists(
"$IP/languages/classes/$class.php" ) ) {
436 include_once
"$IP/languages/classes/$class.php";
446 if ( is_null( self::$dataCache ) ) {
447 global $wgLocalisationCacheConf;
448 $class = $wgLocalisationCacheConf[
'class'];
449 self::$dataCache =
new $class( $wgLocalisationCacheConf );
457 if ( get_class( $this ) ==
'Language' ) {
460 $this->mCode = str_replace(
'_',
'-', strtolower( substr( get_class( $this ), 8 ) ) );
470 unset( $this->
$name );
503 return self::$dataCache->getItem( $this->mCode,
'bookstoreList' );
513 if ( is_null( $this->namespaceNames ) ) {
514 global $wgMetaNamespace, $wgMetaNamespaceTalk, $wgExtraNamespaces;
516 $this->namespaceNames = self::$dataCache->getItem( $this->mCode,
'namespaceNames' );
519 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames + $validNamespaces;
521 $this->namespaceNames[
NS_PROJECT] = $wgMetaNamespace;
522 if ( $wgMetaNamespaceTalk ) {
530 # Sometimes a language will be localised but not actually exist on this wiki.
531 foreach ( $this->namespaceNames
as $key => $text ) {
532 if ( !isset( $validNamespaces[$key] ) ) {
533 unset( $this->namespaceNames[$key] );
537 # The above mixing may leave namespaces out of canonical order.
538 # Re-order by namespace ID number...
539 ksort( $this->namespaceNames );
541 wfRunHooks(
'LanguageGetNamespaces',
array( &$this->namespaceNames ) );
552 $this->mNamespaceIds =
null;
559 $this->namespaceNames =
null;
560 $this->mNamespaceIds =
null;
561 $this->namespaceAliases =
null;
574 foreach ( $ns
as $k => $v ) {
575 $ns[$k] = strtr( $v,
'_',
' ' );
592 return isset( $ns[$index] ) ? $ns[$index] :
false;
610 return strtr( $ns,
'_',
' ' );
622 global $wgExtraGenderNamespaces;
624 $ns = $wgExtraGenderNamespaces + self::$dataCache->getItem( $this->mCode,
'namespaceGenderAliases' );
625 return isset( $ns[$index][$gender] ) ? $ns[$index][$gender] : $this->
getNsText( $index );
635 global $wgExtraGenderNamespaces, $wgExtraNamespaces;
636 if ( count( $wgExtraGenderNamespaces ) > 0 ) {
639 } elseif ( isset( $wgExtraNamespaces[
NS_USER] ) && isset( $wgExtraNamespaces[
NS_USER_TALK] ) ) {
645 $aliases = self::$dataCache->getItem( $this->mCode,
'namespaceGenderAliases' );
646 return count( $aliases ) > 0;
659 $lctext = $this->
lc( $text );
661 return isset( $ids[$lctext] ) ? $ids[$lctext] :
false;
668 if ( is_null( $this->namespaceAliases ) ) {
669 $aliases = self::$dataCache->getItem( $this->mCode,
'namespaceAliases' );
673 foreach ( $aliases
as $name => $index ) {
675 unset( $aliases[
$name] );
677 $aliases[
$name] = $index;
682 global $wgExtraGenderNamespaces;
683 $genders = $wgExtraGenderNamespaces + (
array)self::$dataCache->getItem( $this->mCode,
'namespaceGenderAliases' );
684 foreach ( $genders
as $index => $forms ) {
685 foreach ( $forms
as $alias ) {
686 $aliases[$alias] = $index;
690 # Also add converted namespace names as aliases, to avoid confusion.
691 $convertedNames =
array();
693 if ( $variant === $this->mCode ) {
697 $convertedNames[$this->
getConverter()->convertNamespace( $ns, $variant )] = $ns;
701 $this->namespaceAliases = $aliases + $convertedNames;
710 if ( is_null( $this->mNamespaceIds ) ) {
712 # Put namespace names and aliases into a hashtable.
713 # If this is too slow, then we should arrange it so that it is done
714 # before caching. The catch is that at pre-cache time, the above
715 # class-specific fixup hasn't been done.
716 $this->mNamespaceIds =
array();
718 $this->mNamespaceIds[$this->
lc(
$name )] = $index;
721 $this->mNamespaceIds[$this->
lc(
$name )] = $index;
725 $this->mNamespaceIds[$this->
lc(
$name )] = $index;
740 $lctext = $this->
lc( $text );
742 if ( $ns !==
null ) {
746 return isset( $ids[$lctext] ) ? $ids[$lctext] :
false;
757 $msg =
"variantname-$code";
758 if ( $usemsg &&
wfMessage( $msg )->exists() ) {
763 return $name; #
if it's defined as a language name, show that
765 # otherwise, output the language code
774 function specialPage( $name ) {
775 $aliases = $this->getSpecialPageAliases();
776 if ( isset( $aliases[$name][0] ) ) {
777 $name = $aliases[$name][0];
779 return $this->getNsText( NS_SPECIAL ) . ':
' . $name;
785 function getDatePreferences() {
786 return self::$dataCache->getItem( $this->mCode, 'datePreferences
' );
792 function getDateFormats() {
793 return self::$dataCache->getItem( $this->mCode, 'dateFormats
' );
799 function getDefaultDateFormat() {
800 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat
' );
801 if ( $df === 'dmy or mdy
' ) {
802 global $wgAmericanDates;
803 return $wgAmericanDates ? 'mdy
' : 'dmy
';
812 function getDatePreferenceMigrationMap() {
813 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap
' );
820 function getImageFile( $image ) {
821 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles
', $image );
827 function getExtraUserToggles() {
828 return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles
' );
835 function getUserToggle( $tog ) {
836 return $this->getMessageFromDB( "tog-$tog" );
849 public static function getLanguageNames( $customisedOnly = false ) {
850 return self::fetchLanguageNames( null, $customisedOnly ? 'mwfile
' : 'mw
' );
862 public static function getTranslatedLanguageNames( $code ) {
863 return self::fetchLanguageNames( $code, 'all
' );
877 public static function fetchLanguageNames( $inLanguage = null, $include = 'mw
' ) {
878 global $wgExtraLanguageNames;
879 static $coreLanguageNames;
881 if ( $coreLanguageNames === null ) {
883 include "$IP/languages/Names.php";
886 // If passed an invalid language code to use, fallback to en
887 if ( $inLanguage !== null && !Language::isValidCode( $inLanguage ) ) {
894 # TODO: also include when $inLanguage is null, when this code is more efficient
895 wfRunHooks( 'LanguageGetTranslatedLanguageNames
', array( &$names, $inLanguage ) );
898 $mwNames = $wgExtraLanguageNames + $coreLanguageNames;
899 foreach ( $mwNames as $mwCode => $mwName ) {
900 # - Prefer own MediaWiki native name when not using the hook
901 # - For other names just add if not added through the hook
902 if ( $mwCode === $inLanguage || !isset( $names[$mwCode] ) ) {
903 $names[$mwCode] = $mwName;
907 if ( $include === 'all
' ) {
912 $coreCodes = array_keys( $mwNames );
913 foreach ( $coreCodes as $coreCode ) {
914 $returnMw[$coreCode] = $names[$coreCode];
917 if ( $include === 'mwfile
' ) {
918 $namesMwFile = array();
919 # We do this using a foreach over the codes instead of a directory
920 # loop so that messages files in extensions will work correctly.
921 foreach ( $returnMw as $code => $value ) {
922 if ( is_readable( self::getMessagesFileName( $code ) )
923 || is_readable( self::getJsonMessagesFileName( $code ) )
925 $namesMwFile[$code] = $names[$code];
932 # 'mw
' option; default if it's not one
of the other two
options (all/mwfile)
943 public static function fetchLanguageName( $code, $inLanguage =
null, $include =
'all' ) {
944 $code = strtolower( $code );
946 return !array_key_exists( $code, $array ) ?
'' : $array[$code];
956 return wfMessage( $msg )->inLanguage( $this )->text();
982 $monthNames =
array(
'' );
983 for ( $i = 1; $i < 13; $i++ ) {
1009 $monthNames =
array(
'' );
1010 for ( $i = 1; $i < 13; $i++ ) {
1037 return $this->
getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key - 1] );
1045 return $this->
getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key - 1] );
1053 return $this->
getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key - 1] );
1061 return $this->
getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key - 1] );
1129 function sprintfDate( $format, $ts, DateTimeZone $zone =
null ) {
1134 $dateTimeObj =
false;
1143 if ( strlen( $ts ) !== 14 ) {
1144 throw new MWException( __METHOD__ .
": The timestamp $ts should have 14 characters" );
1147 if ( !ctype_digit( $ts ) ) {
1148 throw new MWException( __METHOD__ .
": The timestamp $ts should be a number" );
1151 for ( $p = 0; $p < strlen( $format ); $p++ ) {
1153 $code = $format[$p];
1154 if ( $code ==
'x' && $p < strlen( $format ) - 1 ) {
1155 $code .= $format[++$p];
1158 if ( ( $code ===
'xi' || $code ==
'xj' || $code ==
'xk' || $code ==
'xm' || $code ==
'xo' || $code ==
'xt' ) && $p < strlen( $format ) - 1 ) {
1159 $code .= $format[++$p];
1170 $rawToggle = !$rawToggle;
1188 $num = substr( $ts, 6, 2 );
1191 if ( !$dateTimeObj ) {
1192 $dateTimeObj = DateTime::createFromFormat(
1193 'YmdHis', $ts, $zone ?:
new DateTimeZone(
'UTC' )
1199 $num = intval( substr( $ts, 6, 2 ) );
1220 if ( !$dateTimeObj ) {
1221 $dateTimeObj = DateTime::createFromFormat(
1222 'YmdHis', $ts, $zone ?:
new DateTimeZone(
'UTC' )
1249 $num = substr( $ts, 4, 2 );
1255 $num = intval( substr( $ts, 4, 2 ) );
1282 $num = substr( $ts, 0, 4 );
1321 $num = substr( $ts, 2, 2 );
1327 $num = substr( $iranian[0], -2 );
1330 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' :
'pm';
1333 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' :
'PM';
1336 $h = substr( $ts, 8, 2 );
1337 $num = $h % 12 ? $h % 12 : 12;
1340 $num = intval( substr( $ts, 8, 2 ) );
1343 $h = substr( $ts, 8, 2 );
1344 $num = sprintf(
'%02d', $h % 12 ? $h % 12 : 12 );
1347 $num = substr( $ts, 8, 2 );
1350 $num = substr( $ts, 10, 2 );
1353 $num = substr( $ts, 12, 2 );
1362 if ( !$dateTimeObj ) {
1363 $dateTimeObj = DateTime::createFromFormat(
1364 'YmdHis', $ts, $zone ?:
new DateTimeZone(
'UTC' )
1367 $s .= $dateTimeObj->format( $code );
1380 if ( !$dateTimeObj ) {
1381 $dateTimeObj = DateTime::createFromFormat(
1382 'YmdHis', $ts, $zone ?:
new DateTimeZone(
'UTC' )
1385 $num = $dateTimeObj->format( $code );
1388 # Backslash escaping
1389 if ( $p < strlen( $format ) - 1 ) {
1390 $s .= $format[++$p];
1397 if ( $p < strlen( $format ) - 1 ) {
1398 $endQuote = strpos( $format,
'"', $p + 1 );
1399 if ( $endQuote ===
false ) {
1400 # No terminating quote, assume literal "
1403 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
1407 # Quote at end of string, assume literal "
1414 if ( $num !==
false ) {
1415 if ( $rawToggle || $raw ) {
1418 } elseif ( $roman ) {
1421 } elseif ( $hebrewNum ) {
1432 private static $GREG_DAYS =
array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
1433 private static $IRANIAN_DAYS =
array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
1448 $gy = substr( $ts, 0, 4 ) -1600;
1449 $gm = substr( $ts, 4, 2 ) -1;
1450 $gd = substr( $ts, 6, 2 ) -1;
1452 # Days passed from the beginning (including leap years)
1454 + floor( ( $gy + 3 ) / 4 )
1455 - floor( ( $gy + 99 ) / 100 )
1456 + floor( ( $gy + 399 ) / 400 );
1459 for ( $i = 0; $i < $gm; $i++ ) {
1460 $gDayNo += self::$GREG_DAYS[$i];
1464 if ( $gm > 1 && ( ( $gy % 4 === 0 && $gy % 100 !== 0 || ( $gy % 400 == 0 ) ) ) ) {
1469 $gDayNo += (int)$gd;
1471 $jDayNo = $gDayNo - 79;
1473 $jNp = floor( $jDayNo / 12053 );
1476 $jy = 979 + 33 * $jNp + 4 * floor( $jDayNo / 1461 );
1479 if ( $jDayNo >= 366 ) {
1480 $jy += floor( ( $jDayNo - 1 ) / 365 );
1481 $jDayNo = floor( ( $jDayNo - 1 ) % 365 );
1484 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
1485 $jDayNo -= self::$IRANIAN_DAYS[$i];
1491 return array( $jy, $jm, $jd );
1505 private static function tsToHijri( $ts ) {
1506 $year = substr( $ts, 0, 4 );
1507 $month = substr( $ts, 4, 2 );
1508 $day = substr( $ts, 6, 2 );
1516 ( $zy > 1582 ) || ( ( $zy == 1582 ) && ( $zm > 10 ) ) ||
1517 ( ( $zy == 1582 ) && ( $zm == 10 ) && ( $zd > 14 ) )
1519 $zjd = (int)( ( 1461 * ( $zy + 4800 + (
int)( ( $zm - 14 ) / 12 ) ) ) / 4 ) +
1520 (
int)( ( 367 * ( $zm - 2 - 12 * ( (int)( ( $zm - 14 ) / 12 ) ) ) ) / 12 ) -
1521 (
int)( ( 3 * (int)( ( ( $zy + 4900 + (
int)( ( $zm - 14 ) / 12 ) ) / 100 ) ) ) / 4 ) +
1524 $zjd = 367 * $zy - (int)( ( 7 * ( $zy + 5001 + (
int)( ( $zm - 9 ) / 7 ) ) ) / 4 ) +
1525 (
int)( ( 275 * $zm ) / 9 ) + $zd + 1729777;
1528 $zl = $zjd -1948440 + 10632;
1529 $zn = (int)( ( $zl - 1 ) / 10631 );
1530 $zl = $zl - 10631 * $zn + 354;
1531 $zj = ( (int)( ( 10985 - $zl ) / 5316 ) ) * ( (int)( ( 50 * $zl ) / 17719 ) ) + ( (int)( $zl / 5670 ) ) * ( (
int)( ( 43 * $zl ) / 15238 ) );
1532 $zl = $zl - ( (int)( ( 30 - $zj ) / 15 ) ) * ( (int)( ( 17719 * $zj ) / 50 ) ) - ( (int)( $zj / 16 ) ) * ( (
int)( ( 15238 * $zj ) / 43 ) ) + 29;
1533 $zm = (int)( ( 24 * $zl ) / 709 );
1534 $zd = $zl - (int)( ( 709 * $zm ) / 24 );
1535 $zy = 30 * $zn + $zj - 30;
1537 return array( $zy, $zm, $zd );
1557 $year = substr( $ts, 0, 4 );
1558 $month = substr( $ts, 4, 2 );
1559 $day = substr( $ts, 6, 2 );
1561 # Calculate Hebrew year
1562 $hebrewYear = $year + 3760;
1564 # Month number when September = 1, August = 12
1566 if ( $month > 12 ) {
1573 # Calculate day of year from 1 September
1575 for ( $i = 1; $i < $month; $i++ ) {
1579 # Check if the year is leap
1580 if ( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1583 } elseif ( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1590 # Calculate the start of the Hebrew year
1593 # Calculate next year's start
1594 if ( $dayOfYear <= $start ) {
1595 # Day is before the start of the year - it is the previous year
1597 $nextStart = $start;
1601 # Add days since previous year's 1 September
1603 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1607 # Start of the new (previous) year
1614 # Calculate Hebrew day of year
1615 $hebrewDayOfYear = $dayOfYear - $start;
1617 # Difference between year's days
1618 $diff = $nextStart - $start;
1619 # Add 12 (or 13 for leap years) days to ignore the difference between
1620 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1621 # difference is only about the year type
1622 if ( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1628 # Check the year pattern, and is leap year
1629 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1630 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1631 # and non-leap years
1632 $yearPattern = $diff % 30;
1633 # Check if leap year
1634 $isLeap = $diff >= 30;
1636 # Calculate day in the month from number of day in the Hebrew year
1637 # Don't check Adar - if the day is not in Adar, we will stop before;
1638 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1639 $hebrewDay = $hebrewDayOfYear;
1642 while ( $hebrewMonth <= 12 ) {
1643 # Calculate days in this month
1644 if ( $isLeap && $hebrewMonth == 6 ) {
1645 # Adar in a leap year
1647 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1649 if ( $hebrewDay <= $days ) {
1653 # Subtract the days of Adar I
1654 $hebrewDay -= $days;
1657 if ( $hebrewDay <= $days ) {
1663 } elseif ( $hebrewMonth == 2 && $yearPattern == 2 ) {
1664 # Cheshvan in a complete year (otherwise as the rule below)
1666 } elseif ( $hebrewMonth == 3 && $yearPattern == 0 ) {
1667 # Kislev in an incomplete year (otherwise as the rule below)
1670 # Odd months have 30 days, even have 29
1671 $days = 30 - ( $hebrewMonth - 1 ) % 2;
1673 if ( $hebrewDay <= $days ) {
1674 # In the current month
1677 # Subtract the days of the current month
1678 $hebrewDay -= $days;
1679 # Try in the next month
1684 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
1697 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1698 $b = intval( ( $year - 1 ) % 4 );
1699 $m = 32.044093161144 + 1.5542417966212 * $a + $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1703 $Mar = intval( $m );
1709 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7 );
1710 if ( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1712 } elseif ( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1714 } elseif ( $c == 2 || $c == 4 || $c == 6 ) {
1718 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1734 private static function tsToYear( $ts, $cName ) {
1735 $gy = substr( $ts, 0, 4 );
1736 $gm = substr( $ts, 4, 2 );
1737 $gd = substr( $ts, 6, 2 );
1739 if ( !strcmp( $cName,
'thai' ) ) {
1741 # Add 543 years to the Gregorian calendar
1742 # Months and days are identical
1743 $gy_offset = $gy + 543;
1744 } elseif ( ( !strcmp( $cName,
'minguo' ) ) || !strcmp( $cName,
'juche' ) ) {
1746 # Deduct 1911 years from the Gregorian calendar
1747 # Months and days are identical
1748 $gy_offset = $gy - 1911;
1749 } elseif ( !strcmp( $cName,
'tenno' ) ) {
1750 # Nengō dates up to Meiji period
1751 # Deduct years from the Gregorian calendar
1752 # depending on the nengo periods
1753 # Months and days are identical
1754 if ( ( $gy < 1912 ) || ( ( $gy == 1912 ) && ( $gm < 7 ) ) || ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd < 31 ) ) ) {
1756 $gy_gannen = $gy - 1868 + 1;
1757 $gy_offset = $gy_gannen;
1758 if ( $gy_gannen == 1 ) {
1761 $gy_offset =
'明治' . $gy_offset;
1763 ( ( $gy == 1912 ) && ( $gm == 7 ) && ( $gd == 31 ) ) ||
1764 ( ( $gy == 1912 ) && ( $gm >= 8 ) ) ||
1765 ( ( $gy > 1912 ) && ( $gy < 1926 ) ) ||
1766 ( ( $gy == 1926 ) && ( $gm < 12 ) ) ||
1767 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd < 26 ) )
1770 $gy_gannen = $gy - 1912 + 1;
1771 $gy_offset = $gy_gannen;
1772 if ( $gy_gannen == 1 ) {
1775 $gy_offset =
'大正' . $gy_offset;
1777 ( ( $gy == 1926 ) && ( $gm == 12 ) && ( $gd >= 26 ) ) ||
1778 ( ( $gy > 1926 ) && ( $gy < 1989 ) ) ||
1779 ( ( $gy == 1989 ) && ( $gm == 1 ) && ( $gd < 8 ) )
1782 $gy_gannen = $gy - 1926 + 1;
1783 $gy_offset = $gy_gannen;
1784 if ( $gy_gannen == 1 ) {
1787 $gy_offset =
'昭和' . $gy_offset;
1790 $gy_gannen = $gy - 1989 + 1;
1791 $gy_offset = $gy_gannen;
1792 if ( $gy_gannen == 1 ) {
1795 $gy_offset =
'平成' . $gy_offset;
1801 return array( $gy_offset, $gm, $gd );
1812 static $table =
array(
1813 array(
'',
'I',
'II',
'III',
'IV',
'V',
'VI',
'VII',
'VIII',
'IX',
'X' ),
1814 array(
'',
'X',
'XX',
'XXX',
'XL',
'L',
'LX',
'LXX',
'LXXX',
'XC',
'C' ),
1815 array(
'',
'C',
'CC',
'CCC',
'CD',
'D',
'DC',
'DCC',
'DCCC',
'CM',
'M' ),
1816 array(
'',
'M',
'MM',
'MMM',
'MMMM',
'MMMMM',
'MMMMMM',
'MMMMMMM',
'MMMMMMMM',
'MMMMMMMMM',
'MMMMMMMMMM' )
1819 $num = intval( $num );
1820 if ( $num > 10000 || $num <= 0 ) {
1825 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1826 if ( $num >= $pow10 ) {
1827 $s .= $table[$i][(int)floor( $num / $pow10 )];
1829 $num = $num % $pow10;
1842 static $table =
array(
1843 array(
'',
'א',
'ב',
'ג',
'ד',
'ה',
'ו',
'ז',
'ח',
'ט',
'י' ),
1844 array(
'',
'י',
'כ',
'ל',
'מ',
'נ',
'ס',
'ע',
'פ',
'צ',
'ק' ),
1845 array(
'',
'ק',
'ר',
'ש',
'ת',
'תק',
'תר',
'תש',
'תת',
'תתק',
'תתר' ),
1846 array(
'',
'א',
'ב',
'ג',
'ד',
'ה',
'ו',
'ז',
'ח',
'ט',
'י' )
1849 $num = intval( $num );
1850 if ( $num > 9999 || $num <= 0 ) {
1855 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1856 if ( $num >= $pow10 ) {
1857 if ( $num == 15 || $num == 16 ) {
1858 $s .= $table[0][9] . $table[0][$num - 9];
1861 $s .= $table[$i][intval( ( $num / $pow10 ) )];
1862 if ( $pow10 == 1000 ) {
1867 $num = $num % $pow10;
1869 if ( strlen(
$s ) == 2 ) {
1872 $str = substr(
$s, 0, strlen(
$s ) - 2 ) .
'"';
1873 $str .= substr(
$s, strlen(
$s ) - 2, 2 );
1875 $start = substr( $str, 0, strlen( $str ) - 2 );
1876 $end = substr( $str, strlen( $str ) - 2 );
1879 $str = $start .
'ך';
1882 $str = $start .
'ם';
1885 $str = $start .
'ן';
1888 $str = $start .
'ף';
1891 $str = $start .
'ץ';
1908 if ( $tz ===
false ) {
1909 $tz =
$wgUser->getOption(
'timecorrection' );
1912 $data = explode(
'|', $tz, 3 );
1914 if ( $data[0] ==
'ZoneInfo' ) {
1916 $userTZ = timezone_open( $data[2] );
1918 if ( $userTZ !==
false ) {
1919 $date = date_create( $ts, timezone_open(
'UTC' ) );
1920 date_timezone_set( $date, $userTZ );
1921 $date = date_format( $date,
'YmdHis' );
1924 # Unrecognized timezone, default to 'Offset' with the stored offset.
1925 $data[0] =
'Offset';
1929 if ( $data[0] ==
'System' || $tz ==
'' ) {
1930 # Global offset in minutes.
1931 if ( isset( $wgLocalTZoffset ) ) {
1932 $minDiff = $wgLocalTZoffset;
1934 } elseif ( $data[0] ==
'Offset' ) {
1935 $minDiff = intval( $data[1] );
1937 $data = explode(
':', $tz );
1938 if ( count( $data ) == 2 ) {
1939 $data[0] = intval( $data[0] );
1940 $data[1] = intval( $data[1] );
1941 $minDiff = abs( $data[0] ) * 60 + $data[1];
1942 if ( $data[0] < 0 ) {
1943 $minDiff = -$minDiff;
1946 $minDiff = intval( $data[0] ) * 60;
1950 # No difference ? Return time unchanged
1951 if ( 0 == $minDiff ) {
1956 # Generate an adjusted date; take advantage of the fact that mktime
1957 # will normalize out-of-range values so we don't have to split $minDiff
1958 # into hours and minutes.
1960 (
int)substr( $ts, 8, 2 ) ), # Hours
1961 (
int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
1962 (
int)substr( $ts, 12, 2 ), # Seconds
1963 (
int)substr( $ts, 4, 2 ), # Month
1964 (
int)substr( $ts, 6, 2 ), # Day
1965 (
int)substr( $ts, 0, 4 ) ); # Year
1967 $date =
date(
'YmdHis',
$t );
1993 if ( is_bool( $usePrefs ) ) {
1995 $datePreference =
$wgUser->getDatePreference();
2000 $datePreference = (string)$usePrefs;
2004 if ( $datePreference ==
'' ) {
2008 return $datePreference;
2021 if ( !isset( $this->dateFormatStrings[
$type][$pref] ) ) {
2022 if ( $pref ==
'default' ) {
2024 $df = self::$dataCache->getSubitem( $this->mCode,
'dateFormats',
"$pref $type" );
2026 $df = self::$dataCache->getSubitem( $this->mCode,
'dateFormats',
"$pref $type" );
2028 if (
$type ===
'pretty' && $df ===
null ) {
2032 if ( $df ===
null ) {
2034 $df = self::$dataCache->getSubitem( $this->mCode,
'dateFormats',
"$pref $type" );
2037 $this->dateFormatStrings[
$type][$pref] = $df;
2039 return $this->dateFormatStrings[
$type][$pref];
2052 function date( $ts, $adj =
false, $format =
true, $timecorrection =
false ) {
2055 $ts = $this->
userAdjust( $ts, $timecorrection );
2071 function time( $ts, $adj =
false, $format =
true, $timecorrection =
false ) {
2074 $ts = $this->
userAdjust( $ts, $timecorrection );
2091 function timeanddate( $ts, $adj =
false, $format =
true, $timecorrection =
false ) {
2094 $ts = $this->
userAdjust( $ts, $timecorrection );
2113 $segments =
array();
2115 foreach ( $intervals
as $intervalName => $intervalValue ) {
2118 $message =
wfMessage(
'duration-' . $intervalName )->numParams( $intervalValue );
2119 $segments[] = $message->inLanguage( $this )->escaped();
2137 if ( empty( $chosenIntervals ) ) {
2138 $chosenIntervals =
array(
'millennia',
'centuries',
'decades',
'years',
'days',
'hours',
'minutes',
'seconds' );
2141 $intervals = array_intersect_key( self::$durationIntervals, array_flip( $chosenIntervals ) );
2142 $sortedNames = array_keys( $intervals );
2143 $smallestInterval = array_pop( $sortedNames );
2145 $segments =
array();
2147 foreach ( $intervals
as $name => $length ) {
2148 $value = floor( $seconds / $length );
2150 if (
$value > 0 || (
$name == $smallestInterval && empty( $segments ) ) ) {
2151 $seconds -=
$value * $length;
2180 $options +=
array(
'timecorrection' =>
true,
'format' =>
true );
2181 if (
$options[
'timecorrection'] !==
false ) {
2182 if (
$options[
'timecorrection'] ===
true ) {
2183 $offset =
$user->getOption(
'timecorrection' );
2185 $offset =
$options[
'timecorrection'];
2189 if (
$options[
'format'] ===
true ) {
2190 $format =
$user->getDatePreference();
2283 $diff = $ts->
diff( $relativeTo );
2284 $diffDay = (bool)( (
int)$ts->timestamp->
format(
'w' ) - (int)$relativeTo->timestamp->
format(
'w' ) );
2285 $days = $diff->days ?: (int)$diffDay;
2286 if ( $diff->invert || $days > 5 && $ts->timestamp->
format(
'Y' ) !== $relativeTo->timestamp->
format(
'Y' ) ) {
2294 } elseif ( $days > 5 ) {
2298 } elseif ( $days > 1 ) {
2301 $weekday = self::$mWeekdayMsgs[$ts->timestamp->format(
'w' )];
2305 ->inLanguage( $this )
2308 } elseif ( $days == 1 ) {
2312 ->inLanguage( $this )
2315 } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
2319 ->inLanguage( $this )
2325 } elseif ( $diff->h == 1 ) {
2327 $ts =
wfMessage(
'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
2328 } elseif ( $diff->i >= 1 ) {
2330 $ts =
wfMessage(
'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
2331 } elseif ( $diff->s >= 30 ) {
2333 $ts =
wfMessage(
'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
2347 return self::$dataCache->getSubitem( $this->mCode,
'messages', $key );
2354 return self::$dataCache->getItem( $this->mCode,
'messages' );
2364 # This is a wrapper for iconv in all languages except esperanto,
2365 # which does some nasty x-conversions beforehand
2367 # Even with //IGNORE iconv can whine about illegal characters in
2368 # *input* string. We just ignore those too.
2369 # REF: http://bugs.php.net/bug.php?id=37166
2370 # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885
2392 return mb_strtoupper(
$matches[0] );
2401 return strtr(
$matches[1], $wikiUpperChars );
2410 return strtr(
$matches[1], $wikiLowerChars );
2418 return mb_strtoupper(
$matches[0] );
2427 return strtr(
$matches[0], $wikiUpperChars );
2441 } elseif ( $o < 128 ) {
2445 return $this->
uc( $str,
true );
2457 function uc( $str, $first =
false ) {
2458 if ( function_exists(
'mb_strtoupper' ) ) {
2461 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2466 return $this->
isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
2470 $x = $first ?
'^' :
'';
2471 return preg_replace_callback(
2472 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
2473 array( $this,
'ucCallback' ),
2477 return $first ?
ucfirst( $str ) : strtoupper( $str );
2489 return strval( $str );
2490 } elseif ( $o >= 128 ) {
2491 return $this->
lc( $str,
true );
2492 } elseif ( $o > 96 ) {
2495 $str[0] = strtolower( $str[0] );
2505 function lc( $str, $first =
false ) {
2506 if ( function_exists(
'mb_strtolower' ) ) {
2509 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
2511 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
2514 return $this->
isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
2518 $x = $first ?
'^' :
'';
2519 return preg_replace_callback(
2520 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
2521 array( $this,
'lcCallback' ),
2525 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
2535 return (
bool)preg_match(
'/[\x80-\xff]/', $str );
2544 $str = $this->
lc( $str );
2547 $replaceRegexp =
"/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2550 if ( function_exists(
'mb_strtoupper' ) ) {
2551 return preg_replace_callback(
2553 array( $this,
'ucwordsCallbackMB' ),
2557 return preg_replace_callback(
2559 array( $this,
'ucwordsCallbackWiki' ),
2564 return ucwords( strtolower( $str ) );
2576 $str = $this->
lc( $str );
2579 $breaks =
"[ \-\(\)\}\{\.,\?!]";
2582 $replaceRegexp =
"/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
2584 if ( function_exists(
'mb_strtoupper' ) ) {
2585 return preg_replace_callback(
2587 array( $this,
'ucwordbreaksCallbackMB' ),
2591 return preg_replace_callback(
2593 array( $this,
'ucwordsCallbackWiki' ),
2598 return preg_replace_callback(
2599 '/\b([\w\x80-\xff]+)\b/',
2600 array( $this,
'ucwordbreaksCallbackAscii' ),
2622 return $this->
uc( $s );
2630 if ( is_array(
$s ) ) {
2631 throw new MWException(
'Given array to checkTitleEncoding.' );
2644 return self::$dataCache->getItem( $this->mCode,
'fallback8bitEncoding' );
2690 static $full =
null;
2691 static $half =
null;
2693 if ( $full ===
null ) {
2694 $fullWidth =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2695 $halfWidth =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2696 $full = str_split( $fullWidth, 3 );
2697 $half = str_split( $halfWidth );
2700 $string = str_replace( $full, $half, $string );
2709 protected static function insertSpace( $string, $pattern ) {
2710 $string = preg_replace( $pattern,
" $1 ", $string );
2711 $string = preg_replace(
'/ +/',
' ', $string );
2720 # some languages, e.g. Chinese, need to do a conversion
2721 # in order for search results to be displayed correctly
2734 '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
2735 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/',
2741 if ( strlen(
$matches[1] ) != 3 ) {
2747 if ( $code < 0xac00 || 0xd7a4 <= $code ) {
2749 } elseif ( $code < 0xb098 ) {
2750 return "\xe3\x84\xb1";
2751 } elseif ( $code < 0xb2e4 ) {
2752 return "\xe3\x84\xb4";
2753 } elseif ( $code < 0xb77c ) {
2754 return "\xe3\x84\xb7";
2755 } elseif ( $code < 0xb9c8 ) {
2756 return "\xe3\x84\xb9";
2757 } elseif ( $code < 0xbc14 ) {
2758 return "\xe3\x85\x81";
2759 } elseif ( $code < 0xc0ac ) {
2760 return "\xe3\x85\x82";
2761 } elseif ( $code < 0xc544 ) {
2762 return "\xe3\x85\x85";
2763 } elseif ( $code < 0xc790 ) {
2764 return "\xe3\x85\x87";
2765 } elseif ( $code < 0xcc28 ) {
2766 return "\xe3\x85\x88";
2767 } elseif ( $code < 0xce74 ) {
2768 return "\xe3\x85\x8a";
2769 } elseif ( $code < 0xd0c0 ) {
2770 return "\xe3\x85\x8b";
2771 } elseif ( $code < 0xd30c ) {
2772 return "\xe3\x85\x8c";
2773 } elseif ( $code < 0xd558 ) {
2774 return "\xe3\x85\x8d";
2776 return "\xe3\x85\x8e";
2784 # Some languages may have an alternate char encoding option
2785 # (Esperanto X-coding, Japanese furigana conversion, etc)
2786 # If this language is used as the primary content language,
2787 # an override to the defaults can be set here on startup.
2795 # For some languages we'll want to explicitly specify
2796 # which characters make it into the edit box raw
2797 # or are converted in some way or another.
2799 if ( $wgEditEncoding ==
'' || $wgEditEncoding ==
'UTF-8' ) {
2802 return $this->
iconv(
'UTF-8', $wgEditEncoding,
$s );
2811 # Take the previous into account.
2813 if ( $wgEditEncoding !=
'' ) {
2814 $enc = $wgEditEncoding;
2818 if ( $enc ==
'UTF-8' ) {
2821 return $this->
iconv( $enc,
'UTF-8',
$s );
2837 global $wgAllUnicodeFixes;
2839 if ( $wgAllUnicodeFixes ) {
2862 if ( !isset( $this->transformData[
$file] ) ) {
2864 if ( $data ===
false ) {
2865 throw new MWException( __METHOD__ .
": The transformation file $file is missing" );
2869 return $this->transformData[
$file]->replace( $string );
2878 return self::$dataCache->getItem( $this->mCode,
'rtl' );
2886 return $this->
isRTL() ?
'rtl' :
'ltr';
2898 return $this->
isRTL() ?
'right' :
'left';
2910 return $this->
isRTL() ?
'left' :
'right';
2926 return $this->
isRTL() ?
'‎' :
'‏';
2928 return $this->
isRTL() ?
'‏' :
'‎';
2942 $lrm =
"\xE2\x80\x8E"; #
LEFT-TO-
RIGHT MARK, commonly abbreviated LRM
2943 $rlm =
"\xE2\x80\x8F"; #
RIGHT-TO-
LEFT MARK, commonly abbreviated RLM
2945 return $this->
isRTL() ? $lrm : $rlm;
2947 return $this->
isRTL() ? $rlm : $lrm;
2954 return self::$dataCache->getItem( $this->mCode,
'capitalizeAllNouns' );
2963 function getArrow( $direction =
'forwards' ) {
2964 switch ( $direction ) {
2966 return $this->
isRTL() ?
'←' :
'→';
2968 return $this->
isRTL() ?
'→' :
'←';
2986 return self::$dataCache->getItem( $this->mCode,
'linkPrefixExtension' );
2994 return self::$dataCache->getItem( $this->mCode,
'magicWords' );
3001 if ( $this->mMagicHookDone ) {
3004 $this->mMagicHookDone =
true;
3017 if ( ! $this->mMagicHookDone ) {
3021 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
3022 $rawEntry = $this->mMagicExtensions[$mw->mId];
3024 $rawEntry = self::$dataCache->getSubitem(
3025 $this->mCode,
'magicWords', $mw->mId );
3028 if ( !is_array( $rawEntry ) ) {
3029 error_log(
"\"$rawEntry\" is not a valid magic word for \"$mw->mId\"" );
3031 $mw->mCaseSensitive = $rawEntry[0];
3032 $mw->mSynonyms = array_slice( $rawEntry, 1 );
3043 $fallbackChain = array_reverse( $fallbackChain );
3044 foreach ( $fallbackChain
as $code ) {
3045 if ( isset( $newWords[$code] ) ) {
3057 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
3059 $this->mExtendedSpecialPageAliases =
3060 self::$dataCache->getItem( $this->mCode,
'specialPageAliases' );
3062 array( &$this->mExtendedSpecialPageAliases, $this->
getCode() ) );
3075 return "<em>$text</em>";
3101 public function formatNum( $number, $nocommafy =
false ) {
3102 global $wgTranslateNumerals;
3103 if ( !$nocommafy ) {
3104 $number = $this->
commafy( $number );
3107 $number = strtr( $number,
$s );
3111 if ( $wgTranslateNumerals ) {
3114 $number = strtr( $number,
$s );
3130 return $this->
formatNum( $number,
true );
3140 $number = strtr( $number, array_flip(
$s ) );
3145 $number = strtr( $number, array_flip(
$s ) );
3148 $number = strtr( $number,
array(
',' =>
'' ) );
3160 if ( $number ===
null ) {
3166 return strrev( (
string)preg_replace(
'/(\d{3})(?=\d)(?!\d*\.)/',
'$1,', strrev( $number ) ) );
3170 if ( intval( $number ) < 0 ) {
3173 $number = substr( $number, 1 );
3175 $integerPart =
array();
3176 $decimalPart =
array();
3178 preg_match(
"/\d+/", $number, $integerPart );
3179 preg_match(
"/\.\d*/", $number, $decimalPart );
3180 $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] :
"";
3181 if ( $groupedNumber === $number ) {
3183 return $sign . $groupedNumber;
3185 $start = $end = strlen( $integerPart[0] );
3186 while ( $start > 0 ) {
3187 $match =
$matches[0][$numMatches - 1];
3188 $matchLen = strlen( $match );
3189 $start = $end - $matchLen;
3193 $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
3195 if ( $numMatches > 1 ) {
3200 $groupedNumber =
"," . $groupedNumber;
3203 return $sign . $groupedNumber;
3211 return self::$dataCache->getItem( $this->mCode,
'digitGroupingPattern' );
3218 return self::$dataCache->getItem( $this->mCode,
'digitTransformTable' );
3225 return self::$dataCache->getItem( $this->mCode,
'separatorTransformTable' );
3238 $m = count( $l ) - 1;
3250 for ( $i = $m - 1; $i >= 0; $i-- ) {
3251 if ( $i == $m - 1 ) {
3252 $s = $l[$i] . $and . $space .
$s;
3254 $s = $l[$i] . $comma .
$s;
3268 wfMessage(
'comma-separator' )->inLanguage( $this )->escaped(),
3281 wfMessage(
'semicolon-separator' )->inLanguage( $this )->escaped(),
3293 wfMessage(
'pipe-separator' )->inLanguage( $this )->escaped(),
3315 function truncate( $string, $length, $ellipsis =
'...', $adjustLength =
true ) {
3316 # Use the localized ellipsis character
3317 if ( $ellipsis ==
'...' ) {
3318 $ellipsis =
wfMessage(
'ellipsis' )->inLanguage( $this )->escaped();
3320 # Check if there is no need to truncate
3321 if ( $length == 0 ) {
3323 } elseif ( strlen( $string ) <= abs( $length ) ) {
3326 $stringOriginal = $string;
3327 # If ellipsis length is >= $length then we can't apply $adjustLength
3328 if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) {
3329 $string = $ellipsis;
3330 # Otherwise, truncate and add ellipsis...
3332 $eLength = $adjustLength ? strlen( $ellipsis ) : 0;
3333 if ( $length > 0 ) {
3334 $length -= $eLength;
3335 $string = substr( $string, 0, $length );
3337 $string = rtrim( $string );
3338 $string = $string . $ellipsis;
3340 $length += $eLength;
3341 $string = substr( $string, $length );
3343 $string = ltrim( $string );
3344 $string = $ellipsis . $string;
3347 # Do not truncate if the ellipsis makes the string longer/equal (bug 22181).
3348 # This check is *not* redundant if $adjustLength, due to the single case where
3349 # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string.
3350 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
3353 return $stringOriginal;
3365 if ( $string !=
'' ) {
3366 $char = ord( $string[strlen( $string ) - 1] );
3368 if ( $char >= 0xc0 ) {
3369 # We got the first byte only of a multibyte char; remove it.
3370 $string = substr( $string, 0, -1 );
3371 } elseif ( $char >= 0x80 &&
3372 preg_match(
'/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
3373 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m )
3375 # We chopped in the middle of a character; remove it
3390 if ( $string !=
'' ) {
3391 $char = ord( $string[0] );
3392 if ( $char >= 0x80 && $char < 0xc0 ) {
3393 # We chopped in the middle of a character; remove the whole thing
3394 $string = preg_replace(
'/^[\x80-\xbf]+/',
'', $string );
3415 function truncateHtml( $text, $length, $ellipsis =
'...' ) {
3416 # Use the localized ellipsis character
3417 if ( $ellipsis ==
'...' ) {
3418 $ellipsis =
wfMessage(
'ellipsis' )->inLanguage( $this )->escaped();
3420 # Check if there is clearly no need to truncate
3421 if ( $length <= 0 ) {
3423 } elseif ( strlen( $text ) <= $length ) {
3428 $testingEllipsis =
false;
3433 $openTags =
array();
3436 $textLen = strlen( $text );
3437 $neLength = max( 0, $length - strlen( $ellipsis ) );
3438 for ( $pos = 0;
true; ++$pos ) {
3439 # Consider truncation once the display length has reached the maximim.
3440 # We check if $dispLen > 0 to grab tags for the $neLength = 0 case.
3441 # Check that we're not in the middle of a bracket/entity...
3442 if ( $dispLen && $dispLen >= $neLength && $bracketState == 0 && !$entityState ) {
3443 if ( !$testingEllipsis ) {
3444 $testingEllipsis =
true;
3445 # Save where we are; we will truncate here unless there turn out to
3446 # be so few remaining characters that truncation is not necessary.
3447 if ( !$maybeState ) {
3450 } elseif ( $dispLen > $length && $dispLen > strlen( $ellipsis ) ) {
3451 # String in fact does need truncation, the truncation point was OK.
3452 list(
$ret, $openTags ) = $maybeState;
3458 if ( $pos >= $textLen ) {
3462 # Read the next char...
3464 $lastCh = $pos ? $text[$pos - 1] :
'';
3470 } elseif ( $ch ==
'>' ) {
3474 } elseif ( $bracketState == 1 ) {
3482 } elseif ( $bracketState == 2 ) {
3489 } elseif ( $bracketState == 0 ) {
3490 if ( $entityState ) {
3496 if ( $neLength == 0 && !$maybeState ) {
3499 $maybeState =
array( substr(
$ret, 0, -1 ), $openTags );
3506 $max = ( $testingEllipsis ? $length : $neLength ) - $dispLen;
3508 $dispLen += $skipped;
3516 while ( count( $openTags ) > 0 ) {
3517 $ret .=
'</' . array_pop( $openTags ) .
'>';
3534 if ( $len ===
null ) {
3536 } elseif ( $len < 0 ) {
3540 if ( $start < strlen( $text ) ) {
3541 $skipCount = strcspn( $text, $search, $start, $len );
3542 $ret .= substr( $text, $start, $skipCount );
3557 $tag = ltrim( $tag );
3559 if ( $tagType == 0 && $lastCh !=
'/' ) {
3561 } elseif ( $tagType == 1 ) {
3562 if ( $openTags && $tag == $openTags[count( $openTags ) - 1] ) {
3563 array_pop( $openTags );
3580 if ( isset( $wgGrammarForms[$this->
getCode()][$case][$word] ) ) {
3581 return $wgGrammarForms[$this->
getCode()][$case][$word];
3592 if ( isset( $wgGrammarForms[$this->
getCode()] ) && is_array( $wgGrammarForms[$this->
getCode()] ) ) {
3593 return $wgGrammarForms[$this->
getCode()];
3616 function gender( $gender, $forms ) {
3617 if ( !count( $forms ) ) {
3621 if ( $gender ===
'male' ) {
3624 if ( $gender ===
'female' ) {
3627 return isset( $forms[2] ) ? $forms[2] : $forms[0];
3648 if ( is_string( $forms ) ) {
3651 if ( !count( $forms ) ) {
3656 $pluralForm = min( $pluralForm, count( $forms ) - 1 );
3657 return $forms[$pluralForm];
3676 foreach ( $forms
as $index =>
$form ) {
3677 if ( preg_match(
'/\d+=/i',
$form ) ) {
3678 $pos = strpos(
$form,
'=' );
3679 if ( substr(
$form, 0, $pos ) === (
string)
$count ) {
3680 return substr(
$form, $pos + 1 );
3682 unset( $forms[$index] );
3685 return array_values( $forms );
3697 while ( count( $forms ) <
$count ) {
3698 $forms[] = $forms[count( $forms ) - 1];
3716 foreach ( $duration
as $show =>
$value ) {
3717 if ( strcmp( $str,
$value ) == 0 ) {
3718 return htmlspecialchars( trim( $show ) );
3724 $indefs =
array(
'infinite',
'infinity',
'indefinite' );
3725 if ( in_array( $str, $indefs ) ) {
3726 foreach ( $indefs
as $val ) {
3727 $show = array_search( $val, $duration,
true );
3728 if ( $show !==
false ) {
3729 return htmlspecialchars( trim( $show ) );
3735 $time = strtotime( $str, 0 );
3736 if (
$time ===
false ) {
3738 } elseif (
$time !== strtotime( $str, 1 ) ) {
3742 if (
$time === 0 ) {
3789 return $this->mConverter->autoConvertToAllVariants( $text );
3798 public function convert( $text ) {
3799 return $this->mConverter->convert( $text );
3809 return $this->mConverter->convertTitle(
$title );
3819 return $this->mConverter->convertNamespace( $ns );
3839 return (
bool)$this->mConverter->validateVariant( $variant );
3850 return $this->mConverter->armourMath( $text );
3860 public function convertHtml( $text, $isTitle =
false ) {
3861 return htmlspecialchars( $this->
convert( $text, $isTitle ) );
3869 return $this->mConverter->convertCategoryKey( $key );
3879 return $this->mConverter->getVariants();
3886 return $this->mConverter->getPreferredVariant();
3893 return $this->mConverter->getDefaultVariant();
3900 return $this->mConverter->getURLVariant();
3916 $this->mConverter->findVariantLink(
$link, $nt, $ignoreOtherCond );
3926 return $this->mConverter->getExtraHashOptions();
3937 return $this->mConverter->getParsedTitle();
3955 return $this->mConverter->markNoConversion( $text );
3968 return self::$dataCache->getItem( $this->mCode,
'linkTrail' );
3978 return self::$dataCache->getItem( $this->mCode,
'linkPrefixCharset' );
3996 if ( $this->mParentLanguage !==
false ) {
4000 $pieces = explode(
'-', $this->
getCode() );
4002 if ( !in_array( $code, LanguageConverter::$languagesWithVariants ) ) {
4003 $this->mParentLanguage =
null;
4007 if ( !$lang->hasVariant( $this->getCode() ) ) {
4008 $this->mParentLanguage =
null;
4012 $this->mParentLanguage = $lang;
4039 if ( is_null( $this->mHtmlCode ) ) {
4048 public function setCode( $code ) {
4049 $this->mCode = $code;
4051 $this->mHtmlCode =
null;
4052 $this->mParentLanguage =
false;
4063 public static function getFileName( $prefix =
'Language', $code, $suffix =
'.php' ) {
4064 if ( !self::isValidBuiltInCode( $code ) ) {
4065 throw new MWException(
"Invalid language code \"$code\"" );
4068 return $prefix . str_replace(
'-',
'_',
ucfirst( $code ) ) . $suffix;
4078 public static function getCodeFromFileName( $filename, $prefix =
'Language', $suffix =
'.php' ) {
4080 preg_match(
'/' . preg_quote( $prefix,
'/' ) .
'([A-Z][a-z_]+)' .
4081 preg_quote( $suffix,
'/' ) .
'/', $filename, $m );
4082 if ( !count( $m ) ) {
4085 return str_replace(
'_',
'-', strtolower( $m[1] ) );
4107 if ( !self::isValidBuiltInCode( $code ) ) {
4108 throw new MWException(
"Invalid language code \"$code\"" );
4111 return "$IP/languages/i18n/$code.json" ;
4135 $first = array_shift( $fallbacks );
4152 $v = array_map(
'trim', explode(
',', $v ) );
4153 if ( $v[count( $v ) - 1] !==
'en' ) {
4173 $cacheKey =
"{$code}-{$wgLanguageCode}";
4175 if ( !array_key_exists( $cacheKey, self::$fallbackLanguageCache ) ) {
4180 array_unshift( $siteFallbacks, $wgLanguageCode );
4183 $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
4185 self::$fallbackLanguageCache[$cacheKey] =
array( $fallbacks, $siteFallbacks );
4187 return self::$fallbackLanguageCache[$cacheKey];
4232 if ( strpos( $talk,
'$1' ) ===
false ) {
4237 $talk = str_replace(
'$1', $wgMetaNamespace, $talk );
4239 # Allow grammar transformations
4240 # Allowing full message-style parsing would make simple requests
4241 # such as action=raw much more expensive than they need to be.
4242 # This will hopefully cover most cases.
4243 $talk = preg_replace_callback(
'/{{grammar:(.*?)\|(.*?)}}/i',
4244 array( &$this,
'replaceGrammarInNamespace' ), $talk );
4245 return str_replace(
' ',
'_', $talk );
4261 static $wikiUpperChars, $wikiLowerChars;
4262 if ( isset( $wikiUpperChars ) ) {
4263 return array( $wikiUpperChars, $wikiLowerChars );
4268 if ( $arr ===
false ) {
4270 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
4272 $wikiUpperChars = $arr[
'wikiUpperChars'];
4273 $wikiLowerChars = $arr[
'wikiLowerChars'];
4275 return array( $wikiUpperChars, $wikiLowerChars );
4289 public function formatExpiry( $expiry, $format =
true ) {
4291 if ( $infinity ===
null ) {
4295 if ( $expiry ==
'' || $expiry == $infinity ) {
4296 return $format ===
true
4300 return $format ===
true
4317 if ( !is_array( $format ) ) {
4318 $format =
array(
'avoid' => $format );
4320 if ( !isset( $format[
'avoid'] ) ) {
4321 $format[
'avoid'] =
false;
4323 if ( !isset( $format[
'noabbrevs' ] ) ) {
4324 $format[
'noabbrevs'] =
false;
4327 $format[
'noabbrevs'] ?
'seconds' :
'seconds-abbrev' )->inLanguage( $this );
4329 $format[
'noabbrevs'] ?
'minutes' :
'minutes-abbrev' )->inLanguage( $this );
4331 $format[
'noabbrevs'] ?
'hours' :
'hours-abbrev' )->inLanguage( $this );
4333 $format[
'noabbrevs'] ?
'days' :
'days-abbrev' )->inLanguage( $this );
4335 if ( round( $seconds * 10 ) < 100 ) {
4336 $s = $this->
formatNum( sprintf(
"%.1f", round( $seconds * 10 ) / 10 ) );
4337 $s = $secondsMsg->params(
$s )->text();
4338 } elseif ( round( $seconds ) < 60 ) {
4340 $s = $secondsMsg->params(
$s )->text();
4341 } elseif ( round( $seconds ) < 3600 ) {
4342 $minutes = floor( $seconds / 60 );
4343 $secondsPart = round( fmod( $seconds, 60 ) );
4344 if ( $secondsPart == 60 ) {
4348 $s = $minutesMsg->params( $this->
formatNum( $minutes ) )->text();
4350 $s .= $secondsMsg->params( $this->
formatNum( $secondsPart ) )->text();
4351 } elseif ( round( $seconds ) <= 2 * 86400 ) {
4352 $hours = floor( $seconds / 3600 );
4353 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
4354 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
4355 if ( $secondsPart == 60 ) {
4359 if ( $minutes == 60 ) {
4363 $s = $hoursMsg->params( $this->
formatNum( $hours ) )->text();
4365 $s .= $minutesMsg->params( $this->
formatNum( $minutes ) )->text();
4366 if ( !in_array( $format[
'avoid'],
array(
'avoidseconds',
'avoidminutes' ) ) ) {
4367 $s .=
' ' . $secondsMsg->params( $this->
formatNum( $secondsPart ) )->text();
4370 $days = floor( $seconds / 86400 );
4371 if ( $format[
'avoid'] ===
'avoidminutes' ) {
4372 $hours = round( ( $seconds - $days * 86400 ) / 3600 );
4373 if ( $hours == 24 ) {
4377 $s = $daysMsg->params( $this->
formatNum( $days ) )->text();
4379 $s .= $hoursMsg->params( $this->
formatNum( $hours ) )->text();
4380 } elseif ( $format[
'avoid'] ===
'avoidseconds' ) {
4381 $hours = floor( ( $seconds - $days * 86400 ) / 3600 );
4382 $minutes = round( ( $seconds - $days * 86400 - $hours * 3600 ) / 60 );
4383 if ( $minutes == 60 ) {
4387 if ( $hours == 24 ) {
4391 $s = $daysMsg->params( $this->
formatNum( $days ) )->text();
4393 $s .= $hoursMsg->params( $this->
formatNum( $hours ) )->text();
4395 $s .= $minutesMsg->params( $this->
formatNum( $minutes ) )->text();
4397 $s = $daysMsg->params( $this->
formatNum( $days ) )->text();
4431 $sizes =
array(
'',
'kilo',
'mega',
'giga',
'tera',
'peta',
'exa',
'zeta',
'yotta' );
4434 $maxIndex = count( $sizes ) - 1;
4435 while (
$size >= $boundary && $index < $maxIndex ) {
4446 $msg = str_replace(
'$1', $sizes[$index], $messageKey );
4476 function specialList( $page, $details, $oppositedm =
true ) {
4477 $dirmark = ( $oppositedm ? $this->
getDirMark(
true ) :
'' ) .
4479 $details = $details ? $dirmark . $this->
getMessageFromDB(
'word-separator' ) .
4480 wfMessage(
'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() :
'';
4481 return $page . $details;
4497 # Make 'previous' link
4499 if ( $offset > 0 ) {
4501 $query, $prev,
'prevn-title',
'mw-prevlink' );
4503 $plink = htmlspecialchars( $prev );
4509 $nlink = htmlspecialchars( $next );
4512 $query, $next,
'nextn-title',
'mw-nextlink' );
4515 # Make links to set number of items per page
4516 $numLinks =
array();
4517 foreach (
array( 20, 50, 100, 250, 500 )
as $num ) {
4518 $numLinks[] = $this->
numLink( $title, $offset, $num,
4522 return wfMessage(
'viewprevnext' )->inLanguage( $this )->title(
$title
4523 )->rawParams( $plink, $nlink, $this->
pipeList( $numLinks ) )->escaped();
4540 $tooltip =
wfMessage( $tooltipMsg )->inLanguage( $this )->title(
$title )->numParams(
$limit )->text();
4542 'title' => $tooltip,
'class' => $class ),
$link );
4551 return $this->mConverter->getConvRuleTitle();
4560 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ),
'compiledPluralRules' );
4562 if ( !$pluralRules ) {
4563 foreach ( $fallbacks
as $fallbackCode ) {
4564 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ),
'compiledPluralRules' );
4565 if ( $pluralRules ) {
4570 return $pluralRules;
4579 $pluralRules = self::$dataCache->getItem( strtolower( $this->mCode ),
'pluralRules' );
4581 if ( !$pluralRules ) {
4582 foreach ( $fallbacks
as $fallbackCode ) {
4583 $pluralRules = self::$dataCache->getItem( strtolower( $fallbackCode ),
'pluralRules' );
4584 if ( $pluralRules ) {
4589 return $pluralRules;
4598 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $this->mCode ),
'pluralRuleTypes' );
4600 if ( !$pluralRuleTypes ) {
4601 foreach ( $fallbacks
as $fallbackCode ) {
4602 $pluralRuleTypes = self::$dataCache->getItem( strtolower( $fallbackCode ),
'pluralRuleTypes' );
4603 if ( $pluralRuleTypes ) {
4608 return $pluralRuleTypes;
4631 if ( isset( $pluralRuleTypes[$index] ) ) {
4632 return $pluralRuleTypes[$index];