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 autoConvertToAllVariants($text) {return $text;}
39 function convert($t, $i) {return $t;}
40 function parserConvert($t, $p) {return $t;}
41 function getVariants() { return array( $this->mLang
->getCode() ); }
42 function getPreferredVariant() {return $this->mLang
->getCode(); }
43 function findVariantLink(&$l, &$n, $ignoreOtherCond = false) {}
44 function getExtraHashOptions() {return '';}
45 function getParsedTitle() {return '';}
46 function markNoConversion($text, $noParse=false) {return $text;}
47 function convertCategoryKey( $key ) {return $key; }
48 function convertLinkToAllVariants($text){ return array( $this->mLang
->getCode() => $text); }
49 function armourMath($text){ return $text; }
53 * Internationalisation code
57 var $mConverter, $mVariants, $mCode, $mLoaded = false;
58 var $mMagicExtensions = array(), $mMagicHookDone = false;
60 static public $mLocalisationKeys = array(
61 'fallback', 'namespaceNames', 'mathNames', 'bookstoreList',
62 'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
63 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
64 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
65 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
66 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
70 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
71 'dateFormats', 'defaultUserOptionOverrides', 'magicWords', 'imageFiles' );
73 static public $mMergeableListKeys = array( 'extraUserToggles' );
75 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
77 static public $mLocalisationCache = array();
78 static public $mLangObjCache = array();
80 static public $mWeekdayMsgs = array(
81 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
85 static public $mWeekdayAbbrevMsgs = array(
86 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
89 static public $mMonthMsgs = array(
90 'january', 'february', 'march', 'april', 'may_long', 'june',
91 'july', 'august', 'september', 'october', 'november',
94 static public $mMonthGenMsgs = array(
95 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
96 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
99 static public $mMonthAbbrevMsgs = array(
100 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
101 'sep', 'oct', 'nov', 'dec'
104 static public $mIranianCalendarMonthMsgs = array(
105 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
106 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
107 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
108 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
111 static public $mHebrewCalendarMonthMsgs = array(
112 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
113 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
114 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
115 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
116 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
119 static public $mHebrewCalendarMonthGenMsgs = array(
120 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
121 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
122 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
123 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
124 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
127 static public $mHijriCalendarMonthMsgs = array(
128 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
129 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
130 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
131 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
135 * Get a cached language object for a given language code
137 static function factory( $code ) {
138 if ( !isset( self
::$mLangObjCache[$code] ) ) {
139 if( count( self
::$mLangObjCache ) > 10 ) {
140 // Don't keep a billion objects around, that's stupid.
141 self
::$mLangObjCache = array();
143 self
::$mLangObjCache[$code] = self
::newFromCode( $code );
145 return self
::$mLangObjCache[$code];
149 * Create a language object for a given language code
151 protected static function newFromCode( $code ) {
153 static $recursionLevel = 0;
154 if ( $code == 'en' ) {
157 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
158 // Preload base classes to work around APC/PHP5 bug
159 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
160 include_once("$IP/languages/classes/$class.deps.php");
162 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
163 include_once("$IP/languages/classes/$class.php");
167 if ( $recursionLevel > 5 ) {
168 throw new MWException( "Language fallback loop detected when creating class $class\n" );
171 if( ! class_exists( $class ) ) {
172 $fallback = Language
::getFallbackFor( $code );
174 $lang = Language
::newFromCode( $fallback );
176 $lang->setCode( $code );
183 function __construct() {
184 $this->mConverter
= new FakeConverter($this);
185 // Set the code to the name of the descendant
186 if ( get_class( $this ) == 'Language' ) {
189 $this->mCode
= str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
194 * Reduce memory usage
196 function __destruct() {
197 foreach ( $this as $name => $value ) {
198 unset( $this->$name );
203 * Hook which will be called if this is the content language.
204 * Descendants can use this to register hook functions or modify globals
206 function initContLang() {}
209 * @deprecated Use User::getDefaultOptions()
212 function getDefaultUserOptions() {
213 wfDeprecated( __METHOD__
);
214 return User
::getDefaultOptions();
217 function getFallbackLanguageCode() {
218 return self
::getFallbackFor( $this->mCode
);
222 * Exports $wgBookstoreListEn
225 function getBookstoreList() {
227 return $this->bookstoreList
;
233 function getNamespaces() {
235 return $this->namespaceNames
;
239 * A convenience function that returns the same thing as
240 * getNamespaces() except with the array values changed to ' '
241 * where it found '_', useful for producing output to be displayed
242 * e.g. in <select> forms.
246 function getFormattedNamespaces() {
247 $ns = $this->getNamespaces();
248 foreach($ns as $k => $v) {
249 $ns[$k] = strtr($v, '_', ' ');
255 * Get a namespace value by key
257 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
258 * echo $mw_ns; // prints 'MediaWiki'
261 * @param $index Int: the array key of the namespace to return
262 * @return mixed, string if the namespace value exists, otherwise false
264 function getNsText( $index ) {
265 $ns = $this->getNamespaces();
266 return isset( $ns[$index] ) ?
$ns[$index] : false;
270 * A convenience function that returns the same thing as
271 * getNsText() except with '_' changed to ' ', useful for
276 function getFormattedNsText( $index ) {
277 $ns = $this->getNsText( $index );
278 return strtr($ns, '_', ' ');
282 * Get a namespace key by value, case insensitive.
283 * Only matches namespace names for the current language, not the
284 * canonical ones defined in Namespace.php.
286 * @param $text String
287 * @return mixed An integer if $text is a valid value otherwise false
289 function getLocalNsIndex( $text ) {
291 $lctext = $this->lc($text);
292 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
296 * Get a namespace key by value, case insensitive. Canonical namespace
297 * names override custom ones defined for the current language.
299 * @param $text String
300 * @return mixed An integer if $text is a valid value otherwise false
302 function getNsIndex( $text ) {
304 $lctext = $this->lc($text);
305 if( ( $ns = MWNamespace
::getCanonicalIndex( $lctext ) ) !== null ) return $ns;
306 return isset( $this->mNamespaceIds
[$lctext] ) ?
$this->mNamespaceIds
[$lctext] : false;
310 * short names for language variants used for language conversion links.
312 * @param $code String
315 function getVariantname( $code ) {
316 return $this->getMessageFromDB( "variantname-$code" );
319 function specialPage( $name ) {
320 $aliases = $this->getSpecialPageAliases();
321 if ( isset( $aliases[$name][0] ) ) {
322 $name = $aliases[$name][0];
324 return $this->getNsText( NS_SPECIAL
) . ':' . $name;
327 function getQuickbarSettings() {
329 $this->getMessage( 'qbsettings-none' ),
330 $this->getMessage( 'qbsettings-fixedleft' ),
331 $this->getMessage( 'qbsettings-fixedright' ),
332 $this->getMessage( 'qbsettings-floatingleft' ),
333 $this->getMessage( 'qbsettings-floatingright' )
337 function getMathNames() {
339 return $this->mathNames
;
342 function getDatePreferences() {
344 return $this->datePreferences
;
347 function getDateFormats() {
349 return $this->dateFormats
;
352 function getDefaultDateFormat() {
354 return $this->defaultDateFormat
;
357 function getDatePreferenceMigrationMap() {
359 return $this->datePreferenceMigrationMap
;
362 function getImageFile( $image ) {
364 return $this->imageFiles
[$image];
367 function getDefaultUserOptionOverrides() {
369 # XXX - apparently some languageas get empty arrays, didn't get to it yet -- midom
370 if (is_array($this->defaultUserOptionOverrides
)) {
371 return $this->defaultUserOptionOverrides
;
377 function getExtraUserToggles() {
379 return $this->extraUserToggles
;
382 function getUserToggle( $tog ) {
383 return $this->getMessageFromDB( "tog-$tog" );
387 * Get language names, indexed by code.
388 * If $customisedOnly is true, only returns codes with a messages file
390 public static function getLanguageNames( $customisedOnly = false ) {
391 global $wgLanguageNames, $wgExtraLanguageNames;
392 $allNames = $wgExtraLanguageNames +
$wgLanguageNames;
393 if ( !$customisedOnly ) {
399 $dir = opendir( "$IP/languages/messages" );
400 while( false !== ( $file = readdir( $dir ) ) ) {
402 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
403 $code = str_replace( '_', '-', strtolower( $m[1] ) );
404 if ( isset( $allNames[$code] ) ) {
405 $names[$code] = $allNames[$code];
414 * Get a message from the MediaWiki namespace.
416 * @param $msg String: message name
419 function getMessageFromDB( $msg ) {
420 return wfMsgExt( $msg, array( 'parsemag', 'language' => $this ) );
423 function getLanguageName( $code ) {
424 $names = self
::getLanguageNames();
425 if ( !array_key_exists( $code, $names ) ) {
428 return $names[$code];
431 function getMonthName( $key ) {
432 return $this->getMessageFromDB( self
::$mMonthMsgs[$key-1] );
435 function getMonthNameGen( $key ) {
436 return $this->getMessageFromDB( self
::$mMonthGenMsgs[$key-1] );
439 function getMonthAbbreviation( $key ) {
440 return $this->getMessageFromDB( self
::$mMonthAbbrevMsgs[$key-1] );
443 function getWeekdayName( $key ) {
444 return $this->getMessageFromDB( self
::$mWeekdayMsgs[$key-1] );
447 function getWeekdayAbbreviation( $key ) {
448 return $this->getMessageFromDB( self
::$mWeekdayAbbrevMsgs[$key-1] );
451 function getIranianCalendarMonthName( $key ) {
452 return $this->getMessageFromDB( self
::$mIranianCalendarMonthMsgs[$key-1] );
455 function getHebrewCalendarMonthName( $key ) {
456 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthMsgs[$key-1] );
459 function getHebrewCalendarMonthNameGen( $key ) {
460 return $this->getMessageFromDB( self
::$mHebrewCalendarMonthGenMsgs[$key-1] );
463 function getHijriCalendarMonthName( $key ) {
464 return $this->getMessageFromDB( self
::$mHijriCalendarMonthMsgs[$key-1] );
468 * Used by date() and time() to adjust the time output.
470 * @param $ts Int the time in date('YmdHis') format
471 * @param $tz Mixed: adjust the time by this amount (default false, mean we
472 * get user timecorrection setting)
475 function userAdjust( $ts, $tz = false ) {
476 global $wgUser, $wgLocalTZoffset;
478 if ( $tz === false ) {
479 $tz = $wgUser->getOption( 'timecorrection' );
482 $data = explode( '|', $tz, 3 );
484 if ( $data[0] == 'ZoneInfo' ) {
485 if ( function_exists( 'timezone_open' ) && @timezone_open
( $data[2] ) !== false ) {
486 $date = date_create( $ts, timezone_open( 'UTC' ) );
487 date_timezone_set( $date, timezone_open( $data[2] ) );
488 $date = date_format( $date, 'YmdHis' );
491 # Unrecognized timezone, default to 'Offset' with the stored offset.
496 if ( $data[0] == 'System' ||
$tz == '' ) {
497 # Global offset in minutes.
498 if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset;
499 } else if ( $data[0] == 'Offset' ) {
500 $minDiff = intval( $data[1] );
502 $data = explode( ':', $tz );
503 if( count( $data ) == 2 ) {
504 $data[0] = intval( $data[0] );
505 $data[1] = intval( $data[1] );
506 $minDiff = abs( $data[0] ) * 60 +
$data[1];
507 if ( $data[0] < 0 ) $minDiff = -$minDiff;
509 $minDiff = intval( $data[0] ) * 60;
513 # No difference ? Return time unchanged
514 if ( 0 == $minDiff ) return $ts;
516 wfSuppressWarnings(); // E_STRICT system time bitching
517 # Generate an adjusted date; take advantage of the fact that mktime
518 # will normalize out-of-range values so we don't have to split $minDiff
519 # into hours and minutes.
521 (int)substr( $ts, 8, 2) ), # Hours
522 (int)substr( $ts, 10, 2 ) +
$minDiff, # Minutes
523 (int)substr( $ts, 12, 2 ), # Seconds
524 (int)substr( $ts, 4, 2 ), # Month
525 (int)substr( $ts, 6, 2 ), # Day
526 (int)substr( $ts, 0, 4 ) ); #Year
528 $date = date( 'YmdHis', $t );
535 * This is a workalike of PHP's date() function, but with better
536 * internationalisation, a reduced set of format characters, and a better
539 * Supported format characters are dDjlNwzWFmMntLoYyaAgGhHiscrU. See the
540 * PHP manual for definitions. "o" format character is supported since
541 * PHP 5.1.0, previous versions return literal o.
542 * There are a number of extensions, which start with "x":
544 * xn Do not translate digits of the next numeric format character
545 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
546 * xr Use roman numerals for the next numeric format character
547 * xh Use hebrew numerals for the next numeric format character
549 * xg Genitive month name
551 * xij j (day number) in Iranian calendar
552 * xiF F (month name) in Iranian calendar
553 * xin n (month number) in Iranian calendar
554 * xiY Y (full year) in Iranian calendar
556 * xjj j (day number) in Hebrew calendar
557 * xjF F (month name) in Hebrew calendar
558 * xjt t (days in month) in Hebrew calendar
559 * xjx xg (genitive month name) in Hebrew calendar
560 * xjn n (month number) in Hebrew calendar
561 * xjY Y (full year) in Hebrew calendar
563 * xmj j (day number) in Hijri calendar
564 * xmF F (month name) in Hijri calendar
565 * xmn n (month number) in Hijri calendar
566 * xmY Y (full year) in Hijri calendar
568 * xkY Y (full year) in Thai solar calendar. Months and days are
569 * identical to the Gregorian calendar
570 * xoY Y (full year) in Minguo calendar or Juche year.
571 * Months and days are identical to the
573 * xtY Y (full year) in Japanese nengo. Months and days are
574 * identical to the Gregorian calendar
576 * Characters enclosed in double quotes will be considered literal (with
577 * the quotes themselves removed). Unmatched quotes will be considered
578 * literal quotes. Example:
580 * "The month is" F => The month is January
583 * Backslash escaping is also supported.
585 * Input timestamp is assumed to be pre-normalized to the desired local
588 * @param $format String
589 * @param $ts String: 14-character timestamp
592 * @todo emulation of "o" format character for PHP pre 5.1.0
593 * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
595 function sprintfDate( $format, $ts ) {
608 for ( $p = 0; $p < strlen( $format ); $p++
) {
611 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
612 $code .= $format[++
$p];
615 if ( ( $code === 'xi' ||
$code == 'xj' ||
$code == 'xk' ||
$code == 'xm' ||
$code == 'xo' ||
$code == 'xt' ) && $p < strlen( $format ) - 1 ) {
616 $code .= $format[++
$p];
627 $rawToggle = !$rawToggle;
636 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
639 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
640 $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
643 $num = substr( $ts, 6, 2 );
646 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
647 $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) +
1 );
650 $num = intval( substr( $ts, 6, 2 ) );
653 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
657 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
661 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
665 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
666 $s .= $this->getWeekdayName( gmdate( 'w', $unix ) +
1 );
669 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
670 $w = gmdate( 'w', $unix );
674 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
675 $num = gmdate( 'w', $unix );
678 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
679 $num = gmdate( 'z', $unix );
682 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
683 $num = gmdate( 'W', $unix );
686 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
689 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
690 $s .= $this->getIranianCalendarMonthName( $iranian[1] );
693 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
694 $s .= $this->getHijriCalendarMonthName( $hijri[1] );
697 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
698 $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
701 $num = substr( $ts, 4, 2 );
704 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
707 $num = intval( substr( $ts, 4, 2 ) );
710 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
714 if ( !$hijri ) $hijri = self
::tsToHijri ( $ts );
718 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
722 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
723 $num = gmdate( 't', $unix );
726 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
730 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
731 $num = gmdate( 'L', $unix );
733 # 'o' is supported since PHP 5.1.0
734 # return literal if not supported
735 # TODO: emulation for pre 5.1.0 versions
737 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
738 if ( version_compare(PHP_VERSION
, '5.1.0') === 1 )
739 $num = date( 'o', $unix );
744 $num = substr( $ts, 0, 4 );
747 if ( !$iranian ) $iranian = self
::tsToIranian( $ts );
751 if ( !$hijri ) $hijri = self
::tsToHijri( $ts );
755 if ( !$hebrew ) $hebrew = self
::tsToHebrew( $ts );
759 if ( !$thai ) $thai = self
::tsToYear( $ts, 'thai' );
763 if ( !$minguo ) $minguo = self
::tsToYear( $ts, 'minguo' );
767 if ( !$tenno ) $tenno = self
::tsToYear( $ts, 'tenno' );
771 $num = substr( $ts, 2, 2 );
774 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'am' : 'pm';
777 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ?
'AM' : 'PM';
780 $h = substr( $ts, 8, 2 );
781 $num = $h %
12 ?
$h %
12 : 12;
784 $num = intval( substr( $ts, 8, 2 ) );
787 $h = substr( $ts, 8, 2 );
788 $num = sprintf( '%02d', $h %
12 ?
$h %
12 : 12 );
791 $num = substr( $ts, 8, 2 );
794 $num = substr( $ts, 10, 2 );
797 $num = substr( $ts, 12, 2 );
800 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
801 $s .= gmdate( 'c', $unix );
804 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
805 $s .= gmdate( 'r', $unix );
808 if ( !$unix ) $unix = wfTimestamp( TS_UNIX
, $ts );
813 if ( $p < strlen( $format ) - 1 ) {
821 if ( $p < strlen( $format ) - 1 ) {
822 $endQuote = strpos( $format, '"', $p +
1 );
823 if ( $endQuote === false ) {
824 # No terminating quote, assume literal "
827 $s .= substr( $format, $p +
1, $endQuote - $p - 1 );
831 # Quote at end of string, assume literal "
838 if ( $num !== false ) {
839 if ( $rawToggle ||
$raw ) {
842 } elseif ( $roman ) {
843 $s .= self
::romanNumeral( $num );
845 } elseif( $hebrewNum ) {
846 $s .= self
::hebrewNumeral( $num );
849 $s .= $this->formatNum( $num, true );
857 private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
858 private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
860 * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
861 * Gregorian dates to Iranian dates. Originally written in C, it
862 * is released under the terms of GNU Lesser General Public
863 * License. Conversion to PHP was performed by Niklas Laxström.
865 * Link: http://www.farsiweb.info/jalali/jalali.c
867 private static function tsToIranian( $ts ) {
868 $gy = substr( $ts, 0, 4 ) -1600;
869 $gm = substr( $ts, 4, 2 ) -1;
870 $gd = substr( $ts, 6, 2 ) -1;
872 # Days passed from the beginning (including leap years)
875 - floor(($gy+
99) / 100)
876 +
floor(($gy+
399) / 400);
879 // Add days of the past months of this year
880 for( $i = 0; $i < $gm; $i++
) {
881 $gDayNo +
= self
::$GREG_DAYS[$i];
885 if ( $gm > 1 && (($gy%4
===0 && $gy%100
!==0 ||
($gy%400
==0)))) {
889 // Days passed in current month
892 $jDayNo = $gDayNo - 79;
894 $jNp = floor($jDayNo / 12053);
897 $jy = 979 +
33*$jNp +
4*floor($jDayNo/1461);
900 if ( $jDayNo >= 366 ) {
901 $jy +
= floor(($jDayNo-1)/365);
902 $jDayNo = floor(($jDayNo-1)%365
);
905 for ( $i = 0; $i < 11 && $jDayNo >= self
::$IRANIAN_DAYS[$i]; $i++
) {
906 $jDayNo -= self
::$IRANIAN_DAYS[$i];
912 return array($jy, $jm, $jd);
915 * Converting Gregorian dates to Hijri dates.
917 * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license
919 * @link http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0
921 private static function tsToHijri ( $ts ) {
922 $year = substr( $ts, 0, 4 );
923 $month = substr( $ts, 4, 2 );
924 $day = substr( $ts, 6, 2 );
933 if (($zy>1582)||
(($zy==1582)&&($zm>10))||
(($zy==1582)&&($zm==10)&&($zd>14)))
937 $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;
941 $zjd = 367*$zy-(int)((7*($zy+
5001+
(int)(($zm-9)/7)))/4)+
(int)((275*$zm)/9)+
$zd+
1729777;
944 $zl=$zjd-1948440+
10632;
945 $zn=(int)(($zl-1)/10631);
946 $zl=$zl-10631*$zn+
354;
947 $zj=((int)((10985-$zl)/5316))*((int)((50*$zl)/17719))+
((int)($zl/5670))*((int)((43*$zl)/15238));
948 $zl=$zl-((int)((30-$zj)/15))*((int)((17719*$zj)/50))-((int)($zj/16))*((int)((15238*$zj)/43))+
29;
949 $zm=(int)((24*$zl)/709);
950 $zd=$zl-(int)((709*$zm)/24);
953 return array ($zy, $zm, $zd);
957 * Converting Gregorian dates to Hebrew dates.
959 * Based on a JavaScript code by Abu Mami and Yisrael Hersch
960 * (abu-mami@kaluach.net, http://www.kaluach.net), who permitted
961 * to translate the relevant functions into PHP and release them under
964 * The months are counted from Tishrei = 1. In a leap year, Adar I is 13
965 * and Adar II is 14. In a non-leap year, Adar is 6.
967 private static function tsToHebrew( $ts ) {
969 $year = substr( $ts, 0, 4 );
970 $month = substr( $ts, 4, 2 );
971 $day = substr( $ts, 6, 2 );
973 # Calculate Hebrew year
974 $hebrewYear = $year +
3760;
976 # Month number when September = 1, August = 12
985 # Calculate day of year from 1 September
987 for( $i = 1; $i < $month; $i++
) {
991 # Check if the year is leap
992 if( $year %
400 == 0 ||
( $year %
4 == 0 && $year %
100 > 0 ) ) {
995 } elseif( $i == 8 ||
$i == 10 ||
$i == 1 ||
$i == 3 ) {
1002 # Calculate the start of the Hebrew year
1003 $start = self
::hebrewYearStart( $hebrewYear );
1005 # Calculate next year's start
1006 if( $dayOfYear <= $start ) {
1007 # Day is before the start of the year - it is the previous year
1009 $nextStart = $start;
1013 # Add days since previous year's 1 September
1015 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1019 # Start of the new (previous) year
1020 $start = self
::hebrewYearStart( $hebrewYear );
1023 $nextStart = self
::hebrewYearStart( $hebrewYear +
1 );
1026 # Calculate Hebrew day of year
1027 $hebrewDayOfYear = $dayOfYear - $start;
1029 # Difference between year's days
1030 $diff = $nextStart - $start;
1031 # Add 12 (or 13 for leap years) days to ignore the difference between
1032 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1033 # difference is only about the year type
1034 if( ( $year %
400 == 0 ) ||
( $year %
100 != 0 && $year %
4 == 0 ) ) {
1040 # Check the year pattern, and is leap year
1041 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1042 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1043 # and non-leap years
1044 $yearPattern = $diff %
30;
1045 # Check if leap year
1046 $isLeap = $diff >= 30;
1048 # Calculate day in the month from number of day in the Hebrew year
1049 # Don't check Adar - if the day is not in Adar, we will stop before;
1050 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1051 $hebrewDay = $hebrewDayOfYear;
1054 while( $hebrewMonth <= 12 ) {
1055 # Calculate days in this month
1056 if( $isLeap && $hebrewMonth == 6 ) {
1057 # Adar in a leap year
1059 # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1061 if( $hebrewDay <= $days ) {
1065 # Subtract the days of Adar I
1066 $hebrewDay -= $days;
1069 if( $hebrewDay <= $days ) {
1075 } elseif( $hebrewMonth == 2 && $yearPattern == 2 ) {
1076 # Cheshvan in a complete year (otherwise as the rule below)
1078 } elseif( $hebrewMonth == 3 && $yearPattern == 0 ) {
1079 # Kislev in an incomplete year (otherwise as the rule below)
1082 # Odd months have 30 days, even have 29
1083 $days = 30 - ( $hebrewMonth - 1 ) %
2;
1085 if( $hebrewDay <= $days ) {
1086 # In the current month
1089 # Subtract the days of the current month
1090 $hebrewDay -= $days;
1091 # Try in the next month
1096 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
1100 * This calculates the Hebrew year start, as days since 1 September.
1101 * Based on Carl Friedrich Gauss algorithm for finding Easter date.
1102 * Used for Hebrew date.
1104 private static function hebrewYearStart( $year ) {
1105 $a = intval( ( 12 * ( $year - 1 ) +
17 ) %
19 );
1106 $b = intval( ( $year - 1 ) %
4 );
1107 $m = 32.044093161144 +
1.5542417966212 * $a +
$b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1111 $Mar = intval( $m );
1117 $c = intval( ( $Mar +
3 * ( $year - 1 ) +
5 * $b +
5 ) %
7);
1118 if( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1120 } else if( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1122 } else if( $c == 2 ||
$c == 4 ||
$c == 6 ) {
1126 $Mar +
= intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1131 * Algorithm to convert Gregorian dates to Thai solar dates,
1132 * Minguo dates or Minguo dates.
1134 * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
1135 * http://en.wikipedia.org/wiki/Minguo_calendar
1136 * http://en.wikipedia.org/wiki/Japanese_era_name
1138 * @param $ts String: 14-character timestamp, calender name
1139 * @return array converted year, month, day
1141 private static function tsToYear( $ts, $cName ) {
1142 $gy = substr( $ts, 0, 4 );
1143 $gm = substr( $ts, 4, 2 );
1144 $gd = substr( $ts, 6, 2 );
1146 if (!strcmp($cName,'thai')) {
1148 # Add 543 years to the Gregorian calendar
1149 # Months and days are identical
1150 $gy_offset = $gy +
543;
1151 } else if ((!strcmp($cName,'minguo')) ||
!strcmp($cName,'juche')) {
1153 # Deduct 1911 years from the Gregorian calendar
1154 # Months and days are identical
1155 $gy_offset = $gy - 1911;
1156 } else if (!strcmp($cName,'tenno')) {
1157 # Nengō dates up to Meiji period
1158 # Deduct years from the Gregorian calendar
1159 # depending on the nengo periods
1160 # Months and days are identical
1161 if (($gy < 1912) ||
(($gy == 1912) && ($gm < 7)) ||
(($gy == 1912) && ($gm == 7) && ($gd < 31))) {
1163 $gy_gannen = $gy - 1868 +
1;
1164 $gy_offset = $gy_gannen;
1165 if ($gy_gannen == 1)
1167 $gy_offset = '明治'.$gy_offset;
1168 } else if ((($gy == 1912) && ($gm == 7) && ($gd == 31)) ||
(($gy == 1912) && ($gm >= 8)) ||
(($gy > 1912) && ($gy < 1926)) ||
(($gy == 1926) && ($gm < 12)) ||
(($gy == 1926) && ($gm == 12) && ($gd < 26))) {
1170 $gy_gannen = $gy - 1912 +
1;
1171 $gy_offset = $gy_gannen;
1172 if ($gy_gannen == 1)
1174 $gy_offset = '大正'.$gy_offset;
1175 } else if ((($gy == 1926) && ($gm == 12) && ($gd >= 26)) ||
(($gy > 1926) && ($gy < 1989)) ||
(($gy == 1989) && ($gm == 1) && ($gd < 8))) {
1177 $gy_gannen = $gy - 1926 +
1;
1178 $gy_offset = $gy_gannen;
1179 if ($gy_gannen == 1)
1181 $gy_offset = '昭和'.$gy_offset;
1184 $gy_gannen = $gy - 1989 +
1;
1185 $gy_offset = $gy_gannen;
1186 if ($gy_gannen == 1)
1188 $gy_offset = '平成'.$gy_offset;
1194 return array( $gy_offset, $gm, $gd );
1198 * Roman number formatting up to 3000
1200 static function romanNumeral( $num ) {
1201 static $table = array(
1202 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1203 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1204 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1205 array( '', 'M', 'MM', 'MMM' )
1208 $num = intval( $num );
1209 if ( $num > 3000 ||
$num <= 0 ) {
1214 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1215 if ( $num >= $pow10 ) {
1216 $s .= $table[$i][floor($num / $pow10)];
1218 $num = $num %
$pow10;
1224 * Hebrew Gematria number formatting up to 9999
1226 static function hebrewNumeral( $num ) {
1227 static $table = array(
1228 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1229 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1230 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1231 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1234 $num = intval( $num );
1235 if ( $num > 9999 ||
$num <= 0 ) {
1240 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1241 if ( $num >= $pow10 ) {
1242 if ( $num == 15 ||
$num == 16 ) {
1243 $s .= $table[0][9] . $table[0][$num - 9];
1246 $s .= $table[$i][intval( ( $num / $pow10 ) )];
1247 if( $pow10 == 1000 ) {
1252 $num = $num %
$pow10;
1254 if( strlen( $s ) == 2 ) {
1257 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1258 $str .= substr( $s, strlen( $s ) - 2, 2 );
1260 $start = substr( $str, 0, strlen( $str ) - 2 );
1261 $end = substr( $str, strlen( $str ) - 2 );
1264 $str = $start . 'ך';
1267 $str = $start . 'ם';
1270 $str = $start . 'ן';
1273 $str = $start . 'ף';
1276 $str = $start . 'ץ';
1283 * This is meant to be used by time(), date(), and timeanddate() to get
1284 * the date preference they're supposed to use, it should be used in
1288 * function timeanddate([...], $format = true) {
1289 * $datePreference = $this->dateFormat($format);
1294 * @param $usePrefs Mixed: if true, the user's preference is used
1295 * if false, the site/language default is used
1296 * if int/string, assumed to be a format.
1299 function dateFormat( $usePrefs = true ) {
1302 if( is_bool( $usePrefs ) ) {
1304 $datePreference = $wgUser->getDatePreference();
1306 $options = User
::getDefaultOptions();
1307 $datePreference = (string)$options['date'];
1310 $datePreference = (string)$usePrefs;
1314 if( $datePreference == '' ) {
1318 return $datePreference;
1322 * @param $ts Mixed: the time format which needs to be turned into a
1323 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1324 * @param $adj Bool: whether to adjust the time output according to the
1325 * user configured offset ($timecorrection)
1326 * @param $format Mixed: true to use user's date format preference
1327 * @param $timecorrection String: the time offset as returned by
1328 * validateTimeZone() in Special:Preferences
1331 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
1334 $ts = $this->userAdjust( $ts, $timecorrection );
1337 $pref = $this->dateFormat( $format );
1338 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref date"] ) ) {
1339 $pref = $this->defaultDateFormat
;
1341 return $this->sprintfDate( $this->dateFormats
["$pref date"], $ts );
1345 * @param $ts Mixed: the time format which needs to be turned into a
1346 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1347 * @param $adj Bool: whether to adjust the time output according to the
1348 * user configured offset ($timecorrection)
1349 * @param $format Mixed: true to use user's date format preference
1350 * @param $timecorrection String: the time offset as returned by
1351 * validateTimeZone() in Special:Preferences
1354 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
1357 $ts = $this->userAdjust( $ts, $timecorrection );
1360 $pref = $this->dateFormat( $format );
1361 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref time"] ) ) {
1362 $pref = $this->defaultDateFormat
;
1364 return $this->sprintfDate( $this->dateFormats
["$pref time"], $ts );
1368 * @param $ts Mixed: the time format which needs to be turned into a
1369 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1370 * @param $adj Bool: whether to adjust the time output according to the
1371 * user configured offset ($timecorrection)
1372 * @param $format Mixed: what format to return, if it's false output the
1373 * default one (default true)
1374 * @param $timecorrection String: the time offset as returned by
1375 * validateTimeZone() in Special:Preferences
1378 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
1381 $ts = wfTimestamp( TS_MW
, $ts );
1384 $ts = $this->userAdjust( $ts, $timecorrection );
1387 $pref = $this->dateFormat( $format );
1388 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref both"] ) ) {
1389 $pref = $this->defaultDateFormat
;
1392 return $this->sprintfDate( $this->dateFormats
["$pref both"], $ts );
1395 function getMessage( $key ) {
1397 return isset( $this->messages
[$key] ) ?
$this->messages
[$key] : null;
1400 function getAllMessages() {
1402 return $this->messages
;
1405 function iconv( $in, $out, $string ) {
1406 # For most languages, this is a wrapper for iconv
1407 return iconv( $in, $out . '//IGNORE', $string );
1410 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
1411 function ucwordbreaksCallbackAscii($matches){
1412 return $this->ucfirst($matches[1]);
1415 function ucwordbreaksCallbackMB($matches){
1416 return mb_strtoupper($matches[0]);
1419 function ucCallback($matches){
1420 list( $wikiUpperChars ) = self
::getCaseMaps();
1421 return strtr( $matches[1], $wikiUpperChars );
1424 function lcCallback($matches){
1425 list( , $wikiLowerChars ) = self
::getCaseMaps();
1426 return strtr( $matches[1], $wikiLowerChars );
1429 function ucwordsCallbackMB($matches){
1430 return mb_strtoupper($matches[0]);
1433 function ucwordsCallbackWiki($matches){
1434 list( $wikiUpperChars ) = self
::getCaseMaps();
1435 return strtr( $matches[0], $wikiUpperChars );
1438 function ucfirst( $str ) {
1439 if ( empty($str) ) return $str;
1440 if ( ord($str[0]) < 128 ) return ucfirst($str);
1441 else return self
::uc($str,true); // fall back to more complex logic in case of multibyte strings
1444 function uc( $str, $first = false ) {
1445 if ( function_exists( 'mb_strtoupper' ) ) {
1447 if ( self
::isMultibyte( $str ) ) {
1448 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1450 return ucfirst( $str );
1453 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
1456 if ( self
::isMultibyte( $str ) ) {
1457 list( $wikiUpperChars ) = $this->getCaseMaps();
1458 $x = $first ?
'^' : '';
1459 return preg_replace_callback(
1460 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1461 array($this,"ucCallback"),
1465 return $first ?
ucfirst( $str ) : strtoupper( $str );
1470 function lcfirst( $str ) {
1471 if ( empty($str) ) return $str;
1472 if ( is_string( $str ) && ord($str[0]) < 128 ) {
1473 // editing string in place = cool
1474 $str[0]=strtolower($str[0]);
1477 else return self
::lc( $str, true );
1480 function lc( $str, $first = false ) {
1481 if ( function_exists( 'mb_strtolower' ) )
1483 if ( self
::isMultibyte( $str ) )
1484 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1486 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
1488 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
1490 if ( self
::isMultibyte( $str ) ) {
1491 list( , $wikiLowerChars ) = self
::getCaseMaps();
1492 $x = $first ?
'^' : '';
1493 return preg_replace_callback(
1494 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1495 array($this,"lcCallback"),
1499 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
1502 function isMultibyte( $str ) {
1503 return (bool)preg_match( '/[\x80-\xff]/', $str );
1506 function ucwords($str) {
1507 if ( self
::isMultibyte( $str ) ) {
1508 $str = self
::lc($str);
1510 // regexp to find first letter in each word (i.e. after each space)
1511 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1513 // function to use to capitalize a single char
1514 if ( function_exists( 'mb_strtoupper' ) )
1515 return preg_replace_callback(
1517 array($this,"ucwordsCallbackMB"),
1521 return preg_replace_callback(
1523 array($this,"ucwordsCallbackWiki"),
1528 return ucwords( strtolower( $str ) );
1531 # capitalize words at word breaks
1532 function ucwordbreaks($str){
1533 if (self
::isMultibyte( $str ) ) {
1534 $str = self
::lc($str);
1536 // since \b doesn't work for UTF-8, we explicitely define word break chars
1537 $breaks= "[ \-\(\)\}\{\.,\?!]";
1539 // find first letter after word break
1540 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1542 if ( function_exists( 'mb_strtoupper' ) )
1543 return preg_replace_callback(
1545 array($this,"ucwordbreaksCallbackMB"),
1549 return preg_replace_callback(
1551 array($this,"ucwordsCallbackWiki"),
1556 return preg_replace_callback(
1557 '/\b([\w\x80-\xff]+)\b/',
1558 array($this,"ucwordbreaksCallbackAscii"),
1563 * Return a case-folded representation of $s
1565 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1566 * and $s2 are the same except for the case of their characters. It is not
1567 * necessary for the value returned to make sense when displayed.
1569 * Do *not* perform any other normalisation in this function. If a caller
1570 * uses this function when it should be using a more general normalisation
1571 * function, then fix the caller.
1573 function caseFold( $s ) {
1574 return $this->uc( $s );
1577 function checkTitleEncoding( $s ) {
1578 if( is_array( $s ) ) {
1579 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1581 # Check for non-UTF-8 URLs
1582 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1583 if(!$ishigh) return $s;
1585 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1586 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1587 if( $isutf8 ) return $s;
1589 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1592 function fallback8bitEncoding() {
1594 return $this->fallback8bitEncoding
;
1598 * Most writing systems use whitespace to break up words.
1599 * Some languages such as Chinese don't conventionally do this,
1600 * which requires special handling when breaking up words for
1603 function hasWordBreaks() {
1608 * Some languages have special punctuation to strip out
1609 * or characters which need to be converted for MySQL's
1610 * indexing to grok it correctly. Make such changes here.
1612 * @param $string String
1615 function stripForSearch( $string ) {
1617 if ( $wgDBtype != 'mysql' ) {
1622 wfProfileIn( __METHOD__
);
1624 // MySQL fulltext index doesn't grok utf-8, so we
1625 // need to fold cases and convert to hex
1626 $out = preg_replace_callback(
1627 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
1628 array( $this, 'stripForSearchCallback' ),
1629 $this->lc( $string ) );
1631 // And to add insult to injury, the default indexing
1632 // ignores short words... Pad them so we can pass them
1633 // through without reconfiguring the server...
1634 $minLength = $this->minSearchLength();
1635 if( $minLength > 1 ) {
1637 $out = preg_replace(
1643 // Periods within things like hostnames and IP addresses
1644 // are also important -- we want a search for "example.com"
1645 // or "192.168.1.1" to work sanely.
1647 // MySQL's search seems to ignore them, so you'd match on
1648 // "example.wikipedia.com" and "192.168.83.1" as well.
1649 $out = preg_replace(
1654 wfProfileOut( __METHOD__
);
1659 * Armor a case-folded UTF-8 string to get through MySQL's
1660 * fulltext search without being mucked up by funny charset
1661 * settings or anything else of the sort.
1663 protected function stripForSearchCallback( $matches ) {
1664 return 'u8' . bin2hex( $matches[1] );
1668 * Check MySQL server's ft_min_word_len setting so we know
1669 * if we need to pad short words...
1671 protected function minSearchLength() {
1672 if( !isset( $this->minSearchLength
) ) {
1673 $sql = "show global variables like 'ft\\_min\\_word\\_len'";
1674 $dbr = wfGetDB( DB_SLAVE
);
1675 $result = $dbr->query( $sql );
1676 $row = $result->fetchObject();
1679 if( $row && $row->Variable_name
== 'ft_min_word_len' ) {
1680 $this->minSearchLength
= intval( $row->Value
);
1682 $this->minSearchLength
= 0;
1685 return $this->minSearchLength
;
1688 function convertForSearchResult( $termsArray ) {
1689 # some languages, e.g. Chinese, need to do a conversion
1690 # in order for search results to be displayed correctly
1695 * Get the first character of a string.
1700 function firstChar( $s ) {
1702 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1703 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1705 if ( isset( $matches[1] ) ) {
1706 if ( strlen( $matches[1] ) != 3 ) {
1710 // Break down Hangul syllables to grab the first jamo
1711 $code = utf8ToCodepoint( $matches[1] );
1712 if ( $code < 0xac00 ||
0xd7a4 <= $code) {
1714 } elseif ( $code < 0xb098 ) {
1715 return "\xe3\x84\xb1";
1716 } elseif ( $code < 0xb2e4 ) {
1717 return "\xe3\x84\xb4";
1718 } elseif ( $code < 0xb77c ) {
1719 return "\xe3\x84\xb7";
1720 } elseif ( $code < 0xb9c8 ) {
1721 return "\xe3\x84\xb9";
1722 } elseif ( $code < 0xbc14 ) {
1723 return "\xe3\x85\x81";
1724 } elseif ( $code < 0xc0ac ) {
1725 return "\xe3\x85\x82";
1726 } elseif ( $code < 0xc544 ) {
1727 return "\xe3\x85\x85";
1728 } elseif ( $code < 0xc790 ) {
1729 return "\xe3\x85\x87";
1730 } elseif ( $code < 0xcc28 ) {
1731 return "\xe3\x85\x88";
1732 } elseif ( $code < 0xce74 ) {
1733 return "\xe3\x85\x8a";
1734 } elseif ( $code < 0xd0c0 ) {
1735 return "\xe3\x85\x8b";
1736 } elseif ( $code < 0xd30c ) {
1737 return "\xe3\x85\x8c";
1738 } elseif ( $code < 0xd558 ) {
1739 return "\xe3\x85\x8d";
1741 return "\xe3\x85\x8e";
1748 function initEncoding() {
1749 # Some languages may have an alternate char encoding option
1750 # (Esperanto X-coding, Japanese furigana conversion, etc)
1751 # If this language is used as the primary content language,
1752 # an override to the defaults can be set here on startup.
1755 function recodeForEdit( $s ) {
1756 # For some languages we'll want to explicitly specify
1757 # which characters make it into the edit box raw
1758 # or are converted in some way or another.
1759 # Note that if wgOutputEncoding is different from
1760 # wgInputEncoding, this text will be further converted
1761 # to wgOutputEncoding.
1762 global $wgEditEncoding;
1763 if( $wgEditEncoding == '' or
1764 $wgEditEncoding == 'UTF-8' ) {
1767 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1771 function recodeInput( $s ) {
1772 # Take the previous into account.
1773 global $wgEditEncoding;
1774 if($wgEditEncoding != "") {
1775 $enc = $wgEditEncoding;
1779 if( $enc == 'UTF-8' ) {
1782 return $this->iconv( $enc, 'UTF-8', $s );
1787 * For right-to-left language support
1797 * A hidden direction mark (LRM or RLM), depending on the language direction
1801 function getDirMark() {
1802 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1805 function capitalizeAllNouns() {
1807 return $this->capitalizeAllNouns
;
1811 * An arrow, depending on the language direction
1815 function getArrow() {
1816 return $this->isRTL() ?
'←' : '→';
1820 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1824 function linkPrefixExtension() {
1826 return $this->linkPrefixExtension
;
1829 function &getMagicWords() {
1831 return $this->magicWords
;
1834 # Fill a MagicWord object with data from here
1835 function getMagic( &$mw ) {
1836 if ( !$this->mMagicHookDone
) {
1837 $this->mMagicHookDone
= true;
1838 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1840 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1841 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1843 $magicWords =& $this->getMagicWords();
1844 if ( isset( $magicWords[$mw->mId
] ) ) {
1845 $rawEntry = $magicWords[$mw->mId
];
1847 # Fall back to English if local list is incomplete
1848 $magicWords =& Language
::getMagicWords();
1849 if ( !isset($magicWords[$mw->mId
]) ) {
1850 throw new MWException("Magic word '{$mw->mId}' not found" );
1852 $rawEntry = $magicWords[$mw->mId
];
1856 if( !is_array( $rawEntry ) ) {
1857 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1859 $mw->mCaseSensitive
= $rawEntry[0];
1860 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1865 * Add magic words to the extension array
1867 function addMagicWordsByLang( $newWords ) {
1868 $code = $this->getCode();
1869 $fallbackChain = array();
1870 while ( $code && !in_array( $code, $fallbackChain ) ) {
1871 $fallbackChain[] = $code;
1872 $code = self
::getFallbackFor( $code );
1874 if ( !in_array( 'en', $fallbackChain ) ) {
1875 $fallbackChain[] = 'en';
1877 $fallbackChain = array_reverse( $fallbackChain );
1878 foreach ( $fallbackChain as $code ) {
1879 if ( isset( $newWords[$code] ) ) {
1880 $this->mMagicExtensions
= $newWords[$code] +
$this->mMagicExtensions
;
1886 * Get special page names, as an associative array
1887 * case folded alias => real name
1889 function getSpecialPageAliases() {
1892 // Cache aliases because it may be slow to load them
1893 if ( !isset( $this->mExtendedSpecialPageAliases
) ) {
1896 $this->mExtendedSpecialPageAliases
= $this->specialPageAliases
;
1898 global $wgExtensionAliasesFiles;
1899 foreach ( $wgExtensionAliasesFiles as $file ) {
1902 if ( !file_exists($file) )
1903 throw new MWException( "Aliases file does not exist: $file" );
1908 // Check the availability of aliases
1909 if ( !isset($aliases['en']) )
1910 throw new MWException( "Malformed aliases file: $file" );
1912 // Merge all aliases in fallback chain
1913 $code = $this->getCode();
1915 if ( !isset($aliases[$code]) ) continue;
1917 $aliases[$code] = $this->fixSpecialPageAliases( $aliases[$code] );
1918 /* Merge the aliases, THIS will break if there is special page name
1919 * which looks like a numerical key, thanks to PHP...
1920 * See the array_merge_recursive manual entry */
1921 $this->mExtendedSpecialPageAliases
= array_merge_recursive(
1922 $this->mExtendedSpecialPageAliases
, $aliases[$code] );
1924 } while ( $code = self
::getFallbackFor( $code ) );
1927 wfRunHooks( 'LanguageGetSpecialPageAliases',
1928 array( &$this->mExtendedSpecialPageAliases
, $this->getCode() ) );
1931 return $this->mExtendedSpecialPageAliases
;
1935 * Function to fix special page aliases. Will convert the first letter to
1936 * upper case and spaces to underscores. Can be given a full aliases array,
1937 * in which case it will recursively fix all aliases.
1939 public function fixSpecialPageAliases( $mixed ) {
1940 // Work recursively until in string level
1941 if ( is_array($mixed) ) {
1942 $callback = array( $this, 'fixSpecialPageAliases' );
1943 return array_map( $callback, $mixed );
1945 return str_replace( ' ', '_', $this->ucfirst( $mixed ) );
1949 * Italic is unsuitable for some languages
1951 * @param $text String: the text to be emphasized.
1954 function emphasize( $text ) {
1955 return "<em>$text</em>";
1959 * Normally we output all numbers in plain en_US style, that is
1960 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1961 * point twohundredthirtyfive. However this is not sutable for all
1962 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1963 * Icelandic just want to use commas instead of dots, and dots instead
1964 * of commas like "293.291,235".
1966 * An example of this function being called:
1968 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1971 * See LanguageGu.php for the Gujarati implementation and
1972 * $separatorTransformTable on MessageIs.php for
1973 * the , => . and . => , implementation.
1975 * @todo check if it's viable to use localeconv() for the decimal
1977 * @param $number Mixed: the string to be formatted, should be an integer
1978 * or a floating point number.
1979 * @param $nocommafy Bool: set to true for special numbers like dates
1982 function formatNum( $number, $nocommafy = false ) {
1983 global $wgTranslateNumerals;
1985 $number = $this->commafy($number);
1986 $s = $this->separatorTransformTable();
1987 if ($s) { $number = strtr($number, $s); }
1990 if ($wgTranslateNumerals) {
1991 $s = $this->digitTransformTable();
1992 if ($s) { $number = strtr($number, $s); }
1998 function parseFormattedNumber( $number ) {
1999 $s = $this->digitTransformTable();
2000 if ($s) { $number = strtr($number, array_flip($s)); }
2002 $s = $this->separatorTransformTable();
2003 if ($s) { $number = strtr($number, array_flip($s)); }
2005 $number = strtr( $number, array (',' => '') );
2010 * Adds commas to a given number
2015 function commafy($_) {
2016 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
2019 function digitTransformTable() {
2021 return $this->digitTransformTable
;
2024 function separatorTransformTable() {
2026 return $this->separatorTransformTable
;
2031 * Take a list of strings and build a locale-friendly comma-separated
2032 * list, using the local comma-separator message.
2033 * The last two strings are chained with an "and".
2038 function listToText( $l ) {
2040 $m = count( $l ) - 1;
2042 return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
2045 for ( $i = $m; $i >= 0; $i-- ) {
2048 } else if( $i == $m - 1 ) {
2049 $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
2051 $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
2059 * Take a list of strings and build a locale-friendly comma-separated
2060 * list, using the local comma-separator message.
2061 * @param $list array of strings to put in a comma list
2064 function commaList( $list ) {
2067 wfMsgExt( 'comma-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
2071 * Take a list of strings and build a locale-friendly semicolon-separated
2072 * list, using the local semicolon-separator message.
2073 * @param $list array of strings to put in a semicolon list
2076 function semicolonList( $list ) {
2079 wfMsgExt( 'semicolon-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
2083 * Same as commaList, but separate it with the pipe instead.
2084 * @param $list array of strings to put in a pipe list
2087 function pipeList( $list ) {
2090 wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) ) );
2094 * Truncate a string to a specified length in bytes, appending an optional
2095 * string (e.g. for ellipses)
2097 * The database offers limited byte lengths for some columns in the database;
2098 * multi-byte character sets mean we need to ensure that only whole characters
2099 * are included, otherwise broken characters can be passed to the user
2101 * If $length is negative, the string will be truncated from the beginning
2103 * @param $string String to truncate
2104 * @param $length Int: maximum length (excluding ellipses)
2105 * @param $ellipsis String to append to the truncated text
2108 function truncate( $string, $length, $ellipsis = '...' ) {
2109 # Use the localized ellipsis character
2110 if( $ellipsis == '...' ) {
2111 $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
2114 if( $length == 0 ) {
2117 if ( strlen( $string ) <= abs( $length ) ) {
2121 $string = substr( $string, 0, $length );
2122 $char = ord( $string[strlen( $string ) - 1] );
2124 if ($char >= 0xc0) {
2125 # We got the first byte only of a multibyte char; remove it.
2126 $string = substr( $string, 0, -1 );
2127 } elseif( $char >= 0x80 &&
2128 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
2129 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
2130 # We chopped in the middle of a character; remove it
2133 return $string . $ellipsis;
2135 $string = substr( $string, $length );
2136 $char = ord( $string[0] );
2137 if( $char >= 0x80 && $char < 0xc0 ) {
2138 # We chopped in the middle of a character; remove the whole thing
2139 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
2141 return $ellipsis . $string;
2146 * Grammatical transformations, needed for inflected languages
2147 * Invoked by putting {{grammar:case|word}} in a message
2149 * @param $word string
2150 * @param $case string
2153 function convertGrammar( $word, $case ) {
2154 global $wgGrammarForms;
2155 if ( isset($wgGrammarForms[$this->getCode()][$case][$word]) ) {
2156 return $wgGrammarForms[$this->getCode()][$case][$word];
2162 * Provides an alternative text depending on specified gender.
2163 * Usage {{gender:username|masculine|feminine|neutral}}.
2164 * username is optional, in which case the gender of current user is used,
2165 * but only in (some) interface messages; otherwise default gender is used.
2166 * If second or third parameter are not specified, masculine is used.
2167 * These details may be overriden per language.
2169 function gender( $gender, $forms ) {
2170 if ( !count($forms) ) { return ''; }
2171 $forms = $this->preConvertPlural( $forms, 2 );
2172 if ( $gender === 'male' ) return $forms[0];
2173 if ( $gender === 'female' ) return $forms[1];
2174 return isset($forms[2]) ?
$forms[2] : $forms[0];
2178 * Plural form transformations, needed for some languages.
2179 * For example, there are 3 form of plural in Russian and Polish,
2180 * depending on "count mod 10". See [[w:Plural]]
2181 * For English it is pretty simple.
2183 * Invoked by putting {{plural:count|wordform1|wordform2}}
2184 * or {{plural:count|wordform1|wordform2|wordform3}}
2186 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
2188 * @param $count Integer: non-localized number
2189 * @param $forms Array: different plural forms
2190 * @return string Correct form of plural for $count in this language
2192 function convertPlural( $count, $forms ) {
2193 if ( !count($forms) ) { return ''; }
2194 $forms = $this->preConvertPlural( $forms, 2 );
2196 return ( $count == 1 ) ?
$forms[0] : $forms[1];
2200 * Checks that convertPlural was given an array and pads it to requested
2201 * amound of forms by copying the last one.
2203 * @param $count Integer: How many forms should there be at least
2204 * @param $forms Array of forms given to convertPlural
2205 * @return array Padded array of forms or an exception if not an array
2207 protected function preConvertPlural( /* Array */ $forms, $count ) {
2208 while ( count($forms) < $count ) {
2209 $forms[] = $forms[count($forms)-1];
2215 * For translaing of expiry times
2216 * @param $str String: the validated block time in English
2217 * @return Somehow translated block time
2218 * @see LanguageFi.php for example implementation
2220 function translateBlockExpiry( $str ) {
2222 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
2224 if ( $scBlockExpiryOptions == '-') {
2228 foreach (explode(',', $scBlockExpiryOptions) as $option) {
2229 if ( strpos($option, ":") === false )
2231 list($show, $value) = explode(":", $option);
2232 if ( strcmp ( $str, $value) == 0 ) {
2233 return htmlspecialchars( trim( $show ) );
2241 * languages like Chinese need to be segmented in order for the diff
2244 * @param $text String
2247 function segmentForDiff( $text ) {
2252 * and unsegment to show the result
2254 * @param $text String
2257 function unsegmentForDiff( $text ) {
2261 # convert text to all supported variants
2262 function autoConvertToAllVariants($text) {
2263 return $this->mConverter
->autoConvertToAllVariants($text);
2266 # convert text to different variants of a language.
2267 function convert( $text, $isTitle = false) {
2268 return $this->mConverter
->convert($text, $isTitle);
2271 # Convert text from within Parser
2272 function parserConvert( $text, &$parser ) {
2273 return $this->mConverter
->parserConvert( $text, $parser );
2276 # Check if this is a language with variants
2277 function hasVariants(){
2278 return sizeof($this->getVariants())>1;
2281 # Put custom tags (e.g. -{ }-) around math to prevent conversion
2282 function armourMath($text){
2283 return $this->mConverter
->armourMath($text);
2288 * Perform output conversion on a string, and encode for safe HTML output.
2289 * @param $text String
2290 * @param $isTitle Bool -- wtf?
2292 * @todo this should get integrated somewhere sane
2294 function convertHtml( $text, $isTitle = false ) {
2295 return htmlspecialchars( $this->convert( $text, $isTitle ) );
2298 function convertCategoryKey( $key ) {
2299 return $this->mConverter
->convertCategoryKey( $key );
2303 * get the list of variants supported by this langauge
2304 * see sample implementation in LanguageZh.php
2306 * @return array an array of language codes
2308 function getVariants() {
2309 return $this->mConverter
->getVariants();
2313 function getPreferredVariant( $fromUser = true ) {
2314 return $this->mConverter
->getPreferredVariant( $fromUser );
2318 * if a language supports multiple variants, it is
2319 * possible that non-existing link in one variant
2320 * actually exists in another variant. this function
2321 * tries to find it. See e.g. LanguageZh.php
2323 * @param $link String: the name of the link
2324 * @param $nt Mixed: the title object of the link
2325 * @param boolean $ignoreOtherCond: to disable other conditions when
2326 * we need to transclude a template or update a category's link
2327 * @return null the input parameters may be modified upon return
2329 function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
2330 $this->mConverter
->findVariantLink( $link, $nt, $ignoreOtherCond );
2334 * If a language supports multiple variants, converts text
2335 * into an array of all possible variants of the text:
2336 * 'variant' => text in that variant
2338 function convertLinkToAllVariants($text){
2339 return $this->mConverter
->convertLinkToAllVariants($text);
2344 * returns language specific options used by User::getPageRenderHash()
2345 * for example, the preferred language variant
2349 function getExtraHashOptions() {
2350 return $this->mConverter
->getExtraHashOptions();
2354 * for languages that support multiple variants, the title of an
2355 * article may be displayed differently in different variants. this
2356 * function returns the apporiate title defined in the body of the article.
2360 function getParsedTitle() {
2361 return $this->mConverter
->getParsedTitle();
2365 * Enclose a string with the "no conversion" tag. This is used by
2366 * various functions in the Parser
2368 * @param $text String: text to be tagged for no conversion
2370 * @return string the tagged text
2372 function markNoConversion( $text, $noParse=false ) {
2373 return $this->mConverter
->markNoConversion( $text, $noParse );
2377 * A regular expression to match legal word-trailing characters
2378 * which should be merged onto a link of the form [[foo]]bar.
2382 function linkTrail() {
2384 return $this->linkTrail
;
2387 function getLangObj() {
2392 * Get the RFC 3066 code for this language object
2394 function getCode() {
2395 return $this->mCode
;
2398 function setCode( $code ) {
2399 $this->mCode
= $code;
2402 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
2403 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
2406 static function getMessagesFileName( $code ) {
2408 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
2411 static function getClassFileName( $code ) {
2413 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
2416 static function getLocalisationArray( $code, $disableCache = false ) {
2417 self
::loadLocalisation( $code, $disableCache );
2418 return self
::$mLocalisationCache[$code];
2422 * Load localisation data for a given code into the static cache
2424 * @return array Dependencies, map of filenames to mtimes
2426 static function loadLocalisation( $code, $disableCache = false ) {
2427 static $recursionGuard = array();
2428 global $wgMemc, $wgEnableSerializedMessages, $wgCheckSerialized;
2431 throw new MWException( "Invalid language code requested" );
2434 if ( !$disableCache ) {
2435 # Try the per-process cache
2436 if ( isset( self
::$mLocalisationCache[$code] ) ) {
2437 return self
::$mLocalisationCache[$code]['deps'];
2440 wfProfileIn( __METHOD__
);
2442 # Try the serialized directory
2443 if( $wgEnableSerializedMessages ) {
2444 $cache = wfGetPrecompiledData( self
::getFileName( "Messages", $code, '.ser' ) );
2446 if ( $wgCheckSerialized && self
::isLocalisationOutOfDate( $cache ) ) {
2448 wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" );
2450 self
::$mLocalisationCache[$code] = $cache;
2451 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
2452 wfProfileOut( __METHOD__
);
2453 return self
::$mLocalisationCache[$code]['deps'];
2460 # Try the global cache
2461 $memcKey = wfMemcKey('localisation', $code );
2462 $fbMemcKey = wfMemcKey('fallback', $cache['fallback'] );
2463 $cache = $wgMemc->get( $memcKey );
2465 if ( self
::isLocalisationOutOfDate( $cache ) ) {
2466 $wgMemc->delete( $memcKey );
2467 $wgMemc->delete( $fbMemcKey );
2469 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired\n" );
2471 self
::$mLocalisationCache[$code] = $cache;
2472 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
2473 wfProfileOut( __METHOD__
);
2474 return $cache['deps'];
2478 wfProfileIn( __METHOD__
);
2481 # Default fallback, may be overridden when the messages file is included
2482 if ( $code != 'en' ) {
2488 # Load the primary localisation from the source file
2489 $filename = self
::getMessagesFileName( $code );
2490 if ( !file_exists( $filename ) ) {
2491 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
2492 $cache = compact( self
::$mLocalisationKeys ); // Set correct fallback
2495 $deps = array( $filename => filemtime( $filename ) );
2496 require( $filename );
2497 $cache = compact( self
::$mLocalisationKeys );
2498 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
2501 # Load magic word source file
2503 $filename = "$IP/includes/MagicWord.php";
2504 $newDeps = array( $filename => filemtime( $filename ) );
2505 $deps = array_merge( $deps, $newDeps );
2507 if ( !empty( $fallback ) ) {
2508 # Load the fallback localisation, with a circular reference guard
2509 if ( isset( $recursionGuard[$code] ) ) {
2510 throw new MWException( "Error: Circular fallback reference in language code $code" );
2512 $recursionGuard[$code] = true;
2513 $newDeps = self
::loadLocalisation( $fallback, $disableCache );
2514 unset( $recursionGuard[$code] );
2516 $secondary = self
::$mLocalisationCache[$fallback];
2517 $deps = array_merge( $deps, $newDeps );
2519 # Merge the fallback localisation with the current localisation
2520 foreach ( self
::$mLocalisationKeys as $key ) {
2521 if ( isset( $cache[$key] ) ) {
2522 if ( isset( $secondary[$key] ) ) {
2523 if ( in_array( $key, self
::$mMergeableMapKeys ) ) {
2524 $cache[$key] = $cache[$key] +
$secondary[$key];
2525 } elseif ( in_array( $key, self
::$mMergeableListKeys ) ) {
2526 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
2527 } elseif ( in_array( $key, self
::$mMergeableAliasListKeys ) ) {
2528 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
2532 $cache[$key] = $secondary[$key];
2536 # Merge bookstore lists if requested
2537 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
2538 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
2540 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
2541 unset( $cache['bookstoreList']['inherit'] );
2545 # Add dependencies to the cache entry
2546 $cache['deps'] = $deps;
2548 # Replace spaces with underscores in namespace names
2549 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
2551 # And do the same for specialpage aliases. $page is an array.
2552 foreach ( $cache['specialPageAliases'] as &$page ) {
2553 $page = str_replace( ' ', '_', $page );
2555 # Decouple the reference to prevent accidental damage
2558 # Save to both caches
2559 self
::$mLocalisationCache[$code] = $cache;
2560 if ( !$disableCache ) {
2561 $wgMemc->set( $memcKey, $cache );
2562 $wgMemc->set( $fbMemcKey, (string) $cache['fallback'] );
2565 wfProfileOut( __METHOD__
);
2570 * Test if a given localisation cache is out of date with respect to the
2571 * source Messages files. This is done automatically for the global cache
2572 * in $wgMemc, but is only done on certain occasions for the serialized
2575 * @param $cache mixed Either a language code or a cache array
2577 static function isLocalisationOutOfDate( $cache ) {
2578 if ( !is_array( $cache ) ) {
2579 self
::loadLocalisation( $cache );
2580 $cache = self
::$mLocalisationCache[$cache];
2582 // At least one language file and the MagicWord file needed
2583 if( count($cache['deps']) < 2 ) {
2587 foreach ( $cache['deps'] as $file => $mtime ) {
2588 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
2597 * Get the fallback for a given language
2599 static function getFallbackFor( $code ) {
2601 if ( $code === 'en' ) return false;
2604 static $cache = array();
2606 if ( isset($cache[$code]) ) return $cache[$code];
2610 $memcKey = wfMemcKey( 'fallback', $code );
2611 $fbcode = $wgMemc->get( $memcKey );
2613 if ( is_string($fbcode) ) {
2614 // False is stored as a string to detect failures in memcache properly
2615 if ( $fbcode === '' ) $fbcode = false;
2617 // Update local cache and return
2618 $cache[$code] = $fbcode;
2622 // Nothing in caches, load and and update both caches
2623 self
::loadLocalisation( $code );
2624 $fbcode = self
::$mLocalisationCache[$code]['fallback'];
2626 $cache[$code] = $fbcode;
2627 $wgMemc->set( $memcKey, (string) $fbcode );
2633 * Get all messages for a given language
2635 static function getMessagesFor( $code ) {
2636 self
::loadLocalisation( $code );
2637 return self
::$mLocalisationCache[$code]['messages'];
2641 * Get a message for a given language
2643 static function getMessageFor( $key, $code ) {
2644 self
::loadLocalisation( $code );
2645 return isset( self
::$mLocalisationCache[$code]['messages'][$key] ) ? self
::$mLocalisationCache[$code]['messages'][$key] : null;
2649 * Load localisation data for this object
2652 if ( !$this->mLoaded
) {
2653 self
::loadLocalisation( $this->getCode() );
2654 $cache =& self
::$mLocalisationCache[$this->getCode()];
2655 foreach ( self
::$mLocalisationKeys as $key ) {
2656 $this->$key = $cache[$key];
2658 $this->mLoaded
= true;
2660 $this->fixUpSettings();
2665 * Do any necessary post-cache-load settings adjustment
2667 function fixUpSettings() {
2668 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
2669 $wgNamespaceAliases, $wgAmericanDates;
2670 wfProfileIn( __METHOD__
);
2671 if ( $wgExtraNamespaces ) {
2672 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
2675 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
2676 if ( $wgMetaNamespaceTalk ) {
2677 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
2679 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
2680 $this->namespaceNames
[NS_PROJECT_TALK
] =
2681 $this->fixVariableInNamespace( $talk );
2684 # The above mixing may leave namespaces out of canonical order.
2685 # Re-order by namespace ID number...
2686 ksort( $this->namespaceNames
);
2688 # Put namespace names and aliases into a hashtable.
2689 # If this is too slow, then we should arrange it so that it is done
2690 # before caching. The catch is that at pre-cache time, the above
2691 # class-specific fixup hasn't been done.
2692 $this->mNamespaceIds
= array();
2693 foreach ( $this->namespaceNames
as $index => $name ) {
2694 $this->mNamespaceIds
[$this->lc($name)] = $index;
2696 if ( $this->namespaceAliases
) {
2697 foreach ( $this->namespaceAliases
as $name => $index ) {
2698 if ( $index === NS_PROJECT_TALK
) {
2699 unset( $this->namespaceAliases
[$name] );
2700 $name = $this->fixVariableInNamespace( $name );
2701 $this->namespaceAliases
[$name] = $index;
2703 $this->mNamespaceIds
[$this->lc($name)] = $index;
2706 if ( $wgNamespaceAliases ) {
2707 foreach ( $wgNamespaceAliases as $name => $index ) {
2708 $this->mNamespaceIds
[$this->lc($name)] = $index;
2712 if ( $this->defaultDateFormat
== 'dmy or mdy' ) {
2713 $this->defaultDateFormat
= $wgAmericanDates ?
'mdy' : 'dmy';
2715 wfProfileOut( __METHOD__
);
2718 function fixVariableInNamespace( $talk ) {
2719 if ( strpos( $talk, '$1' ) === false ) return $talk;
2721 global $wgMetaNamespace;
2722 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
2724 # Allow grammar transformations
2725 # Allowing full message-style parsing would make simple requests
2726 # such as action=raw much more expensive than they need to be.
2727 # This will hopefully cover most cases.
2728 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
2729 array( &$this, 'replaceGrammarInNamespace' ), $talk );
2730 return str_replace( ' ', '_', $talk );
2733 function replaceGrammarInNamespace( $m ) {
2734 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
2737 static function getCaseMaps() {
2738 static $wikiUpperChars, $wikiLowerChars;
2739 if ( isset( $wikiUpperChars ) ) {
2740 return array( $wikiUpperChars, $wikiLowerChars );
2743 wfProfileIn( __METHOD__
);
2744 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
2745 if ( $arr === false ) {
2746 throw new MWException(
2747 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
2750 wfProfileOut( __METHOD__
);
2751 return array( $wikiUpperChars, $wikiLowerChars );
2754 function formatTimePeriod( $seconds ) {
2755 if ( $seconds < 10 ) {
2756 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
2757 } elseif ( $seconds < 60 ) {
2758 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
2759 } elseif ( $seconds < 3600 ) {
2760 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
2761 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
2763 $hours = floor( $seconds / 3600 );
2764 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
2765 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
2766 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
2767 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
2768 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
2772 function formatBitrate( $bps ) {
2773 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
2775 return $this->formatNum( $bps ) . $units[0];
2777 $unitIndex = floor( log10( $bps ) / 3 );
2778 $mantissa = $bps / pow( 1000, $unitIndex );
2779 if ( $mantissa < 10 ) {
2780 $mantissa = round( $mantissa, 1 );
2782 $mantissa = round( $mantissa );
2784 return $this->formatNum( $mantissa ) . $units[$unitIndex];
2788 * Format a size in bytes for output, using an appropriate
2789 * unit (B, KB, MB or GB) according to the magnitude in question
2791 * @param $size Size to format
2792 * @return string Plain text (not HTML)
2794 function formatSize( $size ) {
2795 // For small sizes no decimal places necessary
2797 if( $size > 1024 ) {
2798 $size = $size / 1024;
2799 if( $size > 1024 ) {
2800 $size = $size / 1024;
2801 // For MB and bigger two decimal places are smarter
2803 if( $size > 1024 ) {
2804 $size = $size / 1024;
2805 $msg = 'size-gigabytes';
2807 $msg = 'size-megabytes';
2810 $msg = 'size-kilobytes';
2813 $msg = 'size-bytes';
2815 $size = round( $size, $round );
2816 $text = $this->getMessageFromDB( $msg );
2817 return str_replace( '$1', $this->formatNum( $size ), $text );