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, $v) {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, $ignoreOtherCond = 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; }
49 function groupConvert($group) {return '';}
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', '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 # Minguo dates up to Showa period
1158 # Deduct years from the Gregorian calendar
1159 # depending on the nengo periods
1160 # Months and days are identical
1161 if (($gy < 1989) ||
(($gy == 1989) && ($gm == 1) && ($gd < 8))) {
1163 $gy_gannen = $gy - 1926 +
1;
1164 $gy_offset = $gy_gannen +
$gy_offset;
1165 if ($gy_gannen == 1)
1167 $gy_offset = '昭和'.$gy_offset;
1170 $gy_gannen = $gy - 1989 +
1;
1171 $gy_offset = $gy_gannen +
$gy_offset;
1172 if ($gy_gannen == 1)
1174 $gy_offset = '平成'.$gy_offset;
1180 return array( $gy_offset, $gm, $gd );
1184 * Roman number formatting up to 3000
1186 static function romanNumeral( $num ) {
1187 static $table = array(
1188 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1189 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1190 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1191 array( '', 'M', 'MM', 'MMM' )
1194 $num = intval( $num );
1195 if ( $num > 3000 ||
$num <= 0 ) {
1200 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1201 if ( $num >= $pow10 ) {
1202 $s .= $table[$i][floor($num / $pow10)];
1204 $num = $num %
$pow10;
1210 * Hebrew Gematria number formatting up to 9999
1212 static function hebrewNumeral( $num ) {
1213 static $table = array(
1214 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1215 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1216 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1217 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1220 $num = intval( $num );
1221 if ( $num > 9999 ||
$num <= 0 ) {
1226 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1227 if ( $num >= $pow10 ) {
1228 if ( $num == 15 ||
$num == 16 ) {
1229 $s .= $table[0][9] . $table[0][$num - 9];
1232 $s .= $table[$i][intval( ( $num / $pow10 ) )];
1233 if( $pow10 == 1000 ) {
1238 $num = $num %
$pow10;
1240 if( strlen( $s ) == 2 ) {
1243 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1244 $str .= substr( $s, strlen( $s ) - 2, 2 );
1246 $start = substr( $str, 0, strlen( $str ) - 2 );
1247 $end = substr( $str, strlen( $str ) - 2 );
1250 $str = $start . 'ך';
1253 $str = $start . 'ם';
1256 $str = $start . 'ן';
1259 $str = $start . 'ף';
1262 $str = $start . 'ץ';
1269 * This is meant to be used by time(), date(), and timeanddate() to get
1270 * the date preference they're supposed to use, it should be used in
1274 * function timeanddate([...], $format = true) {
1275 * $datePreference = $this->dateFormat($format);
1280 * @param $usePrefs Mixed: if true, the user's preference is used
1281 * if false, the site/language default is used
1282 * if int/string, assumed to be a format.
1285 function dateFormat( $usePrefs = true ) {
1288 if( is_bool( $usePrefs ) ) {
1290 $datePreference = $wgUser->getDatePreference();
1292 $options = User
::getDefaultOptions();
1293 $datePreference = (string)$options['date'];
1296 $datePreference = (string)$usePrefs;
1300 if( $datePreference == '' ) {
1304 return $datePreference;
1308 * @param $ts Mixed: the time format which needs to be turned into a
1309 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1310 * @param $adj Bool: whether to adjust the time output according to the
1311 * user configured offset ($timecorrection)
1312 * @param $format Mixed: true to use user's date format preference
1313 * @param $timecorrection String: the time offset as returned by
1314 * validateTimeZone() in Special:Preferences
1317 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
1320 $ts = $this->userAdjust( $ts, $timecorrection );
1323 $pref = $this->dateFormat( $format );
1324 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref date"] ) ) {
1325 $pref = $this->defaultDateFormat
;
1327 return $this->sprintfDate( $this->dateFormats
["$pref date"], $ts );
1331 * @param $ts Mixed: the time format which needs to be turned into a
1332 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1333 * @param $adj Bool: whether to adjust the time output according to the
1334 * user configured offset ($timecorrection)
1335 * @param $format Mixed: true to use user's date format preference
1336 * @param $timecorrection String: the time offset as returned by
1337 * validateTimeZone() in Special:Preferences
1340 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
1343 $ts = $this->userAdjust( $ts, $timecorrection );
1346 $pref = $this->dateFormat( $format );
1347 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref time"] ) ) {
1348 $pref = $this->defaultDateFormat
;
1350 return $this->sprintfDate( $this->dateFormats
["$pref time"], $ts );
1354 * @param $ts Mixed: the time format which needs to be turned into a
1355 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1356 * @param $adj Bool: whether to adjust the time output according to the
1357 * user configured offset ($timecorrection)
1358 * @param $format Mixed: what format to return, if it's false output the
1359 * default one (default true)
1360 * @param $timecorrection String: the time offset as returned by
1361 * validateTimeZone() in Special:Preferences
1364 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
1367 $ts = wfTimestamp( TS_MW
, $ts );
1370 $ts = $this->userAdjust( $ts, $timecorrection );
1373 $pref = $this->dateFormat( $format );
1374 if( $pref == 'default' ||
!isset( $this->dateFormats
["$pref both"] ) ) {
1375 $pref = $this->defaultDateFormat
;
1378 return $this->sprintfDate( $this->dateFormats
["$pref both"], $ts );
1381 function getMessage( $key ) {
1383 return isset( $this->messages
[$key] ) ?
$this->messages
[$key] : null;
1386 function getAllMessages() {
1388 return $this->messages
;
1391 function iconv( $in, $out, $string ) {
1392 # For most languages, this is a wrapper for iconv
1393 return iconv( $in, $out . '//IGNORE', $string );
1396 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
1397 function ucwordbreaksCallbackAscii($matches){
1398 return $this->ucfirst($matches[1]);
1401 function ucwordbreaksCallbackMB($matches){
1402 return mb_strtoupper($matches[0]);
1405 function ucCallback($matches){
1406 list( $wikiUpperChars ) = self
::getCaseMaps();
1407 return strtr( $matches[1], $wikiUpperChars );
1410 function lcCallback($matches){
1411 list( , $wikiLowerChars ) = self
::getCaseMaps();
1412 return strtr( $matches[1], $wikiLowerChars );
1415 function ucwordsCallbackMB($matches){
1416 return mb_strtoupper($matches[0]);
1419 function ucwordsCallbackWiki($matches){
1420 list( $wikiUpperChars ) = self
::getCaseMaps();
1421 return strtr( $matches[0], $wikiUpperChars );
1424 function ucfirst( $str ) {
1425 if ( empty($str) ) return $str;
1426 if ( ord($str[0]) < 128 ) return ucfirst($str);
1427 else return self
::uc($str,true); // fall back to more complex logic in case of multibyte strings
1430 function uc( $str, $first = false ) {
1431 if ( function_exists( 'mb_strtoupper' ) ) {
1433 if ( self
::isMultibyte( $str ) ) {
1434 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1436 return ucfirst( $str );
1439 return self
::isMultibyte( $str ) ?
mb_strtoupper( $str ) : strtoupper( $str );
1442 if ( self
::isMultibyte( $str ) ) {
1443 list( $wikiUpperChars ) = $this->getCaseMaps();
1444 $x = $first ?
'^' : '';
1445 return preg_replace_callback(
1446 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1447 array($this,"ucCallback"),
1451 return $first ?
ucfirst( $str ) : strtoupper( $str );
1456 function lcfirst( $str ) {
1457 if ( empty($str) ) return $str;
1458 if ( is_string( $str ) && ord($str[0]) < 128 ) {
1459 // editing string in place = cool
1460 $str[0]=strtolower($str[0]);
1463 else return self
::lc( $str, true );
1466 function lc( $str, $first = false ) {
1467 if ( function_exists( 'mb_strtolower' ) )
1469 if ( self
::isMultibyte( $str ) )
1470 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1472 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
1474 return self
::isMultibyte( $str ) ?
mb_strtolower( $str ) : strtolower( $str );
1476 if ( self
::isMultibyte( $str ) ) {
1477 list( , $wikiLowerChars ) = self
::getCaseMaps();
1478 $x = $first ?
'^' : '';
1479 return preg_replace_callback(
1480 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1481 array($this,"lcCallback"),
1485 return $first ?
strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
1488 function isMultibyte( $str ) {
1489 return (bool)preg_match( '/[\x80-\xff]/', $str );
1492 function ucwords($str) {
1493 if ( self
::isMultibyte( $str ) ) {
1494 $str = self
::lc($str);
1496 // regexp to find first letter in each word (i.e. after each space)
1497 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1499 // function to use to capitalize a single char
1500 if ( function_exists( 'mb_strtoupper' ) )
1501 return preg_replace_callback(
1503 array($this,"ucwordsCallbackMB"),
1507 return preg_replace_callback(
1509 array($this,"ucwordsCallbackWiki"),
1514 return ucwords( strtolower( $str ) );
1517 # capitalize words at word breaks
1518 function ucwordbreaks($str){
1519 if (self
::isMultibyte( $str ) ) {
1520 $str = self
::lc($str);
1522 // since \b doesn't work for UTF-8, we explicitely define word break chars
1523 $breaks= "[ \-\(\)\}\{\.,\?!]";
1525 // find first letter after word break
1526 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1528 if ( function_exists( 'mb_strtoupper' ) )
1529 return preg_replace_callback(
1531 array($this,"ucwordbreaksCallbackMB"),
1535 return preg_replace_callback(
1537 array($this,"ucwordsCallbackWiki"),
1542 return preg_replace_callback(
1543 '/\b([\w\x80-\xff]+)\b/',
1544 array($this,"ucwordbreaksCallbackAscii"),
1549 * Return a case-folded representation of $s
1551 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1552 * and $s2 are the same except for the case of their characters. It is not
1553 * necessary for the value returned to make sense when displayed.
1555 * Do *not* perform any other normalisation in this function. If a caller
1556 * uses this function when it should be using a more general normalisation
1557 * function, then fix the caller.
1559 function caseFold( $s ) {
1560 return $this->uc( $s );
1563 function checkTitleEncoding( $s ) {
1564 if( is_array( $s ) ) {
1565 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1567 # Check for non-UTF-8 URLs
1568 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1569 if(!$ishigh) return $s;
1571 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1572 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1573 if( $isutf8 ) return $s;
1575 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1578 function fallback8bitEncoding() {
1580 return $this->fallback8bitEncoding
;
1584 * Some languages have special punctuation to strip out
1585 * or characters which need to be converted for MySQL's
1586 * indexing to grok it correctly. Make such changes here.
1588 * @param $string String
1591 function stripForSearch( $string ) {
1593 if ( $wgDBtype != 'mysql' ) {
1598 wfProfileIn( __METHOD__
);
1600 // MySQL fulltext index doesn't grok utf-8, so we
1601 // need to fold cases and convert to hex
1602 $out = preg_replace_callback(
1603 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
1604 array( $this, 'stripForSearchCallback' ),
1605 $this->lc( $string ) );
1607 // And to add insult to injury, the default indexing
1608 // ignores short words... Pad them so we can pass them
1609 // through without reconfiguring the server...
1610 $minLength = $this->minSearchLength();
1611 if( $minLength > 1 ) {
1613 $out = preg_replace(
1619 // Periods within things like hostnames and IP addresses
1620 // are also important -- we want a search for "example.com"
1621 // or "192.168.1.1" to work sanely.
1623 // MySQL's search seems to ignore them, so you'd match on
1624 // "example.wikipedia.com" and "192.168.83.1" as well.
1625 $out = preg_replace(
1630 wfProfileOut( __METHOD__
);
1635 * Armor a case-folded UTF-8 string to get through MySQL's
1636 * fulltext search without being mucked up by funny charset
1637 * settings or anything else of the sort.
1639 protected function stripForSearchCallback( $matches ) {
1640 return 'u8' . bin2hex( $matches[1] );
1644 * Check MySQL server's ft_min_word_len setting so we know
1645 * if we need to pad short words...
1647 protected function minSearchLength() {
1648 if( !isset( $this->minSearchLength
) ) {
1649 $sql = "show global variables like 'ft\\_min\\_word\\_len'";
1650 $dbr = wfGetDB( DB_SLAVE
);
1651 $result = $dbr->query( $sql );
1652 $row = $result->fetchObject();
1655 if( $row && $row->Variable_name
== 'ft_min_word_len' ) {
1656 $this->minSearchLength
= intval( $row->Value
);
1658 $this->minSearchLength
= 0;
1661 return $this->minSearchLength
;
1664 function convertForSearchResult( $termsArray ) {
1665 # some languages, e.g. Chinese, need to do a conversion
1666 # in order for search results to be displayed correctly
1671 * Get the first character of a string.
1676 function firstChar( $s ) {
1678 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1679 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1681 if ( isset( $matches[1] ) ) {
1682 if ( strlen( $matches[1] ) != 3 ) {
1686 // Break down Hangul syllables to grab the first jamo
1687 $code = utf8ToCodepoint( $matches[1] );
1688 if ( $code < 0xac00 ||
0xd7a4 <= $code) {
1690 } elseif ( $code < 0xb098 ) {
1691 return "\xe3\x84\xb1";
1692 } elseif ( $code < 0xb2e4 ) {
1693 return "\xe3\x84\xb4";
1694 } elseif ( $code < 0xb77c ) {
1695 return "\xe3\x84\xb7";
1696 } elseif ( $code < 0xb9c8 ) {
1697 return "\xe3\x84\xb9";
1698 } elseif ( $code < 0xbc14 ) {
1699 return "\xe3\x85\x81";
1700 } elseif ( $code < 0xc0ac ) {
1701 return "\xe3\x85\x82";
1702 } elseif ( $code < 0xc544 ) {
1703 return "\xe3\x85\x85";
1704 } elseif ( $code < 0xc790 ) {
1705 return "\xe3\x85\x87";
1706 } elseif ( $code < 0xcc28 ) {
1707 return "\xe3\x85\x88";
1708 } elseif ( $code < 0xce74 ) {
1709 return "\xe3\x85\x8a";
1710 } elseif ( $code < 0xd0c0 ) {
1711 return "\xe3\x85\x8b";
1712 } elseif ( $code < 0xd30c ) {
1713 return "\xe3\x85\x8c";
1714 } elseif ( $code < 0xd558 ) {
1715 return "\xe3\x85\x8d";
1717 return "\xe3\x85\x8e";
1724 function initEncoding() {
1725 # Some languages may have an alternate char encoding option
1726 # (Esperanto X-coding, Japanese furigana conversion, etc)
1727 # If this language is used as the primary content language,
1728 # an override to the defaults can be set here on startup.
1731 function recodeForEdit( $s ) {
1732 # For some languages we'll want to explicitly specify
1733 # which characters make it into the edit box raw
1734 # or are converted in some way or another.
1735 # Note that if wgOutputEncoding is different from
1736 # wgInputEncoding, this text will be further converted
1737 # to wgOutputEncoding.
1738 global $wgEditEncoding;
1739 if( $wgEditEncoding == '' or
1740 $wgEditEncoding == 'UTF-8' ) {
1743 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1747 function recodeInput( $s ) {
1748 # Take the previous into account.
1749 global $wgEditEncoding;
1750 if($wgEditEncoding != "") {
1751 $enc = $wgEditEncoding;
1755 if( $enc == 'UTF-8' ) {
1758 return $this->iconv( $enc, 'UTF-8', $s );
1763 * For right-to-left language support
1773 * A hidden direction mark (LRM or RLM), depending on the language direction
1777 function getDirMark() {
1778 return $this->isRTL() ?
"\xE2\x80\x8F" : "\xE2\x80\x8E";
1782 * An arrow, depending on the language direction
1786 function getArrow() {
1787 return $this->isRTL() ?
'←' : '→';
1791 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1795 function linkPrefixExtension() {
1797 return $this->linkPrefixExtension
;
1800 function &getMagicWords() {
1802 return $this->magicWords
;
1805 # Fill a MagicWord object with data from here
1806 function getMagic( &$mw ) {
1807 if ( !$this->mMagicHookDone
) {
1808 $this->mMagicHookDone
= true;
1809 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions
, $this->getCode() ) );
1811 if ( isset( $this->mMagicExtensions
[$mw->mId
] ) ) {
1812 $rawEntry = $this->mMagicExtensions
[$mw->mId
];
1814 $magicWords =& $this->getMagicWords();
1815 if ( isset( $magicWords[$mw->mId
] ) ) {
1816 $rawEntry = $magicWords[$mw->mId
];
1818 # Fall back to English if local list is incomplete
1819 $magicWords =& Language
::getMagicWords();
1820 if ( !isset($magicWords[$mw->mId
]) ) {
1821 throw new MWException("Magic word '{$mw->mId}' not found" );
1823 $rawEntry = $magicWords[$mw->mId
];
1827 if( !is_array( $rawEntry ) ) {
1828 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1830 $mw->mCaseSensitive
= $rawEntry[0];
1831 $mw->mSynonyms
= array_slice( $rawEntry, 1 );
1836 * Add magic words to the extension array
1838 function addMagicWordsByLang( $newWords ) {
1839 $code = $this->getCode();
1840 $fallbackChain = array();
1841 while ( $code && !in_array( $code, $fallbackChain ) ) {
1842 $fallbackChain[] = $code;
1843 $code = self
::getFallbackFor( $code );
1845 if ( !in_array( 'en', $fallbackChain ) ) {
1846 $fallbackChain[] = 'en';
1848 $fallbackChain = array_reverse( $fallbackChain );
1849 foreach ( $fallbackChain as $code ) {
1850 if ( isset( $newWords[$code] ) ) {
1851 $this->mMagicExtensions
= $newWords[$code] +
$this->mMagicExtensions
;
1857 * Get special page names, as an associative array
1858 * case folded alias => real name
1860 function getSpecialPageAliases() {
1863 // Cache aliases because it may be slow to load them
1864 if ( !isset( $this->mExtendedSpecialPageAliases
) ) {
1867 $this->mExtendedSpecialPageAliases
= $this->specialPageAliases
;
1869 global $wgExtensionAliasesFiles;
1870 foreach ( $wgExtensionAliasesFiles as $file ) {
1873 if ( !file_exists($file) )
1874 throw new MWException( "Aliases file does not exist: $file" );
1879 // Check the availability of aliases
1880 if ( !isset($aliases['en']) )
1881 throw new MWException( "Malformed aliases file: $file" );
1883 // Merge all aliases in fallback chain
1884 $code = $this->getCode();
1886 if ( !isset($aliases[$code]) ) continue;
1888 $aliases[$code] = $this->fixSpecialPageAliases( $aliases[$code] );
1889 /* Merge the aliases, THIS will break if there is special page name
1890 * which looks like a numerical key, thanks to PHP...
1891 * See the array_merge_recursive manual entry */
1892 $this->mExtendedSpecialPageAliases
= array_merge_recursive(
1893 $this->mExtendedSpecialPageAliases
, $aliases[$code] );
1895 } while ( $code = self
::getFallbackFor( $code ) );
1898 wfRunHooks( 'LanguageGetSpecialPageAliases',
1899 array( &$this->mExtendedSpecialPageAliases
, $this->getCode() ) );
1902 return $this->mExtendedSpecialPageAliases
;
1906 * Function to fix special page aliases. Will convert the first letter to
1907 * upper case and spaces to underscores. Can be given a full aliases array,
1908 * in which case it will recursively fix all aliases.
1910 public function fixSpecialPageAliases( $mixed ) {
1911 // Work recursively until in string level
1912 if ( is_array($mixed) ) {
1913 $callback = array( $this, 'fixSpecialPageAliases' );
1914 return array_map( $callback, $mixed );
1916 return str_replace( ' ', '_', $this->ucfirst( $mixed ) );
1920 * Italic is unsuitable for some languages
1922 * @param $text String: the text to be emphasized.
1925 function emphasize( $text ) {
1926 return "<em>$text</em>";
1930 * Normally we output all numbers in plain en_US style, that is
1931 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1932 * point twohundredthirtyfive. However this is not sutable for all
1933 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1934 * Icelandic just want to use commas instead of dots, and dots instead
1935 * of commas like "293.291,235".
1937 * An example of this function being called:
1939 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1942 * See LanguageGu.php for the Gujarati implementation and
1943 * $separatorTransformTable on MessageIs.php for
1944 * the , => . and . => , implementation.
1946 * @todo check if it's viable to use localeconv() for the decimal
1948 * @param $number Mixed: the string to be formatted, should be an integer
1949 * or a floating point number.
1950 * @param $nocommafy Bool: set to true for special numbers like dates
1953 function formatNum( $number, $nocommafy = false ) {
1954 global $wgTranslateNumerals;
1956 $number = $this->commafy($number);
1957 $s = $this->separatorTransformTable();
1958 if ($s) { $number = strtr($number, $s); }
1961 if ($wgTranslateNumerals) {
1962 $s = $this->digitTransformTable();
1963 if ($s) { $number = strtr($number, $s); }
1969 function parseFormattedNumber( $number ) {
1970 $s = $this->digitTransformTable();
1971 if ($s) { $number = strtr($number, array_flip($s)); }
1973 $s = $this->separatorTransformTable();
1974 if ($s) { $number = strtr($number, array_flip($s)); }
1976 $number = strtr( $number, array (',' => '') );
1981 * Adds commas to a given number
1986 function commafy($_) {
1987 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1990 function digitTransformTable() {
1992 return $this->digitTransformTable
;
1995 function separatorTransformTable() {
1997 return $this->separatorTransformTable
;
2002 * Take a list of strings and build a locale-friendly comma-separated
2003 * list, using the local comma-separator message.
2004 * The last two strings are chained with an "and".
2009 function listToText( $l ) {
2011 $m = count( $l ) - 1;
2013 return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
2016 for ( $i = $m; $i >= 0; $i-- ) {
2019 } else if( $i == $m - 1 ) {
2020 $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
2022 $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
2030 * Take a list of strings and build a locale-friendly comma-separated
2031 * list, using the local comma-separator message.
2032 * @param $list array of strings to put in a comma list
2035 function commaList( $list ) {
2038 wfMsgExt( 'comma-separator', array( 'escapenoentities', 'language' => $this ) ) );
2042 * Take a list of strings and build a locale-friendly semicolon-separated
2043 * list, using the local semicolon-separator message.
2044 * @param $list array of strings to put in a semicolon list
2047 function semicolonList( $list ) {
2050 wfMsgExt( 'semicolon-separator', array( 'escapenoentities', 'language' => $this ) ) );
2054 * Same as commaList, but separate it with the pipe instead.
2055 * @param $list array of strings to put in a pipe list
2058 function pipeList( $list ) {
2061 wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) ) );
2065 * Truncate a string to a specified length in bytes, appending an optional
2066 * string (e.g. for ellipses)
2068 * The database offers limited byte lengths for some columns in the database;
2069 * multi-byte character sets mean we need to ensure that only whole characters
2070 * are included, otherwise broken characters can be passed to the user
2072 * If $length is negative, the string will be truncated from the beginning
2074 * @param $string String to truncate
2075 * @param $length Int: maximum length (excluding ellipses)
2076 * @param $ellipsis String to append to the truncated text
2079 function truncate( $string, $length, $ellipsis = '...' ) {
2080 # Use the localized ellipsis character
2081 if( $ellipsis == '...' ) {
2082 $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
2085 if( $length == 0 ) {
2088 if ( strlen( $string ) <= abs( $length ) ) {
2092 $string = substr( $string, 0, $length );
2093 $char = ord( $string[strlen( $string ) - 1] );
2095 if ($char >= 0xc0) {
2096 # We got the first byte only of a multibyte char; remove it.
2097 $string = substr( $string, 0, -1 );
2098 } elseif( $char >= 0x80 &&
2099 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
2100 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
2101 # We chopped in the middle of a character; remove it
2104 return $string . $ellipsis;
2106 $string = substr( $string, $length );
2107 $char = ord( $string[0] );
2108 if( $char >= 0x80 && $char < 0xc0 ) {
2109 # We chopped in the middle of a character; remove the whole thing
2110 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
2112 return $ellipsis . $string;
2117 * Grammatical transformations, needed for inflected languages
2118 * Invoked by putting {{grammar:case|word}} in a message
2120 * @param $word string
2121 * @param $case string
2124 function convertGrammar( $word, $case ) {
2125 global $wgGrammarForms;
2126 if ( isset($wgGrammarForms[$this->getCode()][$case][$word]) ) {
2127 return $wgGrammarForms[$this->getCode()][$case][$word];
2133 * Provides an alternative text depending on specified gender.
2134 * Usage {{gender:username|masculine|feminine|neutral}}.
2135 * username is optional, in which case the gender of current user is used,
2136 * but only in (some) interface messages; otherwise default gender is used.
2137 * If second or third parameter are not specified, masculine is used.
2138 * These details may be overriden per language.
2140 function gender( $gender, $forms ) {
2141 if ( !count($forms) ) { return ''; }
2142 $forms = $this->preConvertPlural( $forms, 2 );
2143 if ( $gender === 'male' ) return $forms[0];
2144 if ( $gender === 'female' ) return $forms[1];
2145 return isset($forms[2]) ?
$forms[2] : $forms[0];
2149 * Plural form transformations, needed for some languages.
2150 * For example, there are 3 form of plural in Russian and Polish,
2151 * depending on "count mod 10". See [[w:Plural]]
2152 * For English it is pretty simple.
2154 * Invoked by putting {{plural:count|wordform1|wordform2}}
2155 * or {{plural:count|wordform1|wordform2|wordform3}}
2157 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
2159 * @param $count Integer: non-localized number
2160 * @param $forms Array: different plural forms
2161 * @return string Correct form of plural for $count in this language
2163 function convertPlural( $count, $forms ) {
2164 if ( !count($forms) ) { return ''; }
2165 $forms = $this->preConvertPlural( $forms, 2 );
2167 return ( $count == 1 ) ?
$forms[0] : $forms[1];
2171 * Checks that convertPlural was given an array and pads it to requested
2172 * amound of forms by copying the last one.
2174 * @param $count Integer: How many forms should there be at least
2175 * @param $forms Array of forms given to convertPlural
2176 * @return array Padded array of forms or an exception if not an array
2178 protected function preConvertPlural( /* Array */ $forms, $count ) {
2179 while ( count($forms) < $count ) {
2180 $forms[] = $forms[count($forms)-1];
2186 * For translaing of expiry times
2187 * @param $str String: the validated block time in English
2188 * @return Somehow translated block time
2189 * @see LanguageFi.php for example implementation
2191 function translateBlockExpiry( $str ) {
2193 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
2195 if ( $scBlockExpiryOptions == '-') {
2199 foreach (explode(',', $scBlockExpiryOptions) as $option) {
2200 if ( strpos($option, ":") === false )
2202 list($show, $value) = explode(":", $option);
2203 if ( strcmp ( $str, $value) == 0 ) {
2204 return htmlspecialchars( trim( $show ) );
2212 * languages like Chinese need to be segmented in order for the diff
2215 * @param $text String
2218 function segmentForDiff( $text ) {
2223 * and unsegment to show the result
2225 * @param $text String
2228 function unsegmentForDiff( $text ) {
2232 # convert text to different variants of a language.
2233 function convert( $text, $isTitle = false, $variant = null ) {
2234 return $this->mConverter
->convert($text, $isTitle, $variant);
2237 # Convert text from within Parser
2238 function parserConvert( $text, &$parser ) {
2239 return $this->mConverter
->parserConvert( $text, $parser );
2242 # Check if this is a language with variants
2243 function hasVariants(){
2244 return sizeof($this->getVariants())>1;
2247 # Put custom tags (e.g. -{ }-) around math to prevent conversion
2248 function armourMath($text){
2249 return $this->mConverter
->armourMath($text);
2254 * Perform output conversion on a string, and encode for safe HTML output.
2255 * @param $text String
2256 * @param $isTitle Bool -- wtf?
2258 * @todo this should get integrated somewhere sane
2260 function convertHtml( $text, $isTitle = false ) {
2261 return htmlspecialchars( $this->convert( $text, $isTitle ) );
2264 function convertCategoryKey( $key ) {
2265 return $this->mConverter
->convertCategoryKey( $key );
2269 * get the list of variants supported by this langauge
2270 * see sample implementation in LanguageZh.php
2272 * @return array an array of language codes
2274 function getVariants() {
2275 return $this->mConverter
->getVariants();
2279 function getPreferredVariant( $fromUser = true ) {
2280 return $this->mConverter
->getPreferredVariant( $fromUser );
2284 * if a language supports multiple variants, it is
2285 * possible that non-existing link in one variant
2286 * actually exists in another variant. this function
2287 * tries to find it. See e.g. LanguageZh.php
2289 * @param $link String: the name of the link
2290 * @param $nt Mixed: the title object of the link
2291 * @param boolean $ignoreOtherCond: to disable other conditions when
2292 * we need to transclude a template or update a category's link
2293 * @return null the input parameters may be modified upon return
2295 function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
2296 $this->mConverter
->findVariantLink( $link, $nt, $ignoreOtherCond );
2300 * If a language supports multiple variants, converts text
2301 * into an array of all possible variants of the text:
2302 * 'variant' => text in that variant
2304 function convertLinkToAllVariants($text){
2305 return $this->mConverter
->convertLinkToAllVariants($text);
2310 * returns language specific options used by User::getPageRenderHash()
2311 * for example, the preferred language variant
2315 function getExtraHashOptions() {
2316 return $this->mConverter
->getExtraHashOptions();
2320 * for languages that support multiple variants, the title of an
2321 * article may be displayed differently in different variants. this
2322 * function returns the apporiate title defined in the body of the article.
2326 function getParsedTitle() {
2327 return $this->mConverter
->getParsedTitle();
2331 * Enclose a string with the "no conversion" tag. This is used by
2332 * various functions in the Parser
2334 * @param $text String: text to be tagged for no conversion
2336 * @return string the tagged text
2338 function markNoConversion( $text, $noParse=false ) {
2339 return $this->mConverter
->markNoConversion( $text, $noParse );
2343 * Callback function for magicword 'groupconvert'
2345 * @param string $group: the group name called for
2346 * @return blank string
2348 function groupConvert( $group ) {
2349 return $this->mConverter
->groupConvert( $group );
2353 * A regular expression to match legal word-trailing characters
2354 * which should be merged onto a link of the form [[foo]]bar.
2358 function linkTrail() {
2360 return $this->linkTrail
;
2363 function getLangObj() {
2368 * Get the RFC 3066 code for this language object
2370 function getCode() {
2371 return $this->mCode
;
2374 function setCode( $code ) {
2375 $this->mCode
= $code;
2378 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
2379 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
2382 static function getMessagesFileName( $code ) {
2384 return self
::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
2387 static function getClassFileName( $code ) {
2389 return self
::getFileName( "$IP/languages/classes/Language", $code, '.php' );
2392 static function getLocalisationArray( $code, $disableCache = false ) {
2393 self
::loadLocalisation( $code, $disableCache );
2394 return self
::$mLocalisationCache[$code];
2398 * Load localisation data for a given code into the static cache
2400 * @return array Dependencies, map of filenames to mtimes
2402 static function loadLocalisation( $code, $disableCache = false ) {
2403 static $recursionGuard = array();
2404 global $wgMemc, $wgEnableSerializedMessages, $wgCheckSerialized;
2407 throw new MWException( "Invalid language code requested" );
2410 if ( !$disableCache ) {
2411 # Try the per-process cache
2412 if ( isset( self
::$mLocalisationCache[$code] ) ) {
2413 return self
::$mLocalisationCache[$code]['deps'];
2416 wfProfileIn( __METHOD__
);
2418 # Try the serialized directory
2419 if( $wgEnableSerializedMessages ) {
2420 $cache = wfGetPrecompiledData( self
::getFileName( "Messages", $code, '.ser' ) );
2422 if ( $wgCheckSerialized && self
::isLocalisationOutOfDate( $cache ) ) {
2424 wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" );
2426 self
::$mLocalisationCache[$code] = $cache;
2427 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
2428 wfProfileOut( __METHOD__
);
2429 return self
::$mLocalisationCache[$code]['deps'];
2436 # Try the global cache
2437 $memcKey = wfMemcKey('localisation', $code );
2438 $fbMemcKey = wfMemcKey('fallback', $cache['fallback'] );
2439 $cache = $wgMemc->get( $memcKey );
2441 if ( self
::isLocalisationOutOfDate( $cache ) ) {
2442 $wgMemc->delete( $memcKey );
2443 $wgMemc->delete( $fbMemcKey );
2445 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired\n" );
2447 self
::$mLocalisationCache[$code] = $cache;
2448 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
2449 wfProfileOut( __METHOD__
);
2450 return $cache['deps'];
2454 wfProfileIn( __METHOD__
);
2457 # Default fallback, may be overridden when the messages file is included
2458 if ( $code != 'en' ) {
2464 # Load the primary localisation from the source file
2465 $filename = self
::getMessagesFileName( $code );
2466 if ( !file_exists( $filename ) ) {
2467 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
2468 $cache = compact( self
::$mLocalisationKeys ); // Set correct fallback
2471 $deps = array( $filename => filemtime( $filename ) );
2472 require( $filename );
2473 $cache = compact( self
::$mLocalisationKeys );
2474 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
2477 # Load magic word source file
2479 $filename = "$IP/includes/MagicWord.php";
2480 $newDeps = array( $filename => filemtime( $filename ) );
2481 $deps = array_merge( $deps, $newDeps );
2483 if ( !empty( $fallback ) ) {
2484 # Load the fallback localisation, with a circular reference guard
2485 if ( isset( $recursionGuard[$code] ) ) {
2486 throw new MWException( "Error: Circular fallback reference in language code $code" );
2488 $recursionGuard[$code] = true;
2489 $newDeps = self
::loadLocalisation( $fallback, $disableCache );
2490 unset( $recursionGuard[$code] );
2492 $secondary = self
::$mLocalisationCache[$fallback];
2493 $deps = array_merge( $deps, $newDeps );
2495 # Merge the fallback localisation with the current localisation
2496 foreach ( self
::$mLocalisationKeys as $key ) {
2497 if ( isset( $cache[$key] ) ) {
2498 if ( isset( $secondary[$key] ) ) {
2499 if ( in_array( $key, self
::$mMergeableMapKeys ) ) {
2500 $cache[$key] = $cache[$key] +
$secondary[$key];
2501 } elseif ( in_array( $key, self
::$mMergeableListKeys ) ) {
2502 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
2503 } elseif ( in_array( $key, self
::$mMergeableAliasListKeys ) ) {
2504 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
2508 $cache[$key] = $secondary[$key];
2512 # Merge bookstore lists if requested
2513 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
2514 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
2516 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
2517 unset( $cache['bookstoreList']['inherit'] );
2521 # Add dependencies to the cache entry
2522 $cache['deps'] = $deps;
2524 # Replace spaces with underscores in namespace names
2525 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
2527 # And do the same for specialpage aliases. $page is an array.
2528 foreach ( $cache['specialPageAliases'] as &$page ) {
2529 $page = str_replace( ' ', '_', $page );
2531 # Decouple the reference to prevent accidental damage
2534 # Save to both caches
2535 self
::$mLocalisationCache[$code] = $cache;
2536 if ( !$disableCache ) {
2537 $wgMemc->set( $memcKey, $cache );
2538 $wgMemc->set( $fbMemcKey, (string) $cache['fallback'] );
2541 wfProfileOut( __METHOD__
);
2546 * Test if a given localisation cache is out of date with respect to the
2547 * source Messages files. This is done automatically for the global cache
2548 * in $wgMemc, but is only done on certain occasions for the serialized
2551 * @param $cache mixed Either a language code or a cache array
2553 static function isLocalisationOutOfDate( $cache ) {
2554 if ( !is_array( $cache ) ) {
2555 self
::loadLocalisation( $cache );
2556 $cache = self
::$mLocalisationCache[$cache];
2558 // At least one language file and the MagicWord file needed
2559 if( count($cache['deps']) < 2 ) {
2563 foreach ( $cache['deps'] as $file => $mtime ) {
2564 if ( !file_exists( $file ) ||
filemtime( $file ) > $mtime ) {
2573 * Get the fallback for a given language
2575 static function getFallbackFor( $code ) {
2577 if ( $code === 'en' ) return false;
2580 static $cache = array();
2582 if ( isset($cache[$code]) ) return $cache[$code];
2586 $memcKey = wfMemcKey( 'fallback', $code );
2587 $fbcode = $wgMemc->get( $memcKey );
2589 if ( is_string($fbcode) ) {
2590 // False is stored as a string to detect failures in memcache properly
2591 if ( $fbcode === '' ) $fbcode = false;
2593 // Update local cache and return
2594 $cache[$code] = $fbcode;
2598 // Nothing in caches, load and and update both caches
2599 self
::loadLocalisation( $code );
2600 $fbcode = self
::$mLocalisationCache[$code]['fallback'];
2602 $cache[$code] = $fbcode;
2603 $wgMemc->set( $memcKey, (string) $fbcode );
2609 * Get all messages for a given language
2611 static function getMessagesFor( $code ) {
2612 self
::loadLocalisation( $code );
2613 return self
::$mLocalisationCache[$code]['messages'];
2617 * Get a message for a given language
2619 static function getMessageFor( $key, $code ) {
2620 self
::loadLocalisation( $code );
2621 return isset( self
::$mLocalisationCache[$code]['messages'][$key] ) ? self
::$mLocalisationCache[$code]['messages'][$key] : null;
2625 * Load localisation data for this object
2628 if ( !$this->mLoaded
) {
2629 self
::loadLocalisation( $this->getCode() );
2630 $cache =& self
::$mLocalisationCache[$this->getCode()];
2631 foreach ( self
::$mLocalisationKeys as $key ) {
2632 $this->$key = $cache[$key];
2634 $this->mLoaded
= true;
2636 $this->fixUpSettings();
2641 * Do any necessary post-cache-load settings adjustment
2643 function fixUpSettings() {
2644 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
2645 $wgNamespaceAliases, $wgAmericanDates;
2646 wfProfileIn( __METHOD__
);
2647 if ( $wgExtraNamespaces ) {
2648 $this->namespaceNames
= $wgExtraNamespaces +
$this->namespaceNames
;
2651 $this->namespaceNames
[NS_PROJECT
] = $wgMetaNamespace;
2652 if ( $wgMetaNamespaceTalk ) {
2653 $this->namespaceNames
[NS_PROJECT_TALK
] = $wgMetaNamespaceTalk;
2655 $talk = $this->namespaceNames
[NS_PROJECT_TALK
];
2656 $this->namespaceNames
[NS_PROJECT_TALK
] =
2657 $this->fixVariableInNamespace( $talk );
2660 # The above mixing may leave namespaces out of canonical order.
2661 # Re-order by namespace ID number...
2662 ksort( $this->namespaceNames
);
2664 # Put namespace names and aliases into a hashtable.
2665 # If this is too slow, then we should arrange it so that it is done
2666 # before caching. The catch is that at pre-cache time, the above
2667 # class-specific fixup hasn't been done.
2668 $this->mNamespaceIds
= array();
2669 foreach ( $this->namespaceNames
as $index => $name ) {
2670 $this->mNamespaceIds
[$this->lc($name)] = $index;
2672 if ( $this->namespaceAliases
) {
2673 foreach ( $this->namespaceAliases
as $name => $index ) {
2674 if ( $index === NS_PROJECT_TALK
) {
2675 unset( $this->namespaceAliases
[$name] );
2676 $name = $this->fixVariableInNamespace( $name );
2677 $this->namespaceAliases
[$name] = $index;
2679 $this->mNamespaceIds
[$this->lc($name)] = $index;
2682 if ( $wgNamespaceAliases ) {
2683 foreach ( $wgNamespaceAliases as $name => $index ) {
2684 $this->mNamespaceIds
[$this->lc($name)] = $index;
2688 if ( $this->defaultDateFormat
== 'dmy or mdy' ) {
2689 $this->defaultDateFormat
= $wgAmericanDates ?
'mdy' : 'dmy';
2691 wfProfileOut( __METHOD__
);
2694 function fixVariableInNamespace( $talk ) {
2695 if ( strpos( $talk, '$1' ) === false ) return $talk;
2697 global $wgMetaNamespace;
2698 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
2700 # Allow grammar transformations
2701 # Allowing full message-style parsing would make simple requests
2702 # such as action=raw much more expensive than they need to be.
2703 # This will hopefully cover most cases.
2704 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
2705 array( &$this, 'replaceGrammarInNamespace' ), $talk );
2706 return str_replace( ' ', '_', $talk );
2709 function replaceGrammarInNamespace( $m ) {
2710 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
2713 static function getCaseMaps() {
2714 static $wikiUpperChars, $wikiLowerChars;
2715 if ( isset( $wikiUpperChars ) ) {
2716 return array( $wikiUpperChars, $wikiLowerChars );
2719 wfProfileIn( __METHOD__
);
2720 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
2721 if ( $arr === false ) {
2722 throw new MWException(
2723 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
2726 wfProfileOut( __METHOD__
);
2727 return array( $wikiUpperChars, $wikiLowerChars );
2730 function formatTimePeriod( $seconds ) {
2731 if ( $seconds < 10 ) {
2732 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
2733 } elseif ( $seconds < 60 ) {
2734 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
2735 } elseif ( $seconds < 3600 ) {
2736 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
2737 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
2739 $hours = floor( $seconds / 3600 );
2740 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
2741 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
2742 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
2743 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
2744 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
2748 function formatBitrate( $bps ) {
2749 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
2751 return $this->formatNum( $bps ) . $units[0];
2753 $unitIndex = floor( log10( $bps ) / 3 );
2754 $mantissa = $bps / pow( 1000, $unitIndex );
2755 if ( $mantissa < 10 ) {
2756 $mantissa = round( $mantissa, 1 );
2758 $mantissa = round( $mantissa );
2760 return $this->formatNum( $mantissa ) . $units[$unitIndex];
2764 * Format a size in bytes for output, using an appropriate
2765 * unit (B, KB, MB or GB) according to the magnitude in question
2767 * @param $size Size to format
2768 * @return string Plain text (not HTML)
2770 function formatSize( $size ) {
2771 // For small sizes no decimal places necessary
2773 if( $size > 1024 ) {
2774 $size = $size / 1024;
2775 if( $size > 1024 ) {
2776 $size = $size / 1024;
2777 // For MB and bigger two decimal places are smarter
2779 if( $size > 1024 ) {
2780 $size = $size / 1024;
2781 $msg = 'size-gigabytes';
2783 $msg = 'size-megabytes';
2786 $msg = 'size-kilobytes';
2789 $msg = 'size-bytes';
2791 $size = round( $size, $round );
2792 $text = $this->getMessageFromDB( $msg );
2793 return str_replace( '$1', $this->formatNum( $size ), $text );