3 * @defgroup Language Language
9 if( !defined( 'MEDIAWIKI' ) ) {
10 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
15 global $wgLanguageNames;
16 require_once( dirname(__FILE__
) . '/Names.php' ) ;
18 global $wgInputEncoding, $wgOutputEncoding;
21 * These are always UTF-8, they exist only for backwards compatibility
23 $wgInputEncoding = "UTF-8";
24 $wgOutputEncoding = "UTF-8";
26 if( function_exists( 'mb_strtoupper' ) ) {
27 mb_internal_encoding('UTF-8');
31 * a fake language converter
37 function FakeConverter($langobj) {$this->mLang
= $langobj;}
38 function convert($t, $i) {return $t;}
39 function parserConvert($t, $p) {return $t;}
40 function getVariants() { return array( $this->mLang
->getCode() ); }
41 function getPreferredVariant() {return $this->mLang
->getCode(); }
42 function findVariantLink(&$l, &$n, $forTemplate = false) {}
43 function getExtraHashOptions() {return '';}
44 function getParsedTitle() {return '';}
45 function markNoConversion($text, $noParse=false) {return $text;}
46 function convertCategoryKey( $key ) {return $key; }
47 function convertLinkToAllVariants($text){ return array( $this->mLang
->getCode() => $text); }
48 function armourMath($text){ return $text; }
52 * Internationalisation code
56 var $mConverter, $mVariants, $mCode, $mLoaded = false;
57 var $mMagicExtensions = array(), $mMagicHookDone = false;
59 static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
60 'skinNames', 'mathNames',
61 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable',
62 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
63 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
64 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
65 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
69 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
70 'dateFormats', 'defaultUserOptionOverrides', 'magicWords', 'imageFiles' );
72 static public $mMergeableListKeys = array( 'extraUserToggles' );
74 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
76 static public $mLocalisationCache = array();
77 static public $mLangObjCache = array();
79 static public $mWeekdayMsgs = array(
80 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
84 static public $mWeekdayAbbrevMsgs = array(
85 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
88 static public $mMonthMsgs = array(
89 'january', 'february', 'march', 'april', 'may_long', 'june',
90 'july', 'august', 'september', 'october', 'november',
93 static public $mMonthGenMsgs = array(
94 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
95 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
98 static public $mMonthAbbrevMsgs = array(
99 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
100 'sep', 'oct', 'nov', 'dec'
103 static public $mIranianCalendarMonthMsgs = array(
104 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
105 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
106 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
107 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
110 static public $mHebrewCalendarMonthMsgs = array(
111 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
112 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
113 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
114 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
115 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
118 static public $mHebrewCalendarMonthGenMsgs = array(
119 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
120 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
121 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
122 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
123 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
126 static public $mHijriCalendarMonthMsgs = array(
127 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
128 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
129 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
130 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
134 * Get a cached language object for a given language code
136 static function factory( $code ) {
137 if ( !isset( self
::$mLangObjCache[$code] ) ) {
138 if( count( self
::$mLangObjCache ) > 10 ) {
139 // Don't keep a billion objects around, that's stupid.
140 self
::$mLangObjCache = array();
142 self
::$mLangObjCache[$code] = self
::newFromCode( $code );
144 return self
::$mLangObjCache[$code];
148 * Create a language object for a given language code
150 protected static function newFromCode( $code ) {
152 static $recursionLevel = 0;
153 if ( $code == 'en' ) {
156 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
157 // Preload base classes to work around APC/PHP5 bug
158 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
159 include_once("$IP/languages/classes/$class.deps.php");
161 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
162 include_once("$IP/languages/classes/$class.php");
166 if ( $recursionLevel > 5 ) {
167 throw new MWException( "Language fallback loop detected when creating class $class\n" );
170 if( ! class_exists( $class ) ) {
171 $fallback = Language
::getFallbackFor( $code );
173 $lang = Language
::newFromCode( $fallback );
175 $lang->setCode( $code );
182 function __construct() {
183 $this->mConverter
= new FakeConverter($this);
184 // Set the code to the name of the descendant
185 if ( get_class( $this ) == 'Language' ) {
188 $this->mCode
= str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
193 * Reduce memory usage
195 function __destruct() {
196 foreach ( $this as $name => $value ) {
197 unset( $this->$name );
202 * Hook which will be called if this is the content language.
203 * Descendants can use this to register hook functions or modify globals
205 function initContLang() {}
208 * @deprecated Use User::getDefaultOptions()
211 function getDefaultUserOptions() {
212 wfDeprecated( __METHOD__
);
213 return User
::getDefaultOptions();
216 function getFallbackLanguageCode() {
217 return self
::getFallbackFor( $this->mCode
);
221 * Exports $wgBookstoreListEn
224 function getBookstoreList() {
226 return $this->bookstoreList
;
232 function getNamespaces() {
234 return $this->namespaceNames
;
238 * A convenience function that returns the same thing as
239 * getNamespaces() except with the array values changed to ' '
240 * where it found '_', useful for producing output to be displayed
241 * e.g. in <select> forms.
245 function getFormattedNamespaces() {
246 $ns = $this->getNamespaces();
247 foreach($ns as $k => $v) {
248 $ns[$k] = strtr($v, '_', ' ');
254 * Get a namespace value by key
256 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
257 * echo $mw_ns; // prints 'MediaWiki'
260 * @param $index Int: the array key of the namespace to return
261 * @return mixed, string if the namespace value exists, otherwise false
263 function getNsText( $index ) {
264 $ns = $this->getNamespaces();
265 return isset( $ns[$index] ) ?
$ns[$index] : false;
269 * A convenience function that returns the same thing as
270 * getNsText() except with '_' changed to ' ', useful for
275 function getFormattedNsText( $index ) {
276 $ns = $this->getNsText( $index );
277 return strtr($ns, '_', ' ');
281 * Get a namespace key by value, case insensitive.
282 * Only matches namespace names for the current language, not the
283 * canonical ones defined in Namespace.php.
285 * @param $text String
286 * @return mixed An integer if $text is a valid value otherwise false
288 function getLocalNsIndex( $text ) {
290 $lctext = $this->lc($text);
291 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
295 * Get a namespace key by value, case insensitive. Canonical namespace
296 * names override custom ones defined for the current language.
298 * @param $text String
299 * @return mixed An integer if $text is a valid value otherwise false
301 function getNsIndex( $text ) {
303 $lctext = $this->lc($text);
304 if( ( $ns = MWNamespace
::getCanonicalIndex( $lctext ) ) !== null ) return $ns;
305 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
309 * short names for language variants used for language conversion links.
311 * @param $code String
314 function getVariantname( $code ) {
315 return $this->getMessageFromDB( "variantname-$code" );
318 function specialPage( $name ) {
319 $aliases = $this->getSpecialPageAliases();
320 if ( isset( $aliases[$name][0] ) ) {
321 $name = $aliases[$name][0];
323 return $this->getNsText(NS_SPECIAL
) . ':' . $name;
326 function getQuickbarSettings() {
328 $this->getMessage( 'qbsettings-none' ),
329 $this->getMessage( 'qbsettings-fixedleft' ),
330 $this->getMessage( 'qbsettings-fixedright' ),
331 $this->getMessage( 'qbsettings-floatingleft' ),
332 $this->getMessage( 'qbsettings-floatingright' )
336 function getSkinNames() {
338 return $this->skinNames
;
341 function getMathNames() {
343 return $this->mathNames
;
346 function getDatePreferences() {
348 return $this->datePreferences
;
351 function getDateFormats() {
353 return $this->dateFormats
;
356 function getDefaultDateFormat() {
358 return $this->defaultDateFormat
;
361 function getDatePreferenceMigrationMap() {
363 return $this->datePreferenceMigrationMap
;
366 function getImageFile( $image ) {
368 return $this->imageFiles
[$image];
371 function getDefaultUserOptionOverrides() {
373 # XXX - apparently some languageas get empty arrays, didn't get to it yet -- midom
374 if (is_array($this->defaultUserOptionOverrides
)) {
375 return $this->defaultUserOptionOverrides
;
381 function getExtraUserToggles() {
383 return $this->extraUserToggles
;
386 function getUserToggle( $tog ) {
387 return $this->getMessageFromDB( "tog-$tog" );
391 * Get language names, indexed by code.
392 * If $customisedOnly is true, only returns codes with a messages file
394 public static function getLanguageNames( $customisedOnly = false ) {
395 global $wgLanguageNames, $wgExtraLanguageNames;
396 $allNames = $wgExtraLanguageNames +
$wgLanguageNames;
397 if ( !$customisedOnly ) {
403 $dir = opendir( "$IP/languages/messages" );
404 while( false !== ( $file = readdir( $dir ) ) ) {
406 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
407 $code = str_replace( '_', '-', strtolower( $m[1] ) );
408 if ( isset( $allNames[$code] ) ) {
409 $names[$code] = $allNames[$code];
418 * Get a message from the MediaWiki namespace.
420 * @param $msg String: message name
423 function getMessageFromDB( $msg ) {
424 return wfMsgExt( $msg, array( 'parsemag', 'language' => $this ) );
427 function getLanguageName( $code ) {
428 $names = self
::getLanguageNames();
429 if ( !array_key_exists( $code, $names ) ) {
432 return $names[$code];
435 function getMonthName( $key ) {
436 return $this->getMessageFromDB( self
::$mMonthMsgs[$key-1] );
439 function getMonthNameGen( $key ) {
440 return $this->getMessageFromDB( self
::$mMonthGenMsgs[$key-1] );
443 function getMonthAbbreviation( $key ) {
444 return $this->getMessageFromDB( self
::$mMonthAbbrevMsgs[$key-1] );
447 function getWeekdayName( $key ) {
448 return $this->getMessageFromDB( self
::$mWeekdayMsgs[$key-1] );
451 function getWeekdayAbbreviation( $key ) {
452 return $this->getMessageFromDB( self
::$mWeekdayAbbrevMsgs[$key-1] );
455 function getIranianCalendarMonthName( $key ) {
456 return $this->getMessageFromDB( self
::$mIranianCalendarMonthMsgs[$key-1] );
459 function getHebrewCalendarMonthName( $key ) {
460 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthMsgs[$key-1] );
463 function getHebrewCalendarMonthNameGen( $key ) {
464 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthGenMsgs[$key-1] );
467 function getHijriCalendarMonthName( $key ) {
468 return $this->getMessageFromDB( self
::$mHijriCalendarMonthMsgs[$key-1] );
472 * Used by date() and time() to adjust the time output.
474 * @param $ts Int the time in date('YmdHis') format
475 * @param $tz Mixed: adjust the time by this amount (default false, mean we
476 * get user timecorrection setting)
479 function userAdjust( $ts, $tz = false ) {
480 global $wgUser, $wgLocalTZoffset;
482 if ( $tz === false ) {
483 $tz = $wgUser->getOption( 'timecorrection' );
486 $data = explode( '|', $tz, 3 );
488 if ( $data[0] == 'ZoneInfo' ) {
489 if ( function_exists( 'timezone_open' ) && @timezone_open
( $data[2] ) !== false ) {
490 $date = date_create( $ts, timezone_open( 'UTC' ) );
491 date_timezone_set( $date, timezone_open( $data[2] ) );
492 $date = date_format( $date, 'YmdHis' );
495 # Unrecognized timezone, default to 'Offset' with the stored offset.
500 if ( $data[0] == 'System' ||
$tz == '' ) {
501 # Global offset in minutes.
502 if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset;
503 } else if ( $data[0] == 'Offset' ) {
504 $minDiff = intval( $data[1] );
506 $data = explode( ':', $tz );
507 if( count( $data ) == 2 ) {
508 $data[0] = intval( $data[0] );
509 $data[1] = intval( $data[1] );
510 $minDiff = abs( $data[0] ) * 60 +
$data[1];
511 if ( $data[0] < 0 ) $minDiff = -$minDiff;
513 $minDiff = intval( $data[0] ) * 60;
517 # No difference ? Return time unchanged
518 if ( 0 == $minDiff ) return $ts;
520 wfSuppressWarnings(); // E_STRICT system time bitching
521 # Generate an adjusted date; take advantage of the fact that mktime
522 # will normalize out-of-range values so we don't have to split $minDiff
523 # into hours and minutes.
525 (int)substr( $ts, 8, 2) ), # Hours
526 (int)substr( $ts, 10, 2 ) +
$minDiff, # Minutes
527 (int)substr( $ts, 12, 2 ), # Seconds
528 (int)substr( $ts, 4, 2 ), # Month
529 (int)substr( $ts, 6, 2 ), # Day
530 (int)substr( $ts, 0, 4 ) ); #Year
532 $date = date( 'YmdHis', $t );
539 * This is a workalike of PHP's date() function, but with better
540 * internationalisation, a reduced set of format characters, and a better
543 * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the
544 * PHP manual for definitions. There are a number of extensions, which
547 * xn Do not translate digits of the next numeric format character
548 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
549 * xr Use roman numerals for the next numeric format character
550 * xh Use hebrew numerals for the next numeric format character
552 * xg Genitive month name
554 * xij j (day number) in Iranian calendar
555 * xiF F (month name) in Iranian calendar
556 * xin n (month number) in Iranian calendar
557 * xiY Y (full year) in Iranian calendar
559 * xjj j (day number) in Hebrew calendar
560 * xjF F (month name) in Hebrew calendar
561 * xjt t (days in month) in Hebrew calendar
562 * xjx xg (genitive month name) in Hebrew calendar
563 * xjn n (month number) in Hebrew calendar
564 * xjY Y (full year) in Hebrew calendar
566 * xmj j (day number) in Hijri calendar
567 * xmF F (month name) in Hijri calendar
568 * xmn n (month number) in Hijri calendar
569 * xmY Y (full year) in Hijri calendar
571 * xkY Y (full year) in Thai solar calendar. Months and days are
572 * identical to the Gregorian calendar
574 * Characters enclosed in double quotes will be considered literal (with
575 * the quotes themselves removed). Unmatched quotes will be considered
576 * literal quotes. Example:
578 * "The month is" F => The month is January
581 * Backslash escaping is also supported.
583 * Input timestamp is assumed to be pre-normalized to the desired local
586 * @param $format String
587 * @param $ts String: 14-character timestamp
591 function sprintfDate( $format, $ts ) {
602 for ( $p = 0; $p < strlen( $format ); $p++
) {
605 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
606 $code .= $format[++
$p];
609 if ( ( $code === 'xi' ||
$code == 'xj' ||
$code == 'xk' ||
$code == 'xm' ) && $p < strlen( $format ) - 1 ) {
610 $code .= $format[++
$p];
621 $rawToggle = !$rawToggle;
630 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
633 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
634 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
637 $num = substr( $ts, 6, 2 );
640 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
641 $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) +
1 );
644 $num = intval( substr( $ts, 6, 2 ) );
647 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
651 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
655 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
659 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
660 $s .= $this->getWeekdayName( gmdate( 'w', $unix ) +
1 );
663 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
664 $w = gmdate( 'w', $unix );
668 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
669 $num = gmdate( 'w', $unix );
672 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
673 $num = gmdate( 'z', $unix );
676 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
677 $num = gmdate( 'W', $unix );
680 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
683 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
684 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
687 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
688 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
691 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
692 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
695 $num = substr( $ts, 4, 2 );
698 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
701 $num = intval( substr( $ts, 4, 2 ) );
704 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
708 if ( !$hijri ) $hijri = self
::tsToHijri ( $ts );
712 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
716 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
717 $num = gmdate( 't', $unix );
720 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
724 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
725 $num = gmdate( 'L', $unix );
728 $num = substr( $ts, 0, 4 );
731 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
735 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
739 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
743 if ( !$thai ) $thai = self
::tsToThai( $ts );
747 $num = substr( $ts, 2, 2 );
750 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' : 'pm';
753 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' : 'PM';
756 $h = substr( $ts, 8, 2 );
757 $num = $h %
12 ?
$h %
12 : 12;
760 $num = intval( substr( $ts, 8, 2 ) );
763 $h = substr( $ts, 8, 2 );
764 $num = sprintf( '%02d', $h %
12 ?
$h %
12 : 12 );
767 $num = substr( $ts, 8, 2 );
770 $num = substr( $ts, 10, 2 );
773 $num = substr( $ts, 12, 2 );
776 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
777 $s .= gmdate( 'c', $unix );
780 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
781 $s .= gmdate( 'r', $unix );
784 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
789 if ( $p < strlen( $format ) - 1 ) {
797 if ( $p < strlen( $format ) - 1 ) {
798 $endQuote = strpos( $format, '"', $p +
1 );
799 if ( $endQuote === false ) {
800 # No terminating quote, assume literal "
803 $s .= substr( $format, $p +
1, $endQuote - $p - 1 );
807 # Quote at end of string, assume literal "
814 if ( $num !== false ) {
815 if ( $rawToggle ||
$raw ) {
818 } elseif ( $roman ) {
819 $s .= self
::romanNumeral( $num );
821 } elseif( $hebrewNum ) {
822 $s .= self
::hebrewNumeral( $num );
825 $s .= $this->formatNum( $num, true );
833 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
834 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
836 * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
837 * Gregorian dates to Iranian dates. Originally written in C, it
838 * is released under the terms of GNU Lesser General Public
839 * License. Conversion to PHP was performed by Niklas Laxström.
841 * Link: http://www.farsiweb.info/jalali/jalali.c
843 private static function tsToIranian( $ts ) {
844 $gy = substr( $ts, 0, 4 ) -1600;
845 $gm = substr( $ts, 4, 2 ) -1;
846 $gd = substr( $ts, 6, 2 ) -1;
848 # Days passed from the beginning (including leap years)
851 - floor(($gy+
99) / 100)
852 +
floor(($gy+
399) / 400);
855 // Add days of the past months of this year
856 for( $i = 0; $i < $gm; $i++
) {
857 $gDayNo +
= self
::$GREG_DAYS[$i];
861 if ( $gm > 1 && (($gy%4
===0 && $gy%100
!==0 ||
($gy%400
==0)))) {
865 // Days passed in current month
868 $jDayNo = $gDayNo - 79;
870 $jNp = floor($jDayNo / 12053);
873 $jy = 979 +
33*$jNp +
4*floor($jDayNo/1461);
876 if ( $jDayNo >= 366 ) {
877 $jy +
= floor(($jDayNo-1)/365);
878 $jDayNo = floor(($jDayNo-1)%365
);
881 for ( $i = 0; $i < 11 && $jDayNo >= self
::$IRANIAN_DAYS[$i]; $i++
) {
882 $jDayNo -= self
::$IRANIAN_DAYS[$i];
888 return array($jy, $jm, $jd);
891 * Converting Gregorian dates to Hijri dates.
893 * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license
895 * @link http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0
897 private static function tsToHijri ( $ts ) {
898 $year = substr( $ts, 0, 4 );
899 $month = substr( $ts, 4, 2 );
900 $day = substr( $ts, 6, 2 );
909 if (($zy>1582)||
(($zy==1582)&&($zm>10))||
(($zy==1582)&&($zm==10)&&($zd>14)))
913 $zjd=(int)((1461*($zy +
4800 +
(int)( ($zm-14) /12) ))/4) +
(int)((367*($zm-2-12*((int)(($zm-14)/12))))/12)-(int)((3*(int)(( ($zy+
4900+
(int)(($zm-14)/12))/100)))/4)+
$zd-32075;
917 $zjd = 367*$zy-(int)((7*($zy+
5001+
(int)(($zm-9)/7)))/4)+
(int)((275*$zm)/9)+
$zd+
1729777;
920 $zl=$zjd-1948440+
10632;
921 $zn=(int)(($zl-1)/10631);
922 $zl=$zl-10631*$zn+
354;
923 $zj=((int)((10985-$zl)/5316))*((int)((50*$zl)/17719))+
((int)($zl/5670))*((int)((43*$zl)/15238));
924 $zl=$zl-((int)((30-$zj)/15))*((int)((17719*$zj)/50))-((int)($zj/16))*((int)((15238*$zj)/43))+
29;
925 $zm=(int)((24*$zl)/709);
926 $zd=$zl-(int)((709*$zm)/24);
929 return array ($zy, $zm, $zd);
933 * Converting Gregorian dates to Hebrew dates.
935 * Based on a JavaScript code by Abu Mami and Yisrael Hersch
936 * (abu-mami@kaluach.net, http://www.kaluach.net), who permitted
937 * to translate the relevant functions into PHP and release them under
940 * The months are counted from Tishrei = 1. In a leap year, Adar I is 13
941 * and Adar II is 14. In a non-leap year, Adar is 6.
943 private static function tsToHebrew( $ts ) {
945 $year = substr( $ts, 0, 4 );
946 $month = substr( $ts, 4, 2 );
947 $day = substr( $ts, 6, 2 );
949 # Calculate Hebrew year
950 $hebrewYear = $year +
3760;
952 # Month number when September = 1, August = 12
961 # Calculate day of year from 1 September
963 for( $i = 1; $i < $month; $i++
) {
967 # Check if the year is leap
968 if( $year %
400 == 0 ||
( $year %
4 == 0 && $year %
100 > 0 ) ) {
971 } elseif( $i == 8 ||
$i == 10 ||
$i == 1 ||
$i == 3 ) {
978 # Calculate the start of the Hebrew year
979 $start = self
::hebrewYearStart( $hebrewYear );
981 # Calculate next year's start
982 if( $dayOfYear <= $start ) {
983 # Day is before the start of the year - it is the previous year
989 # Add days since previous year's 1 September
991 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
995 # Start of the new (previous) year
996 $start = self
::hebrewYearStart( $hebrewYear );
999 $nextStart = self
::hebrewYearStart( $hebrewYear +
1 );
1002 # Calculate Hebrew day of year
1003 $hebrewDayOfYear = $dayOfYear - $start;
1005 # Difference between year's days
1006 $diff = $nextStart - $start;
1007 # Add 12 (or 13 for leap years) days to ignore the difference between
1008 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1009 # difference is only about the year type
1010 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1016 # Check the year pattern, and is leap year
1017 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1018 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1019 # and non-leap years
1020 $yearPattern = $diff %
30;
1021 # Check if leap year
1022 $isLeap = $diff >= 30;
1024 # Calculate day in the month from number of day in the Hebrew year
1025 # Don't check Adar - if the day is not in Adar, we will stop before;
1026 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1027 $hebrewDay = $hebrewDayOfYear;
1030 while( $hebrewMonth <= 12 ) {
1031 # Calculate days in this month
1032 if( $isLeap && $hebrewMonth == 6 ) {
1033 # Adar in a leap year
1035 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1037 if( $hebrewDay <= $days ) {
1041 # Subtract the days of Adar I
1042 $hebrewDay -= $days;
1045 if( $hebrewDay <= $days ) {
1051 } elseif( $hebrewMonth == 2 && $yearPattern == 2 ) {
1052 # Cheshvan in a complete year (otherwise as the rule below)
1054 } elseif( $hebrewMonth == 3 && $yearPattern == 0 ) {
1055 # Kislev in an incomplete year (otherwise as the rule below)
1058 # Odd months have 30 days, even have 29
1059 $days = 30 - ( $hebrewMonth - 1 ) %
2;
1061 if( $hebrewDay <= $days ) {
1062 # In the current month
1065 # Subtract the days of the current month
1066 $hebrewDay -= $days;
1067 # Try in the next month
1072 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
1076 * This calculates the Hebrew year start, as days since 1 September.
1077 * Based on Carl Friedrich Gauss algorithm for finding Easter date.
1078 * Used for Hebrew date.
1080 private static function hebrewYearStart( $year ) {
1081 $a = intval( ( 12 * ( $year - 1 ) +
17 ) %
19 );
1082 $b = intval( ( $year - 1 ) %
4 );
1083 $m = 32.044093161144 +
1.5542417966212 * $a +
$b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1087 $Mar = intval( $m );
1093 $c = intval( ( $Mar +
3 * ( $year - 1 ) +
5 * $b +
5 ) %
7);
1094 if( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1096 } else if( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1098 } else if( $c == 2 ||
$c == 4 ||
$c == 6 ) {
1102 $Mar +
= intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1107 * Algorithm to convert Gregorian dates to Thai solar dates.
1109 * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
1111 * @param $ts String: 14-character timestamp
1112 * @return array converted year, month, day
1114 private static function tsToThai( $ts ) {
1115 $gy = substr( $ts, 0, 4 );
1116 $gm = substr( $ts, 4, 2 );
1117 $gd = substr( $ts, 6, 2 );
1119 # Add 543 years to the Gregorian calendar
1120 # Months and days are identical
1121 $gy_thai = $gy +
543;
1123 return array( $gy_thai, $gm, $gd );
1128 * Roman number formatting up to 3000
1130 static function romanNumeral( $num ) {
1131 static $table = array(
1132 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1133 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1134 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1135 array( '', 'M', 'MM', 'MMM' )
1138 $num = intval( $num );
1139 if ( $num > 3000 ||
$num <= 0 ) {
1144 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1145 if ( $num >= $pow10 ) {
1146 $s .= $table[$i][floor($num / $pow10)];
1148 $num = $num %
$pow10;
1154 * Hebrew Gematria number formatting up to 9999
1156 static function hebrewNumeral( $num ) {
1157 static $table = array(
1158 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1159 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1160 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1161 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1164 $num = intval( $num );
1165 if ( $num > 9999 ||
$num <= 0 ) {
1170 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1171 if ( $num >= $pow10 ) {
1172 if ( $num == 15 ||
$num == 16 ) {
1173 $s .= $table[0][9] . $table[0][$num - 9];
1176 $s .= $table[$i][intval( ( $num / $pow10 ) )];
1177 if( $pow10 == 1000 ) {
1182 $num = $num %
$pow10;
1184 if( strlen( $s ) == 2 ) {
1187 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1188 $str .= substr( $s, strlen( $s ) - 2, 2 );
1190 $start = substr( $str, 0, strlen( $str ) - 2 );
1191 $end = substr( $str, strlen( $str ) - 2 );
1194 $str = $start . 'ך';
1197 $str = $start . 'ם';
1200 $str = $start . 'ן';
1203 $str = $start . 'ף';
1206 $str = $start . 'ץ';
1213 * This is meant to be used by time(), date(), and timeanddate() to get
1214 * the date preference they're supposed to use, it should be used in
1218 * function timeanddate([...], $format = true) {
1219 * $datePreference = $this->dateFormat($format);
1224 * @param $usePrefs Mixed: if true, the user's preference is used
1225 * if false, the site/language default is used
1226 * if int/string, assumed to be a format.
1229 function dateFormat( $usePrefs = true ) {
1232 if( is_bool( $usePrefs ) ) {
1234 $datePreference = $wgUser->getDatePreference();
1236 $options = User
::getDefaultOptions();
1237 $datePreference = (string)$options['date'];
1240 $datePreference = (string)$usePrefs;
1244 if( $datePreference == '' ) {
1248 return $datePreference;
1252 * @param $ts Mixed: the time format which needs to be turned into a
1253 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1254 * @param $adj Bool: whether to adjust the time output according to the
1255 * user configured offset ($timecorrection)
1256 * @param $format Mixed: true to use user's date format preference
1257 * @param $timecorrection String: the time offset as returned by
1258 * validateTimeZone() in Special:Preferences
1261 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
1264 $ts = $this->userAdjust( $ts, $timecorrection );
1267 $pref = $this->dateFormat( $format );
1268 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref date"] ) ) {
1269 $pref = $this->defaultDateFormat
;
1271 return $this->sprintfDate( $this->dateFormats
["$pref date"], $ts );
1275 * @param $ts Mixed: the time format which needs to be turned into a
1276 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1277 * @param $adj Bool: whether to adjust the time output according to the
1278 * user configured offset ($timecorrection)
1279 * @param $format Mixed: true to use user's date format preference
1280 * @param $timecorrection String: the time offset as returned by
1281 * validateTimeZone() in Special:Preferences
1284 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
1287 $ts = $this->userAdjust( $ts, $timecorrection );
1290 $pref = $this->dateFormat( $format );
1291 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref time"] ) ) {
1292 $pref = $this->defaultDateFormat
;
1294 return $this->sprintfDate( $this->dateFormats
["$pref time"], $ts );
1298 * @param $ts Mixed: the time format which needs to be turned into a
1299 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1300 * @param $adj Bool: whether to adjust the time output according to the
1301 * user configured offset ($timecorrection)
1302 * @param $format Mixed: what format to return, if it's false output the
1303 * default one (default true)
1304 * @param $timecorrection String: the time offset as returned by
1305 * validateTimeZone() in Special:Preferences
1308 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
1311 $ts = wfTimestamp( TS_MW
, $ts );
1314 $ts = $this->userAdjust( $ts, $timecorrection );
1317 $pref = $this->dateFormat( $format );
1318 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref both"] ) ) {
1319 $pref = $this->defaultDateFormat
;
1322 return $this->sprintfDate( $this->dateFormats
["$pref both"], $ts );
1325 function getMessage( $key ) {
1327 return isset( $this->messages
[$key] ) ?
$this->messages
[$key] : null;
1330 function getAllMessages() {
1332 return $this->messages
;
1335 function iconv( $in, $out, $string ) {
1336 # For most languages, this is a wrapper for iconv
1337 return iconv( $in, $out . '//IGNORE', $string );
1340 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
1341 function ucwordbreaksCallbackAscii($matches){
1342 return $this->ucfirst($matches[1]);
1345 function ucwordbreaksCallbackMB($matches){
1346 return mb_strtoupper($matches[0]);
1349 function ucCallback($matches){
1350 list( $wikiUpperChars ) = self
::getCaseMaps();
1351 return strtr( $matches[1], $wikiUpperChars );
1354 function lcCallback($matches){
1355 list( , $wikiLowerChars ) = self
::getCaseMaps();
1356 return strtr( $matches[1], $wikiLowerChars );
1359 function ucwordsCallbackMB($matches){
1360 return mb_strtoupper($matches[0]);
1363 function ucwordsCallbackWiki($matches){
1364 list( $wikiUpperChars ) = self
::getCaseMaps();
1365 return strtr( $matches[0], $wikiUpperChars );
1368 function ucfirst( $str ) {
1369 if ( empty($str) ) return $str;
1370 if ( ord($str[0]) < 128 ) return ucfirst($str);
1371 else return self
::uc($str,true); // fall back to more complex logic in case of multibyte strings
1374 function uc( $str, $first = false ) {
1375 if ( function_exists( 'mb_strtoupper' ) ) {
1377 if ( self
::isMultibyte( $str ) ) {
1378 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1380 return ucfirst( $str );
1383 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
1386 if ( self
::isMultibyte( $str ) ) {
1387 list( $wikiUpperChars ) = $this->getCaseMaps();
1388 $x = $first ?
'^' : '';
1389 return preg_replace_callback(
1390 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1391 array($this,"ucCallback"),
1395 return $first ?
ucfirst( $str ) : strtoupper( $str );
1400 function lcfirst( $str ) {
1401 if ( empty($str) ) return $str;
1402 if ( is_string( $str ) && ord($str[0]) < 128 ) {
1403 // editing string in place = cool
1404 $str[0]=strtolower($str[0]);
1407 else return self
::lc( $str, true );
1410 function lc( $str, $first = false ) {
1411 if ( function_exists( 'mb_strtolower' ) )
1413 if ( self
::isMultibyte( $str ) )
1414 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1416 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
1418 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
1420 if ( self
::isMultibyte( $str ) ) {
1421 list( , $wikiLowerChars ) = self
::getCaseMaps();
1422 $x = $first ?
'^' : '';
1423 return preg_replace_callback(
1424 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1425 array($this,"lcCallback"),
1429 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
1432 function isMultibyte( $str ) {
1433 return (bool)preg_match( '/[\x80-\xff]/', $str );
1436 function ucwords($str) {
1437 if ( self
::isMultibyte( $str ) ) {
1438 $str = self
::lc($str);
1440 // regexp to find first letter in each word (i.e. after each space)
1441 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1443 // function to use to capitalize a single char
1444 if ( function_exists( 'mb_strtoupper' ) )
1445 return preg_replace_callback(
1447 array($this,"ucwordsCallbackMB"),
1451 return preg_replace_callback(
1453 array($this,"ucwordsCallbackWiki"),
1458 return ucwords( strtolower( $str ) );
1461 # capitalize words at word breaks
1462 function ucwordbreaks($str){
1463 if (self
::isMultibyte( $str ) ) {
1464 $str = self
::lc($str);
1466 // since \b doesn't work for UTF-8, we explicitely define word break chars
1467 $breaks= "[ \-\(\)\}\{\.,\?!]";
1469 // find first letter after word break
1470 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1472 if ( function_exists( 'mb_strtoupper' ) )
1473 return preg_replace_callback(
1475 array($this,"ucwordbreaksCallbackMB"),
1479 return preg_replace_callback(
1481 array($this,"ucwordsCallbackWiki"),
1486 return preg_replace_callback(
1487 '/\b([\w\x80-\xff]+)\b/',
1488 array($this,"ucwordbreaksCallbackAscii"),
1493 * Return a case-folded representation of $s
1495 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1496 * and $s2 are the same except for the case of their characters. It is not
1497 * necessary for the value returned to make sense when displayed.
1499 * Do *not* perform any other normalisation in this function. If a caller
1500 * uses this function when it should be using a more general normalisation
1501 * function, then fix the caller.
1503 function caseFold( $s ) {
1504 return $this->uc( $s );
1507 function checkTitleEncoding( $s ) {
1508 if( is_array( $s ) ) {
1509 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1511 # Check for non-UTF-8 URLs
1512 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1513 if(!$ishigh) return $s;
1515 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1516 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1517 if( $isutf8 ) return $s;
1519 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1522 function fallback8bitEncoding() {
1524 return $this->fallback8bitEncoding
;
1528 * Some languages have special punctuation to strip out
1529 * or characters which need to be converted for MySQL's
1530 * indexing to grok it correctly. Make such changes here.
1532 * @param $string String
1535 function stripForSearch( $string ) {
1537 if ( $wgDBtype != 'mysql' ) {
1542 wfProfileIn( __METHOD__
);
1544 // MySQL fulltext index doesn't grok utf-8, so we
1545 // need to fold cases and convert to hex
1546 $out = preg_replace_callback(
1547 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
1548 array( $this, 'stripForSearchCallback' ),
1549 $this->lc( $string ) );
1551 // And to add insult to injury, the default indexing
1552 // ignores short words... Pad them so we can pass them
1553 // through without reconfiguring the server...
1554 $minLength = $this->minSearchLength();
1555 if( $minLength > 1 ) {
1557 $out = preg_replace(
1563 // Periods within things like hostnames and IP addresses
1564 // are also important -- we want a search for "example.com"
1565 // or "192.168.1.1" to work sanely.
1567 // MySQL's search seems to ignore them, so you'd match on
1568 // "example.wikipedia.com" and "192.168.83.1" as well.
1569 $out = preg_replace(
1574 wfProfileOut( __METHOD__
);
1579 * Armor a case-folded UTF-8 string to get through MySQL's
1580 * fulltext search without being mucked up by funny charset
1581 * settings or anything else of the sort.
1583 protected function stripForSearchCallback( $matches ) {
1584 return 'U8' . bin2hex( $matches[1] );
1588 * Check MySQL server's ft_min_word_len setting so we know
1589 * if we need to pad short words...
1591 protected function minSearchLength() {
1592 if( !isset( $this->minSearchLength
) ) {
1593 $sql = "show global variables like 'ft\\_min\\_word\\_len'";
1594 $dbr = wfGetDB( DB_SLAVE
);
1595 $result = $dbr->query( $sql );
1596 $row = $result->fetchObject();
1599 if( $row && $row->Variable_name
== 'ft_min_word_len' ) {
1600 $this->minSearchLength
= intval( $row->Value
);
1602 $this->minSearchLength
= 0;
1605 return $this->minSearchLength
;
1608 function convertForSearchResult( $termsArray ) {
1609 # some languages, e.g. Chinese, need to do a conversion
1610 # in order for search results to be displayed correctly
1615 * Get the first character of a string.
1620 function firstChar( $s ) {
1622 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1623 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1625 if ( isset( $matches[1] ) ) {
1626 if ( strlen( $matches[1] ) != 3 ) {
1630 // Break down Hangul syllables to grab the first jamo
1631 $code = utf8ToCodepoint( $matches[1] );
1632 if ( $code < 0xac00 ||
0xd7a4 <= $code) {
1634 } elseif ( $code < 0xb098 ) {
1635 return "\xe3\x84\xb1";
1636 } elseif ( $code < 0xb2e4 ) {
1637 return "\xe3\x84\xb4";
1638 } elseif ( $code < 0xb77c ) {
1639 return "\xe3\x84\xb7";
1640 } elseif ( $code < 0xb9c8 ) {
1641 return "\xe3\x84\xb9";
1642 } elseif ( $code < 0xbc14 ) {
1643 return "\xe3\x85\x81";
1644 } elseif ( $code < 0xc0ac ) {
1645 return "\xe3\x85\x82";
1646 } elseif ( $code < 0xc544 ) {
1647 return "\xe3\x85\x85";
1648 } elseif ( $code < 0xc790 ) {
1649 return "\xe3\x85\x87";
1650 } elseif ( $code < 0xcc28 ) {
1651 return "\xe3\x85\x88";
1652 } elseif ( $code < 0xce74 ) {
1653 return "\xe3\x85\x8a";
1654 } elseif ( $code < 0xd0c0 ) {
1655 return "\xe3\x85\x8b";
1656 } elseif ( $code < 0xd30c ) {
1657 return "\xe3\x85\x8c";
1658 } elseif ( $code < 0xd558 ) {
1659 return "\xe3\x85\x8d";
1661 return "\xe3\x85\x8e";
1668 function initEncoding() {
1669 # Some languages may have an alternate char encoding option
1670 # (Esperanto X-coding, Japanese furigana conversion, etc)
1671 # If this language is used as the primary content language,
1672 # an override to the defaults can be set here on startup.
1675 function recodeForEdit( $s ) {
1676 # For some languages we'll want to explicitly specify
1677 # which characters make it into the edit box raw
1678 # or are converted in some way or another.
1679 # Note that if wgOutputEncoding is different from
1680 # wgInputEncoding, this text will be further converted
1681 # to wgOutputEncoding.
1682 global $wgEditEncoding;
1683 if( $wgEditEncoding == '' or
1684 $wgEditEncoding == 'UTF-8' ) {
1687 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1691 function recodeInput( $s ) {
1692 # Take the previous into account.
1693 global $wgEditEncoding;
1694 if($wgEditEncoding != "") {
1695 $enc = $wgEditEncoding;
1699 if( $enc == 'UTF-8' ) {
1702 return $this->iconv( $enc, 'UTF-8', $s );
1707 * For right-to-left language support
1717 * A hidden direction mark (LRM or RLM), depending on the language direction
1721 function getDirMark() {
1722 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1726 * An arrow, depending on the language direction
1730 function getArrow() {
1731 return $this->isRTL() ?
'←' : '→';
1735 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1739 function linkPrefixExtension() {
1741 return $this->linkPrefixExtension
;
1744 function &getMagicWords() {
1746 return $this->magicWords
;
1749 # Fill a MagicWord object with data from here
1750 function getMagic( &$mw ) {
1751 if ( !$this->mMagicHookDone
) {
1752 $this->mMagicHookDone
= true;
1753 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1755 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1756 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1758 $magicWords =& $this->getMagicWords();
1759 if ( isset( $magicWords[$mw->mId
] ) ) {
1760 $rawEntry = $magicWords[$mw->mId
];
1762 # Fall back to English if local list is incomplete
1763 $magicWords =& Language
::getMagicWords();
1764 if ( !isset($magicWords[$mw->mId
]) ) {
1765 throw new MWException("Magic word '{$mw->mId}' not found" );
1767 $rawEntry = $magicWords[$mw->mId
];
1771 if( !is_array( $rawEntry ) ) {
1772 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1774 $mw->mCaseSensitive
= $rawEntry[0];
1775 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1780 * Add magic words to the extension array
1782 function addMagicWordsByLang( $newWords ) {
1783 $code = $this->getCode();
1784 $fallbackChain = array();
1785 while ( $code && !in_array( $code, $fallbackChain ) ) {
1786 $fallbackChain[] = $code;
1787 $code = self
::getFallbackFor( $code );
1789 if ( !in_array( 'en', $fallbackChain ) ) {
1790 $fallbackChain[] = 'en';
1792 $fallbackChain = array_reverse( $fallbackChain );
1793 foreach ( $fallbackChain as $code ) {
1794 if ( isset( $newWords[$code] ) ) {
1795 $this->mMagicExtensions
= $newWords[$code] +
$this->mMagicExtensions
;
1801 * Get special page names, as an associative array
1802 * case folded alias => real name
1804 function getSpecialPageAliases() {
1807 // Cache aliases because it may be slow to load them
1808 if ( !isset( $this->mExtendedSpecialPageAliases
) ) {
1811 $this->mExtendedSpecialPageAliases
= $this->specialPageAliases
;
1813 global $wgExtensionAliasesFiles;
1814 foreach ( $wgExtensionAliasesFiles as $file ) {
1817 if ( !file_exists($file) )
1818 throw new MWException( "Aliases file does not exist: $file" );
1823 // Check the availability of aliases
1824 if ( !isset($aliases['en']) )
1825 throw new MWException( "Malformed aliases file: $file" );
1827 // Merge all aliases in fallback chain
1828 $code = $this->getCode();
1830 if ( !isset($aliases[$code]) ) continue;
1832 $aliases[$code] = $this->fixSpecialPageAliases( $aliases[$code] );
1833 /* Merge the aliases, THIS will break if there is special page name
1834 * which looks like a numerical key, thanks to PHP...
1835 * See the array_merge_recursive manual entry */
1836 $this->mExtendedSpecialPageAliases
= array_merge_recursive(
1837 $this->mExtendedSpecialPageAliases
, $aliases[$code] );
1839 } while ( $code = self
::getFallbackFor( $code ) );
1842 wfRunHooks( 'LanguageGetSpecialPageAliases',
1843 array( &$this->mExtendedSpecialPageAliases
, $this->getCode() ) );
1846 return $this->mExtendedSpecialPageAliases
;
1850 * Function to fix special page aliases. Will convert the first letter to
1851 * upper case and spaces to underscores. Can be given a full aliases array,
1852 * in which case it will recursively fix all aliases.
1854 public function fixSpecialPageAliases( $mixed ) {
1855 // Work recursively until in string level
1856 if ( is_array($mixed) ) {
1857 $callback = array( $this, 'fixSpecialPageAliases' );
1858 return array_map( $callback, $mixed );
1860 return str_replace( ' ', '_', $this->ucfirst( $mixed ) );
1864 * Italic is unsuitable for some languages
1866 * @param $text String: the text to be emphasized.
1869 function emphasize( $text ) {
1870 return "<em>$text</em>";
1874 * Normally we output all numbers in plain en_US style, that is
1875 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1876 * point twohundredthirtyfive. However this is not sutable for all
1877 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1878 * Icelandic just want to use commas instead of dots, and dots instead
1879 * of commas like "293.291,235".
1881 * An example of this function being called:
1883 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1886 * See LanguageGu.php for the Gujarati implementation and
1887 * $separatorTransformTable on MessageIs.php for
1888 * the , => . and . => , implementation.
1890 * @todo check if it's viable to use localeconv() for the decimal
1892 * @param $number Mixed: the string to be formatted, should be an integer
1893 * or a floating point number.
1894 * @param $nocommafy Bool: set to true for special numbers like dates
1897 function formatNum( $number, $nocommafy = false ) {
1898 global $wgTranslateNumerals;
1900 $number = $this->commafy($number);
1901 $s = $this->separatorTransformTable();
1902 if (!is_null($s)) { $number = strtr($number, $s); }
1905 if ($wgTranslateNumerals) {
1906 $s = $this->digitTransformTable();
1907 if (!is_null($s)) { $number = strtr($number, $s); }
1913 function parseFormattedNumber( $number ) {
1914 $s = $this->digitTransformTable();
1915 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1917 $s = $this->separatorTransformTable();
1918 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1920 $number = strtr( $number, array (',' => '') );
1925 * Adds commas to a given number
1930 function commafy($_) {
1931 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1934 function digitTransformTable() {
1936 return $this->digitTransformTable
;
1939 function separatorTransformTable() {
1941 return $this->separatorTransformTable
;
1946 * For the credit list in includes/Credits.php (action=credits)
1951 function listToText( $l ) {
1954 for ($i = $m; $i >= 0; $i--) {
1957 } else if ($i == $m - 1) {
1958 $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
1960 $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
1967 * Take a list of strings and build a locale-friendly comma-separated
1968 * list, using the local comma-separator message.
1969 * @param $list array of strings to put in a comma list
1972 function commaList( $list, $forContent = false ) {
1975 wfMsgExt( 'comma-separator', array( 'escapenoentities', 'language' => $this ) ) );
1979 * Same as commaList, but separate it with the pipe instead.
1980 * @param $list array of strings to put in a pipe list
1983 function pipeList( $list ) {
1986 wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) ) );
1990 * Truncate a string to a specified length in bytes, appending an optional
1991 * string (e.g. for ellipses)
1993 * The database offers limited byte lengths for some columns in the database;
1994 * multi-byte character sets mean we need to ensure that only whole characters
1995 * are included, otherwise broken characters can be passed to the user
1997 * If $length is negative, the string will be truncated from the beginning
1999 * @param $string String to truncate
2000 * @param $length Int: maximum length (excluding ellipses)
2001 * @param $ellipsis String to append to the truncated text
2004 function truncate( $string, $length, $ellipsis = "" ) {
2005 if( $length == 0 ) {
2008 if ( strlen( $string ) <= abs( $length ) ) {
2012 $string = substr( $string, 0, $length );
2013 $char = ord( $string[strlen( $string ) - 1] );
2015 if ($char >= 0xc0) {
2016 # We got the first byte only of a multibyte char; remove it.
2017 $string = substr( $string, 0, -1 );
2018 } elseif( $char >= 0x80 &&
2019 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
2020 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
2021 # We chopped in the middle of a character; remove it
2024 return $string . $ellipsis;
2026 $string = substr( $string, $length );
2027 $char = ord( $string[0] );
2028 if( $char >= 0x80 && $char < 0xc0 ) {
2029 # We chopped in the middle of a character; remove the whole thing
2030 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
2032 return $ellipsis . $string;
2037 * Grammatical transformations, needed for inflected languages
2038 * Invoked by putting {{grammar:case|word}} in a message
2040 * @param $word string
2041 * @param $case string
2044 function convertGrammar( $word, $case ) {
2045 global $wgGrammarForms;
2046 if ( isset($wgGrammarForms[$this->getCode()][$case][$word]) ) {
2047 return $wgGrammarForms[$this->getCode()][$case][$word];
2053 * Plural form transformations, needed for some languages.
2054 * For example, there are 3 form of plural in Russian and Polish,
2055 * depending on "count mod 10". See [[w:Plural]]
2056 * For English it is pretty simple.
2058 * Invoked by putting {{plural:count|wordform1|wordform2}}
2059 * or {{plural:count|wordform1|wordform2|wordform3}}
2061 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
2063 * @param $count Integer: non-localized number
2064 * @param $forms Array: different plural forms
2065 * @return string Correct form of plural for $count in this language
2067 function convertPlural( $count, $forms ) {
2068 if ( !count($forms) ) { return ''; }
2069 $forms = $this->preConvertPlural( $forms, 2 );
2071 return ( $count == 1 ) ?
$forms[0] : $forms[1];
2075 * Checks that convertPlural was given an array and pads it to requested
2076 * amound of forms by copying the last one.
2078 * @param $count Integer: How many forms should there be at least
2079 * @param $forms Array of forms given to convertPlural
2080 * @return array Padded array of forms or an exception if not an array
2082 protected function preConvertPlural( /* Array */ $forms, $count ) {
2083 while ( count($forms) < $count ) {
2084 $forms[] = $forms[count($forms)-1];
2090 * For translaing of expiry times
2091 * @param $str String: the validated block time in English
2092 * @return Somehow translated block time
2093 * @see LanguageFi.php for example implementation
2095 function translateBlockExpiry( $str ) {
2097 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
2099 if ( $scBlockExpiryOptions == '-') {
2103 foreach (explode(',', $scBlockExpiryOptions) as $option) {
2104 if ( strpos($option, ":") === false )
2106 list($show, $value) = explode(":", $option);
2107 if ( strcmp ( $str, $value) == 0 ) {
2108 return htmlspecialchars( trim( $show ) );
2116 * languages like Chinese need to be segmented in order for the diff
2119 * @param $text String
2122 function segmentForDiff( $text ) {
2127 * and unsegment to show the result
2129 * @param $text String
2132 function unsegmentForDiff( $text ) {
2136 # convert text to different variants of a language.
2137 function convert( $text, $isTitle = false) {
2138 return $this->mConverter
->convert($text, $isTitle);
2141 # Convert text from within Parser
2142 function parserConvert( $text, &$parser ) {
2143 return $this->mConverter
->parserConvert( $text, $parser );
2146 # Check if this is a language with variants
2147 function hasVariants(){
2148 return sizeof($this->getVariants())>1;
2151 # Put custom tags (e.g. -{ }-) around math to prevent conversion
2152 function armourMath($text){
2153 return $this->mConverter
->armourMath($text);
2158 * Perform output conversion on a string, and encode for safe HTML output.
2159 * @param $text String
2160 * @param $isTitle Bool -- wtf?
2162 * @todo this should get integrated somewhere sane
2164 function convertHtml( $text, $isTitle = false ) {
2165 return htmlspecialchars( $this->convert( $text, $isTitle ) );
2168 function convertCategoryKey( $key ) {
2169 return $this->mConverter
->convertCategoryKey( $key );
2173 * get the list of variants supported by this langauge
2174 * see sample implementation in LanguageZh.php
2176 * @return array an array of language codes
2178 function getVariants() {
2179 return $this->mConverter
->getVariants();
2183 function getPreferredVariant( $fromUser = true ) {
2184 return $this->mConverter
->getPreferredVariant( $fromUser );
2188 * if a language supports multiple variants, it is
2189 * possible that non-existing link in one variant
2190 * actually exists in another variant. this function
2191 * tries to find it. See e.g. LanguageZh.php
2193 * @param $link String: the name of the link
2194 * @param $nt Mixed: the title object of the link
2195 * @return null the input parameters may be modified upon return
2197 function findVariantLink( &$link, &$nt, $forTemplate = false ) {
2198 $this->mConverter
->findVariantLink($link, $nt, $forTemplate );
2202 * If a language supports multiple variants, converts text
2203 * into an array of all possible variants of the text:
2204 * 'variant' => text in that variant
2207 function convertLinkToAllVariants($text){
2208 return $this->mConverter
->convertLinkToAllVariants($text);
2213 * returns language specific options used by User::getPageRenderHash()
2214 * for example, the preferred language variant
2218 function getExtraHashOptions() {
2219 return $this->mConverter
->getExtraHashOptions();
2223 * for languages that support multiple variants, the title of an
2224 * article may be displayed differently in different variants. this
2225 * function returns the apporiate title defined in the body of the article.
2229 function getParsedTitle() {
2230 return $this->mConverter
->getParsedTitle();
2234 * Enclose a string with the "no conversion" tag. This is used by
2235 * various functions in the Parser
2237 * @param $text String: text to be tagged for no conversion
2239 * @return string the tagged text
2241 function markNoConversion( $text, $noParse=false ) {
2242 return $this->mConverter
->markNoConversion( $text, $noParse );
2246 * A regular expression to match legal word-trailing characters
2247 * which should be merged onto a link of the form [[foo]]bar.
2251 function linkTrail() {
2253 return $this->linkTrail
;
2256 function getLangObj() {
2261 * Get the RFC 3066 code for this language object
2263 function getCode() {
2264 return $this->mCode
;
2267 function setCode( $code ) {
2268 $this->mCode
= $code;
2271 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
2272 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
2275 static function getMessagesFileName( $code ) {
2277 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
2280 static function getClassFileName( $code ) {
2282 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
2285 static function getLocalisationArray( $code, $disableCache = false ) {
2286 self
::loadLocalisation( $code, $disableCache );
2287 return self
::$mLocalisationCache[$code];
2291 * Load localisation data for a given code into the static cache
2293 * @return array Dependencies, map of filenames to mtimes
2295 static function loadLocalisation( $code, $disableCache = false ) {
2296 static $recursionGuard = array();
2297 global $wgMemc, $wgEnableSerializedMessages, $wgCheckSerialized;
2300 throw new MWException( "Invalid language code requested" );
2303 if ( !$disableCache ) {
2304 # Try the per-process cache
2305 if ( isset( self
::$mLocalisationCache[$code] ) ) {
2306 return self
::$mLocalisationCache[$code]['deps'];
2309 wfProfileIn( __METHOD__
);
2311 # Try the serialized directory
2312 if( $wgEnableSerializedMessages ) {
2313 $cache = wfGetPrecompiledData( self
::getFileName( "Messages", $code, '.ser' ) );
2315 if ( $wgCheckSerialized && self
::isLocalisationOutOfDate( $cache ) ) {
2317 wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" );
2319 self
::$mLocalisationCache[$code] = $cache;
2320 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
2321 wfProfileOut( __METHOD__
);
2322 return self
::$mLocalisationCache[$code]['deps'];
2327 # Try the global cache
2328 $memcKey = wfMemcKey('localisation', $code );
2329 $fbMemcKey = wfMemcKey('fallback', $cache['fallback'] );
2330 $cache = $wgMemc->get( $memcKey );
2332 if ( self
::isLocalisationOutOfDate( $cache ) ) {
2333 $wgMemc->delete( $memcKey );
2334 $wgMemc->delete( $fbMemcKey );
2336 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired\n" );
2338 self
::$mLocalisationCache[$code] = $cache;
2339 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
2340 wfProfileOut( __METHOD__
);
2341 return $cache['deps'];
2345 wfProfileIn( __METHOD__
);
2348 # Default fallback, may be overridden when the messages file is included
2349 if ( $code != 'en' ) {
2355 # Load the primary localisation from the source file
2356 $filename = self
::getMessagesFileName( $code );
2357 if ( !file_exists( $filename ) ) {
2358 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
2359 $cache = compact( self
::$mLocalisationKeys ); // Set correct fallback
2362 $deps = array( $filename => filemtime( $filename ) );
2363 require( $filename );
2364 $cache = compact( self
::$mLocalisationKeys );
2365 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
2368 if ( !empty( $fallback ) ) {
2369 # Load the fallback localisation, with a circular reference guard
2370 if ( isset( $recursionGuard[$code] ) ) {
2371 throw new MWException( "Error: Circular fallback reference in language code $code" );
2373 $recursionGuard[$code] = true;
2374 $newDeps = self
::loadLocalisation( $fallback, $disableCache );
2375 unset( $recursionGuard[$code] );
2377 $secondary = self
::$mLocalisationCache[$fallback];
2378 $deps = array_merge( $deps, $newDeps );
2380 # Merge the fallback localisation with the current localisation
2381 foreach ( self
::$mLocalisationKeys as $key ) {
2382 if ( isset( $cache[$key] ) ) {
2383 if ( isset( $secondary[$key] ) ) {
2384 if ( in_array( $key, self
::$mMergeableMapKeys ) ) {
2385 $cache[$key] = $cache[$key] +
$secondary[$key];
2386 } elseif ( in_array( $key, self
::$mMergeableListKeys ) ) {
2387 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
2388 } elseif ( in_array( $key, self
::$mMergeableAliasListKeys ) ) {
2389 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
2393 $cache[$key] = $secondary[$key];
2397 # Merge bookstore lists if requested
2398 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
2399 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
2401 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
2402 unset( $cache['bookstoreList']['inherit'] );
2406 # Add dependencies to the cache entry
2407 $cache['deps'] = $deps;
2409 # Replace spaces with underscores in namespace names
2410 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
2412 # And do the same for specialpage aliases. $page is an array.
2413 foreach ( $cache['specialPageAliases'] as &$page ) {
2414 $page = str_replace( ' ', '_', $page );
2416 # Decouple the reference to prevent accidental damage
2419 # Save to both caches
2420 self
::$mLocalisationCache[$code] = $cache;
2421 if ( !$disableCache ) {
2422 $wgMemc->set( $memcKey, $cache );
2423 $wgMemc->set( $fbMemcKey, (string) $cache['fallback'] );
2426 wfProfileOut( __METHOD__
);
2431 * Test if a given localisation cache is out of date with respect to the
2432 * source Messages files. This is done automatically for the global cache
2433 * in $wgMemc, but is only done on certain occasions for the serialized
2436 * @param $cache mixed Either a language code or a cache array
2438 static function isLocalisationOutOfDate( $cache ) {
2439 if ( !is_array( $cache ) ) {
2440 self
::loadLocalisation( $cache );
2441 $cache = self
::$mLocalisationCache[$cache];
2444 foreach ( $cache['deps'] as $file => $mtime ) {
2445 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
2454 * Get the fallback for a given language
2456 static function getFallbackFor( $code ) {
2458 if ( $code === 'en' ) return false;
2461 static $cache = array();
2463 if ( isset($cache[$code]) ) return $cache[$code];
2467 $memcKey = wfMemcKey( 'fallback', $code );
2468 $fbcode = $wgMemc->get( $memcKey );
2470 if ( is_string($fbcode) ) {
2471 // False is stored as a string to detect failures in memcache properly
2472 if ( $fbcode === '' ) $fbcode = false;
2474 // Update local cache and return
2475 $cache[$code] = $fbcode;
2479 // Nothing in caches, load and and update both caches
2480 self
::loadLocalisation( $code );
2481 $fbcode = self
::$mLocalisationCache[$code]['fallback'];
2483 $cache[$code] = $fbcode;
2484 $wgMemc->set( $memcKey, (string) $fbcode );
2490 * Get all messages for a given language
2492 static function getMessagesFor( $code ) {
2493 self
::loadLocalisation( $code );
2494 return self
::$mLocalisationCache[$code]['messages'];
2498 * Get a message for a given language
2500 static function getMessageFor( $key, $code ) {
2501 self
::loadLocalisation( $code );
2502 return isset( self
::$mLocalisationCache[$code]['messages'][$key] ) ? self
::$mLocalisationCache[$code]['messages'][$key] : null;
2506 * Load localisation data for this object
2509 if ( !$this->mLoaded
) {
2510 self
::loadLocalisation( $this->getCode() );
2511 $cache =& self
::$mLocalisationCache[$this->getCode()];
2512 foreach ( self
::$mLocalisationKeys as $key ) {
2513 $this->$key = $cache[$key];
2515 $this->mLoaded
= true;
2517 $this->fixUpSettings();
2522 * Do any necessary post-cache-load settings adjustment
2524 function fixUpSettings() {
2525 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
2526 $wgNamespaceAliases, $wgAmericanDates;
2527 wfProfileIn( __METHOD__
);
2528 if ( $wgExtraNamespaces ) {
2529 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
2532 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
2533 if ( $wgMetaNamespaceTalk ) {
2534 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
2536 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
2537 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
2539 # Allow grammar transformations
2540 # Allowing full message-style parsing would make simple requests
2541 # such as action=raw much more expensive than they need to be.
2542 # This will hopefully cover most cases.
2543 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
2544 array( &$this, 'replaceGrammarInNamespace' ), $talk );
2545 $talk = str_replace( ' ', '_', $talk );
2546 $this->namespaceNames
[NS_PROJECT_TALK
] = $talk;
2549 # The above mixing may leave namespaces out of canonical order.
2550 # Re-order by namespace ID number...
2551 ksort( $this->namespaceNames
);
2553 # Put namespace names and aliases into a hashtable.
2554 # If this is too slow, then we should arrange it so that it is done
2555 # before caching. The catch is that at pre-cache time, the above
2556 # class-specific fixup hasn't been done.
2557 $this->mNamespaceIds
= array();
2558 foreach ( $this->namespaceNames
as $index => $name ) {
2559 $this->mNamespaceIds
[$this->lc($name)] = $index;
2561 if ( $this->namespaceAliases
) {
2562 foreach ( $this->namespaceAliases
as $name => $index ) {
2563 $this->mNamespaceIds
[$this->lc($name)] = $index;
2566 if ( $wgNamespaceAliases ) {
2567 foreach ( $wgNamespaceAliases as $name => $index ) {
2568 $this->mNamespaceIds
[$this->lc($name)] = $index;
2572 if ( $this->defaultDateFormat
== 'dmy or mdy' ) {
2573 $this->defaultDateFormat
= $wgAmericanDates ?
'mdy' : 'dmy';
2575 wfProfileOut( __METHOD__
);
2578 function replaceGrammarInNamespace( $m ) {
2579 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
2582 static function getCaseMaps() {
2583 static $wikiUpperChars, $wikiLowerChars;
2584 if ( isset( $wikiUpperChars ) ) {
2585 return array( $wikiUpperChars, $wikiLowerChars );
2588 wfProfileIn( __METHOD__
);
2589 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
2590 if ( $arr === false ) {
2591 throw new MWException(
2592 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
2595 wfProfileOut( __METHOD__
);
2596 return array( $wikiUpperChars, $wikiLowerChars );
2599 function formatTimePeriod( $seconds ) {
2600 if ( $seconds < 10 ) {
2601 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
2602 } elseif ( $seconds < 60 ) {
2603 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
2604 } elseif ( $seconds < 3600 ) {
2605 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
2606 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
2608 $hours = floor( $seconds / 3600 );
2609 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
2610 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
2611 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
2612 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
2613 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
2617 function formatBitrate( $bps ) {
2618 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
2620 return $this->formatNum( $bps ) . $units[0];
2622 $unitIndex = floor( log10( $bps ) / 3 );
2623 $mantissa = $bps / pow( 1000, $unitIndex );
2624 if ( $mantissa < 10 ) {
2625 $mantissa = round( $mantissa, 1 );
2627 $mantissa = round( $mantissa );
2629 return $this->formatNum( $mantissa ) . $units[$unitIndex];
2633 * Format a size in bytes for output, using an appropriate
2634 * unit (B, KB, MB or GB) according to the magnitude in question
2636 * @param $size Size to format
2637 * @return string Plain text (not HTML)
2639 function formatSize( $size ) {
2640 // For small sizes no decimal places necessary
2642 if( $size > 1024 ) {
2643 $size = $size / 1024;
2644 if( $size > 1024 ) {
2645 $size = $size / 1024;
2646 // For MB and bigger two decimal places are smarter
2648 if( $size > 1024 ) {
2649 $size = $size / 1024;
2650 $msg = 'size-gigabytes';
2652 $msg = 'size-megabytes';
2655 $msg = 'size-kilobytes';
2658 $msg = 'size-bytes';
2660 $size = round( $size, $round );
2661 $text = $this->getMessageFromDB( $msg );
2662 return str_replace( '$1', $this->formatNum( $size ), $text );