]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - languages/Language.php
MediaWiki 1.16.4
[autoinstalls/mediawiki.git] / languages / Language.php
1 <?php
2 /**
3  * @defgroup Language Language
4  *
5  * @file
6  * @ingroup Language
7  */
8
9 if( !defined( 'MEDIAWIKI' ) ) {
10         echo "This file is part of MediaWiki, it is not a valid entry point.\n";
11         exit( 1 );
12 }
13
14 # Read language names
15 global $wgLanguageNames;
16 require_once( dirname(__FILE__) . '/Names.php' ) ;
17
18 global $wgInputEncoding, $wgOutputEncoding;
19
20 /**
21  * These are always UTF-8, they exist only for backwards compatibility
22  */
23 $wgInputEncoding    = "UTF-8";
24 $wgOutputEncoding       = "UTF-8";
25
26 if( function_exists( 'mb_strtoupper' ) ) {
27         mb_internal_encoding('UTF-8');
28 }
29
30 /**
31  * a fake language converter
32  *
33  * @ingroup Language
34  */
35 class FakeConverter {
36         var $mLang;
37         function FakeConverter( $langobj ) { $this->mLang = $langobj; }
38         function autoConvertToAllVariants( $text ) { return $text; }
39         function convert( $t ) { return $t; }
40         function convertTitle( $t ) { return $t->getPrefixedText(); }
41         function getVariants() { return array( $this->mLang->getCode() ); }
42         function getPreferredVariant() { return $this->mLang->getCode(); }
43         function getConvRuleTitle() { return false; }
44         function findVariantLink(&$l, &$n, $ignoreOtherCond = false) {}
45         function getExtraHashOptions() {return '';}
46         function getParsedTitle() {return '';}
47         function markNoConversion($text, $noParse=false) {return $text;}
48         function convertCategoryKey( $key ) {return $key; }
49         function convertLinkToAllVariants($text){ return array( $this->mLang->getCode() => $text); }
50         function armourMath($text){ return $text; }
51 }
52
53 /**
54  * Internationalisation code
55  * @ingroup Language
56  */
57 class Language {
58         var $mConverter, $mVariants, $mCode, $mLoaded = false;
59         var $mMagicExtensions = array(), $mMagicHookDone = false;
60
61         var $mNamespaceIds, $namespaceNames, $namespaceAliases;
62         var $dateFormatStrings = array();
63         var $mExtendedSpecialPageAliases;
64
65         /**
66          * ReplacementArray object caches
67          */
68         var $transformData = array();
69
70         static public $dataCache;
71         static public $mLangObjCache = array();
72
73         static public $mWeekdayMsgs = array(
74                 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday',
75                 'friday', 'saturday'
76         );
77
78         static public $mWeekdayAbbrevMsgs = array(
79                 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'
80         );
81
82         static public $mMonthMsgs = array(
83                 'january', 'february', 'march', 'april', 'may_long', 'june',
84                 'july', 'august', 'september', 'october', 'november',
85                 'december'
86         );
87         static public $mMonthGenMsgs = array(
88                 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen',
89                 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen',
90                 'december-gen'
91         );
92         static public $mMonthAbbrevMsgs = array(
93                 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug',
94                 'sep', 'oct', 'nov', 'dec'
95         );
96
97         static public $mIranianCalendarMonthMsgs = array(
98                 'iranian-calendar-m1', 'iranian-calendar-m2', 'iranian-calendar-m3',
99                 'iranian-calendar-m4', 'iranian-calendar-m5', 'iranian-calendar-m6',
100                 'iranian-calendar-m7', 'iranian-calendar-m8', 'iranian-calendar-m9',
101                 'iranian-calendar-m10', 'iranian-calendar-m11', 'iranian-calendar-m12'
102         );
103
104         static public $mHebrewCalendarMonthMsgs = array(
105                 'hebrew-calendar-m1', 'hebrew-calendar-m2', 'hebrew-calendar-m3',
106                 'hebrew-calendar-m4', 'hebrew-calendar-m5', 'hebrew-calendar-m6',
107                 'hebrew-calendar-m7', 'hebrew-calendar-m8', 'hebrew-calendar-m9',
108                 'hebrew-calendar-m10', 'hebrew-calendar-m11', 'hebrew-calendar-m12',
109                 'hebrew-calendar-m6a', 'hebrew-calendar-m6b'
110         );
111
112         static public $mHebrewCalendarMonthGenMsgs = array(
113                 'hebrew-calendar-m1-gen', 'hebrew-calendar-m2-gen', 'hebrew-calendar-m3-gen',
114                 'hebrew-calendar-m4-gen', 'hebrew-calendar-m5-gen', 'hebrew-calendar-m6-gen',
115                 'hebrew-calendar-m7-gen', 'hebrew-calendar-m8-gen', 'hebrew-calendar-m9-gen',
116                 'hebrew-calendar-m10-gen', 'hebrew-calendar-m11-gen', 'hebrew-calendar-m12-gen',
117                 'hebrew-calendar-m6a-gen', 'hebrew-calendar-m6b-gen'
118         );
119
120         static public $mHijriCalendarMonthMsgs = array(
121                 'hijri-calendar-m1', 'hijri-calendar-m2', 'hijri-calendar-m3',
122                 'hijri-calendar-m4', 'hijri-calendar-m5', 'hijri-calendar-m6',
123                 'hijri-calendar-m7', 'hijri-calendar-m8', 'hijri-calendar-m9',
124                 'hijri-calendar-m10', 'hijri-calendar-m11', 'hijri-calendar-m12'
125         );
126
127         /**
128          * Get a cached language object for a given language code
129          */
130         static function factory( $code ) {
131                 if ( !isset( self::$mLangObjCache[$code] ) ) {
132                         if( count( self::$mLangObjCache ) > 10 ) {
133                                 // Don't keep a billion objects around, that's stupid.
134                                 self::$mLangObjCache = array();
135                         }
136                         self::$mLangObjCache[$code] = self::newFromCode( $code );
137                 }
138                 return self::$mLangObjCache[$code];
139         }
140
141         /**
142          * Create a language object for a given language code
143          */
144         protected static function newFromCode( $code ) {
145                 global $IP;
146                 static $recursionLevel = 0;
147
148                 // Protect against path traversal below
149                 if ( !Language::isValidCode( $code ) 
150                         || strcspn( $code, "/\\\000" ) !== strlen( $code ) ) 
151                 {
152                         throw new MWException( "Invalid language code \"$code\"" );
153                 }
154
155                 if ( $code == 'en' ) {
156                         $class = 'Language';
157                 } else {
158                         $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
159                         // Preload base classes to work around APC/PHP5 bug
160                         if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
161                                 include_once("$IP/languages/classes/$class.deps.php");
162                         }
163                         if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
164                                 include_once("$IP/languages/classes/$class.php");
165                         }
166                 }
167
168                 if ( $recursionLevel > 5 ) {
169                         throw new MWException( "Language fallback loop detected when creating class $class\n" );
170                 }
171
172                 if( ! class_exists( $class ) ) {
173                         $fallback = Language::getFallbackFor( $code );
174                         ++$recursionLevel;
175                         $lang = Language::newFromCode( $fallback );
176                         --$recursionLevel;
177                         $lang->setCode( $code );
178                 } else {
179                         $lang = new $class;
180                 }
181                 return $lang;
182         }
183
184         /**
185          * Returns true if a language code string is of a valid form, whether or 
186          * not it exists.
187          */
188         public static function isValidCode( $code ) {
189                 return strcspn( $code, "/\\\000" ) === strlen( $code );
190         }
191
192         /**
193          * Get the LocalisationCache instance
194          */
195         public static function getLocalisationCache() {
196                 if ( is_null( self::$dataCache ) ) {
197                         global $wgLocalisationCacheConf;
198                         $class = $wgLocalisationCacheConf['class'];
199                         self::$dataCache = new $class( $wgLocalisationCacheConf );
200                 }
201                 return self::$dataCache;
202         }
203
204         function __construct() {
205                 $this->mConverter = new FakeConverter($this);
206                 // Set the code to the name of the descendant
207                 if ( get_class( $this ) == 'Language' ) {
208                         $this->mCode = 'en';
209                 } else {
210                         $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
211                 }
212                 self::getLocalisationCache();
213         }
214
215         /**
216          * Reduce memory usage
217          */
218         function __destruct() {
219                 foreach ( $this as $name => $value ) {
220                         unset( $this->$name );
221                 }
222         }
223
224         /**
225          * Hook which will be called if this is the content language.
226          * Descendants can use this to register hook functions or modify globals
227          */
228         function initContLang() {}
229
230         /**
231          * @deprecated Use User::getDefaultOptions()
232          * @return array
233          */
234         function getDefaultUserOptions() {
235                 wfDeprecated( __METHOD__ );
236                 return User::getDefaultOptions();
237         }
238
239         function getFallbackLanguageCode() {
240                 if ( $this->mCode === 'en' ) {
241                         return false;
242                 } else {
243                         return self::$dataCache->getItem( $this->mCode, 'fallback' );
244                 }
245         }
246
247         /**
248          * Exports $wgBookstoreListEn
249          * @return array
250          */
251         function getBookstoreList() {
252                 return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
253         }
254
255         /**
256          * @return array
257          */
258         function getNamespaces() {
259                 if ( is_null( $this->namespaceNames ) ) {
260                         global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk;
261
262                         $this->namespaceNames = self::$dataCache->getItem( $this->mCode, 'namespaceNames' );
263                         if ( $wgExtraNamespaces ) {
264                                 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames;
265                         }
266
267                         $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
268                         if ( $wgMetaNamespaceTalk ) {
269                                 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
270                         } else {
271                                 $talk = $this->namespaceNames[NS_PROJECT_TALK];
272                                 $this->namespaceNames[NS_PROJECT_TALK] =
273                                         $this->fixVariableInNamespace( $talk );
274                         }
275
276                         # The above mixing may leave namespaces out of canonical order.
277                         # Re-order by namespace ID number...
278                         ksort( $this->namespaceNames );
279                 }
280                 return $this->namespaceNames;
281         }
282
283         /**
284          * A convenience function that returns the same thing as
285          * getNamespaces() except with the array values changed to ' '
286          * where it found '_', useful for producing output to be displayed
287          * e.g. in <select> forms.
288          *
289          * @return array
290          */
291         function getFormattedNamespaces() {
292                 $ns = $this->getNamespaces();
293                 foreach($ns as $k => $v) {
294                         $ns[$k] = strtr($v, '_', ' ');
295                 }
296                 return $ns;
297         }
298
299         /**
300          * Get a namespace value by key
301          * <code>
302          * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
303          * echo $mw_ns; // prints 'MediaWiki'
304          * </code>
305          *
306          * @param $index Int: the array key of the namespace to return
307          * @return mixed, string if the namespace value exists, otherwise false
308          */
309         function getNsText( $index ) {
310                 $ns = $this->getNamespaces();
311                 return isset( $ns[$index] ) ? $ns[$index] : false;
312         }
313
314         /**
315          * A convenience function that returns the same thing as
316          * getNsText() except with '_' changed to ' ', useful for
317          * producing output.
318          *
319          * @return array
320          */
321         function getFormattedNsText( $index ) {
322                 $ns = $this->getNsText( $index );
323                 return strtr($ns, '_', ' ');
324         }
325
326         /**
327          * Get a namespace key by value, case insensitive.
328          * Only matches namespace names for the current language, not the
329          * canonical ones defined in Namespace.php.
330          *
331          * @param $text String
332          * @return mixed An integer if $text is a valid value otherwise false
333          */
334         function getLocalNsIndex( $text ) {
335                 $lctext = $this->lc($text);
336                 $ids = $this->getNamespaceIds();
337                 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
338         }
339
340         function getNamespaceAliases() {
341                 if ( is_null( $this->namespaceAliases ) ) {
342                         $aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
343                         if ( !$aliases ) {
344                                 $aliases = array();
345                         } else {
346                                 foreach ( $aliases as $name => $index ) {
347                                         if ( $index === NS_PROJECT_TALK ) {
348                                                 unset( $aliases[$name] );
349                                                 $name = $this->fixVariableInNamespace( $name );
350                                                 $aliases[$name] = $index;
351                                         }
352                                 }
353                         }
354                         $this->namespaceAliases = $aliases;
355                 }
356                 return $this->namespaceAliases;
357         }
358
359         function getNamespaceIds() {
360                 if ( is_null( $this->mNamespaceIds ) ) {
361                         global $wgNamespaceAliases;
362                         # Put namespace names and aliases into a hashtable.
363                         # If this is too slow, then we should arrange it so that it is done
364                         # before caching. The catch is that at pre-cache time, the above
365                         # class-specific fixup hasn't been done.
366                         $this->mNamespaceIds = array();
367                         foreach ( $this->getNamespaces() as $index => $name ) {
368                                 $this->mNamespaceIds[$this->lc($name)] = $index;
369                         }
370                         foreach ( $this->getNamespaceAliases() as $name => $index ) {
371                                 $this->mNamespaceIds[$this->lc($name)] = $index;
372                         }
373                         if ( $wgNamespaceAliases ) {
374                                 foreach ( $wgNamespaceAliases as $name => $index ) {
375                                         $this->mNamespaceIds[$this->lc($name)] = $index;
376                                 }
377                         }
378                 }
379                 return $this->mNamespaceIds;
380         }
381
382
383         /**
384          * Get a namespace key by value, case insensitive.  Canonical namespace
385          * names override custom ones defined for the current language.
386          *
387          * @param $text String
388          * @return mixed An integer if $text is a valid value otherwise false
389          */
390         function getNsIndex( $text ) {
391                 $lctext = $this->lc($text);
392                 if ( ( $ns = MWNamespace::getCanonicalIndex( $lctext ) ) !== null ) {
393                         return $ns;
394                 }
395                 $ids = $this->getNamespaceIds();
396                 return isset( $ids[$lctext] ) ? $ids[$lctext] : false;
397         }
398
399         /**
400          * short names for language variants used for language conversion links.
401          *
402          * @param $code String
403          * @return string
404          */
405         function getVariantname( $code ) {
406                 return $this->getMessageFromDB( "variantname-$code" );
407         }
408
409         function specialPage( $name ) {
410                 $aliases = $this->getSpecialPageAliases();
411                 if ( isset( $aliases[$name][0] ) ) {
412                         $name = $aliases[$name][0];
413                 }
414                 return $this->getNsText( NS_SPECIAL ) . ':' . $name;
415         }
416
417         function getQuickbarSettings() {
418                 return array(
419                         $this->getMessage( 'qbsettings-none' ),
420                         $this->getMessage( 'qbsettings-fixedleft' ),
421                         $this->getMessage( 'qbsettings-fixedright' ),
422                         $this->getMessage( 'qbsettings-floatingleft' ),
423                         $this->getMessage( 'qbsettings-floatingright' )
424                 );
425         }
426
427         function getMathNames() {
428                 return self::$dataCache->getItem( $this->mCode, 'mathNames' );
429         }
430
431         function getDatePreferences() {
432                 return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
433         }
434
435         function getDateFormats() {
436                 return self::$dataCache->getItem( $this->mCode, 'dateFormats' );
437         }
438
439         function getDefaultDateFormat() {
440                 $df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
441                 if ( $df === 'dmy or mdy' ) {
442                         global $wgAmericanDates;
443                         return $wgAmericanDates ? 'mdy' : 'dmy';
444                 } else {
445                         return $df;
446                 }
447         }
448
449         function getDatePreferenceMigrationMap() {
450                 return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
451         }
452
453         function getImageFile( $image ) {
454                 return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
455         }
456
457         function getDefaultUserOptionOverrides() {
458                 return self::$dataCache->getItem( $this->mCode, 'defaultUserOptionOverrides' );
459         }
460
461         function getExtraUserToggles() {
462                 return self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
463         }
464
465         function getUserToggle( $tog ) {
466                 return $this->getMessageFromDB( "tog-$tog" );
467         }
468
469         /**
470          * Get language names, indexed by code.
471          * If $customisedOnly is true, only returns codes with a messages file
472          */
473         public static function getLanguageNames( $customisedOnly = false ) {
474                 global $wgLanguageNames, $wgExtraLanguageNames;
475                 $allNames = $wgExtraLanguageNames + $wgLanguageNames;
476                 if ( !$customisedOnly ) {
477                         return $allNames;
478                 }
479
480                 global $IP;
481                 $names = array();
482                 $dir = opendir( "$IP/languages/messages" );
483                 while( false !== ( $file = readdir( $dir ) ) ) {
484                         $code = self::getCodeFromFileName( $file, 'Messages' );
485                         if ( $code && isset( $allNames[$code] ) ) {
486                                 $names[$code] = $allNames[$code];
487                         }
488                 }
489                 closedir( $dir );
490                 return $names;
491         }
492
493         /**
494          * Get a message from the MediaWiki namespace.
495          *
496          * @param $msg String: message name
497          * @return string
498          */
499         function getMessageFromDB( $msg ) {
500                 return wfMsgExt( $msg, array( 'parsemag', 'language' => $this ) );
501         }
502
503         function getLanguageName( $code ) {
504                 $names = self::getLanguageNames();
505                 if ( !array_key_exists( $code, $names ) ) {
506                         return '';
507                 }
508                 return $names[$code];
509         }
510
511         function getMonthName( $key ) {
512                 return $this->getMessageFromDB( self::$mMonthMsgs[$key-1] );
513         }
514
515         function getMonthNameGen( $key ) {
516                 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key-1] );
517         }
518
519         function getMonthAbbreviation( $key ) {
520                 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key-1] );
521         }
522
523         function getWeekdayName( $key ) {
524                 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key-1] );
525         }
526
527         function getWeekdayAbbreviation( $key ) {
528                 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key-1] );
529         }
530
531         function getIranianCalendarMonthName( $key ) {
532                 return $this->getMessageFromDB( self::$mIranianCalendarMonthMsgs[$key-1] );
533         }
534
535         function getHebrewCalendarMonthName( $key ) {
536                 return $this->getMessageFromDB( self::$mHebrewCalendarMonthMsgs[$key-1] );
537         }
538
539         function getHebrewCalendarMonthNameGen( $key ) {
540                 return $this->getMessageFromDB( self::$mHebrewCalendarMonthGenMsgs[$key-1] );
541         }
542
543         function getHijriCalendarMonthName( $key ) {
544                 return $this->getMessageFromDB( self::$mHijriCalendarMonthMsgs[$key-1] );
545         }
546
547         /**
548          * Used by date() and time() to adjust the time output.
549          *
550          * @param $ts Int the time in date('YmdHis') format
551          * @param $tz Mixed: adjust the time by this amount (default false, mean we
552          *            get user timecorrection setting)
553          * @return int
554          */
555         function userAdjust( $ts, $tz = false ) {
556                 global $wgUser, $wgLocalTZoffset;
557
558                 if ( $tz === false ) {
559                         $tz = $wgUser->getOption( 'timecorrection' );
560                 }
561
562                 $data = explode( '|', $tz, 3 );
563
564                 if ( $data[0] == 'ZoneInfo' ) {
565                         if ( function_exists( 'timezone_open' ) && @timezone_open( $data[2] ) !== false ) {
566                                 $date = date_create( $ts, timezone_open( 'UTC' ) );
567                                 date_timezone_set( $date, timezone_open( $data[2] ) );
568                                 $date = date_format( $date, 'YmdHis' );
569                                 return $date;
570                         }
571                         # Unrecognized timezone, default to 'Offset' with the stored offset.
572                         $data[0] = 'Offset';
573                 }
574
575                 $minDiff = 0;
576                 if ( $data[0] == 'System' || $tz == '' ) {
577                         # Global offset in minutes.
578                         if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset;
579                 } else if ( $data[0] == 'Offset' ) {
580                         $minDiff = intval( $data[1] );
581                 } else {
582                         $data = explode( ':', $tz );
583                         if( count( $data ) == 2 ) {
584                                 $data[0] = intval( $data[0] );
585                                 $data[1] = intval( $data[1] );
586                                 $minDiff = abs( $data[0] ) * 60 + $data[1];
587                                 if ( $data[0] < 0 ) $minDiff = -$minDiff;
588                         } else {
589                                 $minDiff = intval( $data[0] ) * 60;
590                         }
591                 }
592
593                 # No difference ? Return time unchanged
594                 if ( 0 == $minDiff ) return $ts;
595
596                 wfSuppressWarnings(); // E_STRICT system time bitching
597                 # Generate an adjusted date; take advantage of the fact that mktime
598                 # will normalize out-of-range values so we don't have to split $minDiff
599                 # into hours and minutes.
600                 $t = mktime( (
601                   (int)substr( $ts, 8, 2) ), # Hours
602                   (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
603                   (int)substr( $ts, 12, 2 ), # Seconds
604                   (int)substr( $ts, 4, 2 ), # Month
605                   (int)substr( $ts, 6, 2 ), # Day
606                   (int)substr( $ts, 0, 4 ) ); #Year
607
608                 $date = date( 'YmdHis', $t );
609                 wfRestoreWarnings();
610
611                 return $date;
612         }
613
614         /**
615          * This is a workalike of PHP's date() function, but with better
616          * internationalisation, a reduced set of format characters, and a better
617          * escaping format.
618          *
619          * Supported format characters are dDjlNwzWFmMntLoYyaAgGhHiscrU. See the
620          * PHP manual for definitions. "o" format character is supported since
621          * PHP 5.1.0, previous versions return literal o.
622          * There are a number of extensions, which start with "x":
623          *
624          *    xn   Do not translate digits of the next numeric format character
625          *    xN   Toggle raw digit (xn) flag, stays set until explicitly unset
626          *    xr   Use roman numerals for the next numeric format character
627          *    xh   Use hebrew numerals for the next numeric format character
628          *    xx   Literal x
629          *    xg   Genitive month name
630          *
631          *    xij  j (day number) in Iranian calendar
632          *    xiF  F (month name) in Iranian calendar
633          *    xin  n (month number) in Iranian calendar
634          *    xiY  Y (full year) in Iranian calendar
635          *
636          *    xjj  j (day number) in Hebrew calendar
637          *    xjF  F (month name) in Hebrew calendar
638          *    xjt  t (days in month) in Hebrew calendar
639          *    xjx  xg (genitive month name) in Hebrew calendar
640          *    xjn  n (month number) in Hebrew calendar
641          *    xjY  Y (full year) in Hebrew calendar
642          *
643          *    xmj  j (day number) in Hijri calendar
644          *    xmF  F (month name) in Hijri calendar
645          *    xmn  n (month number) in Hijri calendar
646          *    xmY  Y (full year) in Hijri calendar
647          *
648          *    xkY  Y (full year) in Thai solar calendar. Months and days are
649          *                       identical to the Gregorian calendar
650          *    xoY  Y (full year) in Minguo calendar or Juche year.
651          *                       Months and days are identical to the
652          *                       Gregorian calendar
653          *    xtY  Y (full year) in Japanese nengo. Months and days are
654          *                       identical to the Gregorian calendar
655          *
656          * Characters enclosed in double quotes will be considered literal (with
657          * the quotes themselves removed). Unmatched quotes will be considered
658          * literal quotes. Example:
659          *
660          * "The month is" F       => The month is January
661          * i's"                   => 20'11"
662          *
663          * Backslash escaping is also supported.
664          *
665          * Input timestamp is assumed to be pre-normalized to the desired local
666          * time zone, if any.
667          *
668          * @param $format String
669          * @param $ts String: 14-character timestamp
670          *      YYYYMMDDHHMMSS
671          *      01234567890123
672          * @todo emulation of "o" format character for PHP pre 5.1.0
673          * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
674          */
675         function sprintfDate( $format, $ts ) {
676                 $s = '';
677                 $raw = false;
678                 $roman = false;
679                 $hebrewNum = false;
680                 $unix = false;
681                 $rawToggle = false;
682                 $iranian = false;
683                 $hebrew = false;
684                 $hijri = false;
685                 $thai = false;
686                 $minguo = false;
687                 $tenno = false;
688                 for ( $p = 0; $p < strlen( $format ); $p++ ) {
689                         $num = false;
690                         $code = $format[$p];
691                         if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
692                                 $code .= $format[++$p];
693                         }
694
695                         if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' || $code == 'xo' || $code == 'xt' ) && $p < strlen( $format ) - 1 ) {
696                                 $code .= $format[++$p];
697                         }
698
699                         switch ( $code ) {
700                                 case 'xx':
701                                         $s .= 'x';
702                                         break;
703                                 case 'xn':
704                                         $raw = true;
705                                         break;
706                                 case 'xN':
707                                         $rawToggle = !$rawToggle;
708                                         break;
709                                 case 'xr':
710                                         $roman = true;
711                                         break;
712                                 case 'xh':
713                                         $hebrewNum = true;
714                                         break;
715                                 case 'xg':
716                                         $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
717                                         break;
718                                 case 'xjx':
719                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
720                                         $s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
721                                         break;
722                                 case 'd':
723                                         $num = substr( $ts, 6, 2 );
724                                         break;
725                                 case 'D':
726                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
727                                         $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) + 1 );
728                                         break;
729                                 case 'j':
730                                         $num = intval( substr( $ts, 6, 2 ) );
731                                         break;
732                                 case 'xij':
733                                         if ( !$iranian ) $iranian = self::tsToIranian( $ts );
734                                         $num = $iranian[2];
735                                         break;
736                                 case 'xmj':
737                                         if ( !$hijri ) $hijri = self::tsToHijri( $ts );
738                                         $num = $hijri[2];
739                                         break;
740                                 case 'xjj':
741                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
742                                         $num = $hebrew[2];
743                                         break;
744                                 case 'l':
745                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
746                                         $s .= $this->getWeekdayName( gmdate( 'w', $unix ) + 1 );
747                                         break;
748                                 case 'N':
749                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
750                                         $w = gmdate( 'w', $unix );
751                                         $num = $w ? $w : 7;
752                                         break;
753                                 case 'w':
754                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
755                                         $num = gmdate( 'w', $unix );
756                                         break;
757                                 case 'z':
758                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
759                                         $num = gmdate( 'z', $unix );
760                                         break;
761                                 case 'W':
762                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
763                                         $num = gmdate( 'W', $unix );
764                                         break;
765                                 case 'F':
766                                         $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
767                                         break;
768                                 case 'xiF':
769                                         if ( !$iranian ) $iranian = self::tsToIranian( $ts );
770                                         $s .= $this->getIranianCalendarMonthName( $iranian[1] );
771                                         break;
772                                 case 'xmF':
773                                         if ( !$hijri ) $hijri = self::tsToHijri( $ts );
774                                         $s .= $this->getHijriCalendarMonthName( $hijri[1] );
775                                         break;
776                                 case 'xjF':
777                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
778                                         $s .= $this->getHebrewCalendarMonthName( $hebrew[1] );
779                                         break;
780                                 case 'm':
781                                         $num = substr( $ts, 4, 2 );
782                                         break;
783                                 case 'M':
784                                         $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
785                                         break;
786                                 case 'n':
787                                         $num = intval( substr( $ts, 4, 2 ) );
788                                         break;
789                                 case 'xin':
790                                         if ( !$iranian ) $iranian = self::tsToIranian( $ts );
791                                         $num = $iranian[1];
792                                         break;
793                                 case 'xmn':
794                                         if ( !$hijri ) $hijri = self::tsToHijri ( $ts );
795                                         $num = $hijri[1];
796                                         break;
797                                 case 'xjn':
798                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
799                                         $num = $hebrew[1];
800                                         break;
801                                 case 't':
802                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
803                                         $num = gmdate( 't', $unix );
804                                         break;
805                                 case 'xjt':
806                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
807                                         $num = $hebrew[3];
808                                         break;
809                                 case 'L':
810                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
811                                         $num = gmdate( 'L', $unix );
812                                         break;
813                                 # 'o' is supported since PHP 5.1.0
814                                 # return literal if not supported
815                                 # TODO: emulation for pre 5.1.0 versions
816                                 case 'o':
817                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
818                                         if ( version_compare(PHP_VERSION, '5.1.0') === 1 )
819                                                 $num = date( 'o', $unix );
820                                         else
821                                                 $s .= 'o';
822                                         break;
823                                 case 'Y':
824                                         $num = substr( $ts, 0, 4 );
825                                         break;
826                                 case 'xiY':
827                                         if ( !$iranian ) $iranian = self::tsToIranian( $ts );
828                                         $num = $iranian[0];
829                                         break;
830                                 case 'xmY':
831                                         if ( !$hijri ) $hijri = self::tsToHijri( $ts );
832                                         $num = $hijri[0];
833                                         break;
834                                 case 'xjY':
835                                         if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
836                                         $num = $hebrew[0];
837                                         break;
838                                 case 'xkY':
839                                         if ( !$thai ) $thai = self::tsToYear( $ts, 'thai' );
840                                         $num = $thai[0];
841                                         break;
842                                 case 'xoY':
843                                         if ( !$minguo ) $minguo = self::tsToYear( $ts, 'minguo' );
844                                         $num = $minguo[0];
845                                         break;
846                                 case 'xtY':
847                                         if ( !$tenno ) $tenno = self::tsToYear( $ts, 'tenno' );
848                                         $num = $tenno[0];
849                                         break;
850                                 case 'y':
851                                         $num = substr( $ts, 2, 2 );
852                                         break;
853                                 case 'a':
854                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
855                                         break;
856                                 case 'A':
857                                         $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
858                                         break;
859                                 case 'g':
860                                         $h = substr( $ts, 8, 2 );
861                                         $num = $h % 12 ? $h % 12 : 12;
862                                         break;
863                                 case 'G':
864                                         $num = intval( substr( $ts, 8, 2 ) );
865                                         break;
866                                 case 'h':
867                                         $h = substr( $ts, 8, 2 );
868                                         $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
869                                         break;
870                                 case 'H':
871                                         $num = substr( $ts, 8, 2 );
872                                         break;
873                                 case 'i':
874                                         $num = substr( $ts, 10, 2 );
875                                         break;
876                                 case 's':
877                                         $num = substr( $ts, 12, 2 );
878                                         break;
879                                 case 'c':
880                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
881                                         $s .= gmdate( 'c', $unix );
882                                         break;
883                                 case 'r':
884                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
885                                         $s .= gmdate( 'r', $unix );
886                                         break;
887                                 case 'U':
888                                         if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
889                                         $num = $unix;
890                                         break;
891                                 case '\\':
892                                         # Backslash escaping
893                                         if ( $p < strlen( $format ) - 1 ) {
894                                                 $s .= $format[++$p];
895                                         } else {
896                                                 $s .= '\\';
897                                         }
898                                         break;
899                                 case '"':
900                                         # Quoted literal
901                                         if ( $p < strlen( $format ) - 1 ) {
902                                                 $endQuote = strpos( $format, '"', $p + 1 );
903                                                 if ( $endQuote === false ) {
904                                                         # No terminating quote, assume literal "
905                                                         $s .= '"';
906                                                 } else {
907                                                         $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
908                                                         $p = $endQuote;
909                                                 }
910                                         } else {
911                                                 # Quote at end of string, assume literal "
912                                                 $s .= '"';
913                                         }
914                                         break;
915                                 default:
916                                         $s .= $format[$p];
917                         }
918                         if ( $num !== false ) {
919                                 if ( $rawToggle || $raw ) {
920                                         $s .= $num;
921                                         $raw = false;
922                                 } elseif ( $roman ) {
923                                         $s .= self::romanNumeral( $num );
924                                         $roman = false;
925                                 } elseif( $hebrewNum ) {
926                                         $s .= self::hebrewNumeral( $num );
927                                         $hebrewNum = false;
928                                 } else {
929                                         $s .= $this->formatNum( $num, true );
930                                 }
931                                 $num = false;
932                         }
933                 }
934                 return $s;
935         }
936
937         private static $GREG_DAYS = array( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
938         private static $IRANIAN_DAYS = array( 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 );
939         /**
940          * Algorithm by Roozbeh Pournader and Mohammad Toossi to convert
941          * Gregorian dates to Iranian dates. Originally written in C, it
942          * is released under the terms of GNU Lesser General Public
943          * License. Conversion to PHP was performed by Niklas Laxström.
944          *
945          * Link: http://www.farsiweb.info/jalali/jalali.c
946          */
947         private static function tsToIranian( $ts ) {
948                 $gy = substr( $ts, 0, 4 ) -1600;
949                 $gm = substr( $ts, 4, 2 ) -1;
950                 $gd = substr( $ts, 6, 2 ) -1;
951
952                 # Days passed from the beginning (including leap years)
953                 $gDayNo = 365*$gy
954                         + floor(($gy+3) / 4)
955                         - floor(($gy+99) / 100)
956                         + floor(($gy+399) / 400);
957
958
959                 // Add days of the past months of this year
960                 for( $i = 0; $i < $gm; $i++ ) {
961                         $gDayNo += self::$GREG_DAYS[$i];
962                 }
963
964                 // Leap years
965                 if ( $gm > 1 && (($gy%4===0 && $gy%100!==0 || ($gy%400==0)))) {
966                         $gDayNo++;
967                 }
968
969                 // Days passed in current month
970                 $gDayNo += $gd;
971
972                 $jDayNo = $gDayNo - 79;
973
974                 $jNp = floor($jDayNo / 12053);
975                 $jDayNo %= 12053;
976
977                 $jy = 979 + 33*$jNp + 4*floor($jDayNo/1461);
978                 $jDayNo %= 1461;
979
980                 if ( $jDayNo >= 366 ) {
981                         $jy += floor(($jDayNo-1)/365);
982                         $jDayNo = floor(($jDayNo-1)%365);
983                 }
984
985                 for ( $i = 0; $i < 11 && $jDayNo >= self::$IRANIAN_DAYS[$i]; $i++ ) {
986                         $jDayNo -= self::$IRANIAN_DAYS[$i];
987                 }
988
989                 $jm= $i+1;
990                 $jd= $jDayNo+1;
991
992                 return array($jy, $jm, $jd);
993         }
994         /**
995          * Converting Gregorian dates to Hijri dates.
996          *
997          * Based on a PHP-Nuke block by Sharjeel which is released under GNU/GPL license
998          *
999          * @link http://phpnuke.org/modules.php?name=News&file=article&sid=8234&mode=thread&order=0&thold=0
1000          */
1001         private static function tsToHijri ( $ts ) {
1002                 $year = substr( $ts, 0, 4 );
1003                 $month = substr( $ts, 4, 2 );
1004                 $day = substr( $ts, 6, 2 );
1005
1006                 $zyr = $year;
1007                 $zd=$day;
1008                 $zm=$month;
1009                 $zy=$zyr;
1010
1011
1012
1013                 if (($zy>1582)||(($zy==1582)&&($zm>10))||(($zy==1582)&&($zm==10)&&($zd>14)))
1014                         {
1015
1016
1017                                     $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;
1018                                     }
1019                  else
1020                         {
1021                                     $zjd = 367*$zy-(int)((7*($zy+5001+(int)(($zm-9)/7)))/4)+(int)((275*$zm)/9)+$zd+1729777;
1022                         }
1023
1024                 $zl=$zjd-1948440+10632;
1025                 $zn=(int)(($zl-1)/10631);
1026                 $zl=$zl-10631*$zn+354;
1027                 $zj=((int)((10985-$zl)/5316))*((int)((50*$zl)/17719))+((int)($zl/5670))*((int)((43*$zl)/15238));
1028                 $zl=$zl-((int)((30-$zj)/15))*((int)((17719*$zj)/50))-((int)($zj/16))*((int)((15238*$zj)/43))+29;
1029                 $zm=(int)((24*$zl)/709);
1030                 $zd=$zl-(int)((709*$zm)/24);
1031                 $zy=30*$zn+$zj-30;
1032
1033                 return array ($zy, $zm, $zd);
1034         }
1035
1036         /**
1037          * Converting Gregorian dates to Hebrew dates.
1038          *
1039          * Based on a JavaScript code by Abu Mami and Yisrael Hersch
1040          * (abu-mami@kaluach.net, http://www.kaluach.net), who permitted
1041          * to translate the relevant functions into PHP and release them under
1042          * GNU GPL.
1043          *
1044          * The months are counted from Tishrei = 1. In a leap year, Adar I is 13
1045          * and Adar II is 14. In a non-leap year, Adar is 6.
1046          */
1047         private static function tsToHebrew( $ts ) {
1048                 # Parse date
1049                 $year = substr( $ts, 0, 4 );
1050                 $month = substr( $ts, 4, 2 );
1051                 $day = substr( $ts, 6, 2 );
1052
1053                 # Calculate Hebrew year
1054                 $hebrewYear = $year + 3760;
1055
1056                 # Month number when September = 1, August = 12
1057                 $month += 4;
1058                 if( $month > 12 ) {
1059                         # Next year
1060                         $month -= 12;
1061                         $year++;
1062                         $hebrewYear++;
1063                 }
1064
1065                 # Calculate day of year from 1 September
1066                 $dayOfYear = $day;
1067                 for( $i = 1; $i < $month; $i++ ) {
1068                         if( $i == 6 ) {
1069                                 # February
1070                                 $dayOfYear += 28;
1071                                 # Check if the year is leap
1072                                 if( $year % 400 == 0 || ( $year % 4 == 0 && $year % 100 > 0 ) ) {
1073                                         $dayOfYear++;
1074                                 }
1075                         } elseif( $i == 8 || $i == 10 || $i == 1 || $i == 3 ) {
1076                                 $dayOfYear += 30;
1077                         } else {
1078                                 $dayOfYear += 31;
1079                         }
1080                 }
1081
1082                 # Calculate the start of the Hebrew year
1083                 $start = self::hebrewYearStart( $hebrewYear );
1084
1085                 # Calculate next year's start
1086                 if( $dayOfYear <= $start ) {
1087                         # Day is before the start of the year - it is the previous year
1088                         # Next year's start
1089                         $nextStart = $start;
1090                         # Previous year
1091                         $year--;
1092                         $hebrewYear--;
1093                         # Add days since previous year's 1 September
1094                         $dayOfYear += 365;
1095                         if( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1096                                 # Leap year
1097                                 $dayOfYear++;
1098                         }
1099                         # Start of the new (previous) year
1100                         $start = self::hebrewYearStart( $hebrewYear );
1101                 } else {
1102                         # Next year's start
1103                         $nextStart = self::hebrewYearStart( $hebrewYear + 1 );
1104                 }
1105
1106                 # Calculate Hebrew day of year
1107                 $hebrewDayOfYear = $dayOfYear - $start;
1108
1109                 # Difference between year's days
1110                 $diff = $nextStart - $start;
1111                 # Add 12 (or 13 for leap years) days to ignore the difference between
1112                 # Hebrew and Gregorian year (353 at least vs. 365/6) - now the
1113                 # difference is only about the year type
1114                 if( ( $year % 400 == 0 ) || ( $year % 100 != 0 && $year % 4 == 0 ) ) {
1115                         $diff += 13;
1116                 } else {
1117                         $diff += 12;
1118                 }
1119
1120                 # Check the year pattern, and is leap year
1121                 # 0 means an incomplete year, 1 means a regular year, 2 means a complete year
1122                 # This is mod 30, to work on both leap years (which add 30 days of Adar I)
1123                 # and non-leap years
1124                 $yearPattern = $diff % 30;
1125                 # Check if leap year
1126                 $isLeap = $diff >= 30;
1127
1128                 # Calculate day in the month from number of day in the Hebrew year
1129                 # Don't check Adar - if the day is not in Adar, we will stop before;
1130                 # if it is in Adar, we will use it to check if it is Adar I or Adar II
1131                 $hebrewDay = $hebrewDayOfYear;
1132                 $hebrewMonth = 1;
1133                 $days = 0;
1134                 while( $hebrewMonth <= 12 ) {
1135                         # Calculate days in this month
1136                         if( $isLeap && $hebrewMonth == 6 ) {
1137                                 # Adar in a leap year
1138                                 if( $isLeap ) {
1139                                         # Leap year - has Adar I, with 30 days, and Adar II, with 29 days
1140                                         $days = 30;
1141                                         if( $hebrewDay <= $days ) {
1142                                                 # Day in Adar I
1143                                                 $hebrewMonth = 13;
1144                                         } else {
1145                                                 # Subtract the days of Adar I
1146                                                 $hebrewDay -= $days;
1147                                                 # Try Adar II
1148                                                 $days = 29;
1149                                                 if( $hebrewDay <= $days ) {
1150                                                         # Day in Adar II
1151                                                         $hebrewMonth = 14;
1152                                                 }
1153                                         }
1154                                 }
1155                         } elseif( $hebrewMonth == 2 && $yearPattern == 2 ) {
1156                                 # Cheshvan in a complete year (otherwise as the rule below)
1157                                 $days = 30;
1158                         } elseif( $hebrewMonth == 3 && $yearPattern == 0 ) {
1159                                 # Kislev in an incomplete year (otherwise as the rule below)
1160                                 $days = 29;
1161                         } else {
1162                                 # Odd months have 30 days, even have 29
1163                                 $days = 30 - ( $hebrewMonth - 1 ) % 2;
1164                         }
1165                         if( $hebrewDay <= $days ) {
1166                                 # In the current month
1167                                 break;
1168                         } else {
1169                                 # Subtract the days of the current month
1170                                 $hebrewDay -= $days;
1171                                 # Try in the next month
1172                                 $hebrewMonth++;
1173                         }
1174                 }
1175
1176                 return array( $hebrewYear, $hebrewMonth, $hebrewDay, $days );
1177         }
1178
1179         /**
1180          * This calculates the Hebrew year start, as days since 1 September.
1181          * Based on Carl Friedrich Gauss algorithm for finding Easter date.
1182          * Used for Hebrew date.
1183          */
1184         private static function hebrewYearStart( $year ) {
1185                 $a = intval( ( 12 * ( $year - 1 ) + 17 ) % 19 );
1186                 $b = intval( ( $year - 1 ) % 4 );
1187                 $m = 32.044093161144 + 1.5542417966212 * $a +  $b / 4.0 - 0.0031777940220923 * ( $year - 1 );
1188                 if( $m < 0 ) {
1189                         $m--;
1190                 }
1191                 $Mar = intval( $m );
1192                 if( $m < 0 ) {
1193                         $m++;
1194                 }
1195                 $m -= $Mar;
1196
1197                 $c = intval( ( $Mar + 3 * ( $year - 1 ) + 5 * $b + 5 ) % 7);
1198                 if( $c == 0 && $a > 11 && $m >= 0.89772376543210 ) {
1199                         $Mar++;
1200                 } else if( $c == 1 && $a > 6 && $m >= 0.63287037037037 ) {
1201                         $Mar += 2;
1202                 } else if( $c == 2 || $c == 4 || $c == 6 ) {
1203                         $Mar++;
1204                 }
1205
1206                 $Mar += intval( ( $year - 3761 ) / 100 ) - intval( ( $year - 3761 ) / 400 ) - 24;
1207                 return $Mar;
1208         }
1209
1210         /**
1211          * Algorithm to convert Gregorian dates to Thai solar dates,
1212          * Minguo dates or Minguo dates.
1213          *
1214          * Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
1215          *       http://en.wikipedia.org/wiki/Minguo_calendar
1216          *       http://en.wikipedia.org/wiki/Japanese_era_name
1217          *
1218          * @param $ts String: 14-character timestamp, calender name
1219          * @return array converted year, month, day
1220          */
1221         private static function tsToYear( $ts, $cName ) {
1222                 $gy = substr( $ts, 0, 4 );
1223                 $gm = substr( $ts, 4, 2 );
1224                 $gd = substr( $ts, 6, 2 );
1225
1226                 if (!strcmp($cName,'thai')) {
1227                         # Thai solar dates
1228                         # Add 543 years to the Gregorian calendar
1229                         # Months and days are identical
1230                         $gy_offset = $gy + 543;
1231                 } else if ((!strcmp($cName,'minguo')) || !strcmp($cName,'juche')) {
1232                         # Minguo dates
1233                         # Deduct 1911 years from the Gregorian calendar
1234                         # Months and days are identical
1235                         $gy_offset = $gy - 1911;
1236                 } else if (!strcmp($cName,'tenno')) {
1237                         # Nengō dates up to Meiji period
1238                         # Deduct years from the Gregorian calendar
1239                         # depending on the nengo periods
1240                         # Months and days are identical
1241                         if (($gy < 1912) || (($gy == 1912) && ($gm < 7)) || (($gy == 1912) && ($gm == 7) && ($gd < 31))) {
1242                                 # Meiji period
1243                                 $gy_gannen = $gy - 1868 + 1;
1244                                 $gy_offset = $gy_gannen;
1245                                 if ($gy_gannen == 1)
1246                                         $gy_offset = '元';
1247                                 $gy_offset = '明治'.$gy_offset;
1248                         } 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))) {
1249                                 # Taishō period
1250                                 $gy_gannen = $gy - 1912 + 1;
1251                                 $gy_offset = $gy_gannen;
1252                                 if ($gy_gannen == 1)
1253                                         $gy_offset = '元';
1254                                 $gy_offset = '大正'.$gy_offset;
1255                         } else if ((($gy == 1926) && ($gm == 12) && ($gd >= 26)) || (($gy > 1926) && ($gy < 1989)) || (($gy == 1989) && ($gm == 1) && ($gd < 8))) {
1256                                 # Shōwa period
1257                                 $gy_gannen = $gy - 1926 + 1;
1258                                 $gy_offset = $gy_gannen;
1259                                 if ($gy_gannen == 1)
1260                                         $gy_offset = '元';
1261                                 $gy_offset = '昭和'.$gy_offset;
1262                         } else {
1263                                 # Heisei period
1264                                 $gy_gannen = $gy - 1989 + 1;
1265                                 $gy_offset = $gy_gannen;
1266                                 if ($gy_gannen == 1)
1267                                         $gy_offset = '元';
1268                                 $gy_offset = '平成'.$gy_offset;
1269                         }
1270                 } else {
1271                         $gy_offset = $gy;
1272                 }
1273
1274                 return array( $gy_offset, $gm, $gd );
1275         }
1276
1277         /**
1278          * Roman number formatting up to 3000
1279          */
1280         static function romanNumeral( $num ) {
1281                 static $table = array(
1282                         array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1283                         array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1284                         array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1285                         array( '', 'M', 'MM', 'MMM' )
1286                 );
1287
1288                 $num = intval( $num );
1289                 if ( $num > 3000 || $num <= 0 ) {
1290                         return $num;
1291                 }
1292
1293                 $s = '';
1294                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1295                         if ( $num >= $pow10 ) {
1296                                 $s .= $table[$i][floor($num / $pow10)];
1297                         }
1298                         $num = $num % $pow10;
1299                 }
1300                 return $s;
1301         }
1302
1303         /**
1304          * Hebrew Gematria number formatting up to 9999
1305          */
1306         static function hebrewNumeral( $num ) {
1307                 static $table = array(
1308                         array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1309                         array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1310                         array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1311                         array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1312                 );
1313
1314                 $num = intval( $num );
1315                 if ( $num > 9999 || $num <= 0 ) {
1316                         return $num;
1317                 }
1318
1319                 $s = '';
1320                 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1321                         if ( $num >= $pow10 ) {
1322                                 if ( $num == 15 || $num == 16 ) {
1323                                         $s .= $table[0][9] . $table[0][$num - 9];
1324                                         $num = 0;
1325                                 } else {
1326                                         $s .= $table[$i][intval( ( $num / $pow10 ) )];
1327                                         if( $pow10 == 1000 ) {
1328                                                 $s .= "'";
1329                                         }
1330                                 }
1331                         }
1332                         $num = $num % $pow10;
1333                 }
1334                 if( strlen( $s ) == 2 ) {
1335                         $str = $s . "'";
1336                 } else  {
1337                         $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1338                         $str .= substr( $s, strlen( $s ) - 2, 2 );
1339                 }
1340                 $start = substr( $str, 0, strlen( $str ) - 2 );
1341                 $end = substr( $str, strlen( $str ) - 2 );
1342                 switch( $end ) {
1343                         case 'כ':
1344                                 $str = $start . 'ך';
1345                                 break;
1346                         case 'מ':
1347                                 $str = $start . 'ם';
1348                                 break;
1349                         case 'נ':
1350                                 $str = $start . 'ן';
1351                                 break;
1352                         case 'פ':
1353                                 $str = $start . 'ף';
1354                                 break;
1355                         case 'צ':
1356                                 $str = $start . 'ץ';
1357                                 break;
1358                 }
1359                 return $str;
1360         }
1361
1362         /**
1363          * This is meant to be used by time(), date(), and timeanddate() to get
1364          * the date preference they're supposed to use, it should be used in
1365          * all children.
1366          *
1367          *<code>
1368          * function timeanddate([...], $format = true) {
1369          *      $datePreference = $this->dateFormat($format);
1370          * [...]
1371          * }
1372          *</code>
1373          *
1374          * @param $usePrefs Mixed: if true, the user's preference is used
1375          *                         if false, the site/language default is used
1376          *                         if int/string, assumed to be a format.
1377          * @return string
1378          */
1379         function dateFormat( $usePrefs = true ) {
1380                 global $wgUser;
1381
1382                 if( is_bool( $usePrefs ) ) {
1383                         if( $usePrefs ) {
1384                                 $datePreference = $wgUser->getDatePreference();
1385                         } else {
1386                                 $options = User::getDefaultOptions();
1387                                 $datePreference = (string)$options['date'];
1388                         }
1389                 } else {
1390                         $datePreference = (string)$usePrefs;
1391                 }
1392
1393                 // return int
1394                 if( $datePreference == '' ) {
1395                         return 'default';
1396                 }
1397
1398                 return $datePreference;
1399         }
1400
1401         /**
1402          * Get a format string for a given type and preference
1403          * @param $type May be date, time or both
1404          * @param $pref The format name as it appears in Messages*.php
1405          */
1406         function getDateFormatString( $type, $pref ) {
1407                 if ( !isset( $this->dateFormatStrings[$type][$pref] ) ) {
1408                         if ( $pref == 'default' ) {
1409                                 $pref = $this->getDefaultDateFormat();
1410                                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
1411                         } else {
1412                                 $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
1413                                 if ( is_null( $df ) ) {
1414                                         $pref = $this->getDefaultDateFormat();
1415                                         $df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
1416                                 }
1417                         }
1418                         $this->dateFormatStrings[$type][$pref] = $df;
1419                 }
1420                 return $this->dateFormatStrings[$type][$pref];
1421         }
1422
1423         /**
1424          * @param $ts Mixed: the time format which needs to be turned into a
1425          *            date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1426          * @param $adj Bool: whether to adjust the time output according to the
1427          *             user configured offset ($timecorrection)
1428          * @param $format Mixed: true to use user's date format preference
1429          * @param $timecorrection String: the time offset as returned by
1430          *                        validateTimeZone() in Special:Preferences
1431          * @return string
1432          */
1433         function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
1434                 if ( $adj ) {
1435                         $ts = $this->userAdjust( $ts, $timecorrection );
1436                 }
1437                 $df = $this->getDateFormatString( 'date', $this->dateFormat( $format ) );
1438                 return $this->sprintfDate( $df, $ts );
1439         }
1440
1441         /**
1442          * @param $ts Mixed: the time format which needs to be turned into a
1443          *            date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1444          * @param $adj Bool: whether to adjust the time output according to the
1445          *             user configured offset ($timecorrection)
1446          * @param $format Mixed: true to use user's date format preference
1447          * @param $timecorrection String: the time offset as returned by
1448          *                        validateTimeZone() in Special:Preferences
1449          * @return string
1450          */
1451         function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
1452                 if ( $adj ) {
1453                         $ts = $this->userAdjust( $ts, $timecorrection );
1454                 }
1455                 $df = $this->getDateFormatString( 'time', $this->dateFormat( $format ) );
1456                 return $this->sprintfDate( $df, $ts );
1457         }
1458
1459         /**
1460          * @param $ts Mixed: the time format which needs to be turned into a
1461          *            date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1462          * @param $adj Bool: whether to adjust the time output according to the
1463          *             user configured offset ($timecorrection)
1464          * @param $format Mixed: what format to return, if it's false output the
1465          *                default one (default true)
1466          * @param $timecorrection String: the time offset as returned by
1467          *                        validateTimeZone() in Special:Preferences
1468          * @return string
1469          */
1470         function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
1471                 $ts = wfTimestamp( TS_MW, $ts );
1472                 if ( $adj ) {
1473                         $ts = $this->userAdjust( $ts, $timecorrection );
1474                 }
1475                 $df = $this->getDateFormatString( 'both', $this->dateFormat( $format ) );
1476                 return $this->sprintfDate( $df, $ts );
1477         }
1478
1479         function getMessage( $key ) {
1480                 return self::$dataCache->getSubitem( $this->mCode, 'messages', $key );
1481         }
1482
1483         function getAllMessages() {
1484                 return self::$dataCache->getItem( $this->mCode, 'messages' );
1485         }
1486
1487         function iconv( $in, $out, $string ) {
1488                 # This is a wrapper for iconv in all languages except esperanto,
1489                 # which does some nasty x-conversions beforehand
1490
1491                 # Even with //IGNORE iconv can whine about illegal characters in
1492                 # *input* string. We just ignore those too.
1493                 # REF: http://bugs.php.net/bug.php?id=37166
1494                 # REF: https://bugzilla.wikimedia.org/show_bug.cgi?id=16885
1495                 wfSuppressWarnings();
1496                 $text = iconv( $in, $out . '//IGNORE', $string );
1497                 wfRestoreWarnings();
1498                 return $text;
1499         }
1500
1501         // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
1502         function ucwordbreaksCallbackAscii($matches){
1503                 return $this->ucfirst($matches[1]);
1504         }
1505
1506         function ucwordbreaksCallbackMB($matches){
1507                 return mb_strtoupper($matches[0]);
1508         }
1509
1510         function ucCallback($matches){
1511                 list( $wikiUpperChars ) = self::getCaseMaps();
1512                 return strtr( $matches[1], $wikiUpperChars );
1513         }
1514
1515         function lcCallback($matches){
1516                 list( , $wikiLowerChars ) = self::getCaseMaps();
1517                 return strtr( $matches[1], $wikiLowerChars );
1518         }
1519
1520         function ucwordsCallbackMB($matches){
1521                 return mb_strtoupper($matches[0]);
1522         }
1523
1524         function ucwordsCallbackWiki($matches){
1525                 list( $wikiUpperChars ) = self::getCaseMaps();
1526                 return strtr( $matches[0], $wikiUpperChars );
1527         }
1528
1529         function ucfirst( $str ) {
1530                 $o = ord( $str );
1531                 if ( $o < 96 ) {
1532                         return $str;
1533                 } elseif ( $o < 128 ) {
1534                         return ucfirst($str);
1535                 } else {
1536                         // fall back to more complex logic in case of multibyte strings
1537                         return self::uc($str,true);
1538                 }
1539         }
1540
1541         function uc( $str, $first = false ) {
1542                 if ( function_exists( 'mb_strtoupper' ) ) {
1543                         if ( $first ) {
1544                                 if ( self::isMultibyte( $str ) ) {
1545                                         return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1546                                 } else {
1547                                         return ucfirst( $str );
1548                                 }
1549                         } else {
1550                                 return self::isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
1551                         }
1552                 } else {
1553                         if ( self::isMultibyte( $str ) ) {
1554                                 list( $wikiUpperChars ) = $this->getCaseMaps();
1555                                 $x = $first ? '^' : '';
1556                                 return preg_replace_callback(
1557                                         "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1558                                         array($this,"ucCallback"),
1559                                         $str
1560                                 );
1561                         } else {
1562                                 return $first ? ucfirst( $str ) : strtoupper( $str );
1563                         }
1564                 }
1565         }
1566
1567         function lcfirst( $str ) {
1568                 $o = ord( $str );
1569                 if ( !$o ) {
1570                         return strval( $str );
1571                 } elseif ( $o >= 128 ) {
1572                         return self::lc( $str, true );
1573                 } elseif ( $o > 96 ) {
1574                         return $str;
1575                 } else {
1576                         $str[0] = strtolower( $str[0] );
1577                         return $str;
1578                 }
1579         }
1580
1581         function lc( $str, $first = false ) {
1582                 if ( function_exists( 'mb_strtolower' ) )
1583                         if ( $first )
1584                                 if ( self::isMultibyte( $str ) )
1585                                         return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1586                                 else
1587                                         return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
1588                         else
1589                                 return self::isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
1590                 else
1591                         if ( self::isMultibyte( $str ) ) {
1592                                 list( , $wikiLowerChars ) = self::getCaseMaps();
1593                                 $x = $first ? '^' : '';
1594                                 return preg_replace_callback(
1595                                         "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1596                                         array($this,"lcCallback"),
1597                                         $str
1598                                 );
1599                         } else
1600                                 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
1601         }
1602
1603         function isMultibyte( $str ) {
1604                 return (bool)preg_match( '/[\x80-\xff]/', $str );
1605         }
1606
1607         function ucwords($str) {
1608                 if ( self::isMultibyte( $str ) ) {
1609                         $str = self::lc($str);
1610
1611                         // regexp to find first letter in each word (i.e. after each space)
1612                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1613
1614                         // function to use to capitalize a single char
1615                         if ( function_exists( 'mb_strtoupper' ) )
1616                                 return preg_replace_callback(
1617                                         $replaceRegexp,
1618                                         array($this,"ucwordsCallbackMB"),
1619                                         $str
1620                                 );
1621                         else
1622                                 return preg_replace_callback(
1623                                         $replaceRegexp,
1624                                         array($this,"ucwordsCallbackWiki"),
1625                                         $str
1626                                 );
1627                 }
1628                 else
1629                         return ucwords( strtolower( $str ) );
1630         }
1631
1632   # capitalize words at word breaks
1633         function ucwordbreaks($str){
1634                 if (self::isMultibyte( $str ) ) {
1635                         $str = self::lc($str);
1636
1637                         // since \b doesn't work for UTF-8, we explicitely define word break chars
1638                         $breaks= "[ \-\(\)\}\{\.,\?!]";
1639
1640                         // find first letter after word break
1641                         $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1642
1643                         if ( function_exists( 'mb_strtoupper' ) )
1644                                 return preg_replace_callback(
1645                                         $replaceRegexp,
1646                                         array($this,"ucwordbreaksCallbackMB"),
1647                                         $str
1648                                 );
1649                         else
1650                                 return preg_replace_callback(
1651                                         $replaceRegexp,
1652                                         array($this,"ucwordsCallbackWiki"),
1653                                         $str
1654                                 );
1655                 }
1656                 else
1657                         return preg_replace_callback(
1658                         '/\b([\w\x80-\xff]+)\b/',
1659                         array($this,"ucwordbreaksCallbackAscii"),
1660                         $str );
1661         }
1662
1663         /**
1664          * Return a case-folded representation of $s
1665          *
1666          * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1667          * and $s2 are the same except for the case of their characters. It is not
1668          * necessary for the value returned to make sense when displayed.
1669          *
1670          * Do *not* perform any other normalisation in this function. If a caller
1671          * uses this function when it should be using a more general normalisation
1672          * function, then fix the caller.
1673          */
1674         function caseFold( $s ) {
1675                 return $this->uc( $s );
1676         }
1677
1678         function checkTitleEncoding( $s ) {
1679                 if( is_array( $s ) ) {
1680                         wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1681                 }
1682                 # Check for non-UTF-8 URLs
1683                 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1684                 if(!$ishigh) return $s;
1685
1686                 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1687                 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1688                 if( $isutf8 ) return $s;
1689
1690                 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1691         }
1692
1693         function fallback8bitEncoding() {
1694                 return self::$dataCache->getItem( $this->mCode, 'fallback8bitEncoding' );
1695         }
1696
1697         /**
1698          * Most writing systems use whitespace to break up words.
1699          * Some languages such as Chinese don't conventionally do this,
1700          * which requires special handling when breaking up words for
1701          * searching etc.
1702          */
1703         function hasWordBreaks() {
1704                 return true;
1705         }
1706         
1707         /**
1708          * Some languages such as Chinese require word segmentation,
1709          * Specify such segmentation when overridden in derived class.
1710          * 
1711          * @param $string String
1712          * @return String
1713          */
1714         function wordSegmentation( $string ) {
1715                 return $string;
1716         }
1717
1718         /**
1719          * Some languages have special punctuation need to be normalized.
1720          * Make such changes here.
1721          *
1722          * @param $string String
1723          * @return String
1724          */
1725         function normalizeForSearch( $string ) {
1726                 return $string;
1727         }
1728
1729         /**
1730          * convert double-width roman characters to single-width.
1731          * range: ff00-ff5f ~= 0020-007f
1732          */
1733         protected static function convertDoubleWidth( $string ) {
1734                 $string = preg_replace( '/\xef\xbc([\x80-\xbf])/e', 'chr((ord("$1") & 0x3f) + 0x20)', $string );
1735                 $string = preg_replace( '/\xef\xbd([\x80-\x99])/e', 'chr((ord("$1") & 0x3f) + 0x60)', $string );
1736                 return $string;
1737         }
1738
1739         protected static function insertSpace( $string, $pattern ) {
1740                 $string = preg_replace( $pattern, " $1 ", $string );
1741                 $string = preg_replace( '/ +/', ' ', $string );
1742                 return $string;
1743         }
1744
1745         function convertForSearchResult( $termsArray ) {
1746                 # some languages, e.g. Chinese, need to do a conversion
1747                 # in order for search results to be displayed correctly
1748                 return $termsArray;
1749         }
1750
1751         /**
1752          * Get the first character of a string.
1753          *
1754          * @param $s string
1755          * @return string
1756          */
1757         function firstChar( $s ) {
1758                 $matches = array();
1759                 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1760                 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1761
1762                 if ( isset( $matches[1] ) ) {
1763                         if ( strlen( $matches[1] ) != 3 ) {
1764                                 return $matches[1];
1765                         }
1766
1767                         // Break down Hangul syllables to grab the first jamo
1768                         $code = utf8ToCodepoint( $matches[1] );
1769                         if ( $code < 0xac00 || 0xd7a4 <= $code) {
1770                                 return $matches[1];
1771                         } elseif ( $code < 0xb098 ) {
1772                                 return "\xe3\x84\xb1";
1773                         } elseif ( $code < 0xb2e4 ) {
1774                                 return "\xe3\x84\xb4";
1775                         } elseif ( $code < 0xb77c ) {
1776                                 return "\xe3\x84\xb7";
1777                         } elseif ( $code < 0xb9c8 ) {
1778                                 return "\xe3\x84\xb9";
1779                         } elseif ( $code < 0xbc14 ) {
1780                                 return "\xe3\x85\x81";
1781                         } elseif ( $code < 0xc0ac ) {
1782                                 return "\xe3\x85\x82";
1783                         } elseif ( $code < 0xc544 ) {
1784                                 return "\xe3\x85\x85";
1785                         } elseif ( $code < 0xc790 ) {
1786                                 return "\xe3\x85\x87";
1787                         } elseif ( $code < 0xcc28 ) {
1788                                 return "\xe3\x85\x88";
1789                         } elseif ( $code < 0xce74 ) {
1790                                 return "\xe3\x85\x8a";
1791                         } elseif ( $code < 0xd0c0 ) {
1792                                 return "\xe3\x85\x8b";
1793                         } elseif ( $code < 0xd30c ) {
1794                                 return "\xe3\x85\x8c";
1795                         } elseif ( $code < 0xd558 ) {
1796                                 return "\xe3\x85\x8d";
1797                         } else {
1798                                 return "\xe3\x85\x8e";
1799                         }
1800                 } else {
1801                         return "";
1802                 }
1803         }
1804
1805         function initEncoding() {
1806                 # Some languages may have an alternate char encoding option
1807                 # (Esperanto X-coding, Japanese furigana conversion, etc)
1808                 # If this language is used as the primary content language,
1809                 # an override to the defaults can be set here on startup.
1810         }
1811
1812         function recodeForEdit( $s ) {
1813                 # For some languages we'll want to explicitly specify
1814                 # which characters make it into the edit box raw
1815                 # or are converted in some way or another.
1816                 # Note that if wgOutputEncoding is different from
1817                 # wgInputEncoding, this text will be further converted
1818                 # to wgOutputEncoding.
1819                 global $wgEditEncoding;
1820                 if( $wgEditEncoding == '' or
1821                   $wgEditEncoding == 'UTF-8' ) {
1822                         return $s;
1823                 } else {
1824                         return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1825                 }
1826         }
1827
1828         function recodeInput( $s ) {
1829                 # Take the previous into account.
1830                 global $wgEditEncoding;
1831                 if($wgEditEncoding != "") {
1832                         $enc = $wgEditEncoding;
1833                 } else {
1834                         $enc = 'UTF-8';
1835                 }
1836                 if( $enc == 'UTF-8' ) {
1837                         return $s;
1838                 } else {
1839                         return $this->iconv( $enc, 'UTF-8', $s );
1840                 }
1841         }
1842
1843         /**
1844          * Convert a UTF-8 string to normal form C. In Malayalam and Arabic, this
1845          * also cleans up certain backwards-compatible sequences, converting them
1846          * to the modern Unicode equivalent.
1847          *
1848          * This is language-specific for performance reasons only.
1849          */
1850         function normalize( $s ) {
1851                 return UtfNormal::cleanUp( $s );
1852         }
1853
1854         /**
1855          * Transform a string using serialized data stored in the given file (which
1856          * must be in the serialized subdirectory of $IP). The file contains pairs
1857          * mapping source characters to destination characters.
1858          *
1859          * The data is cached in process memory. This will go faster if you have the
1860          * FastStringSearch extension.
1861          */
1862         function transformUsingPairFile( $file, $string ) {
1863                 if ( !isset( $this->transformData[$file] ) ) {
1864                         $data = wfGetPrecompiledData( $file );
1865                         if ( $data === false ) {
1866                                 throw new MWException( __METHOD__.": The transformation file $file is missing" );
1867                         }
1868                         $this->transformData[$file] = new ReplacementArray( $data );
1869                 }
1870                 return $this->transformData[$file]->replace( $string );
1871         }
1872
1873         /**
1874          * For right-to-left language support
1875          *
1876          * @return bool
1877          */
1878         function isRTL() {
1879                 return self::$dataCache->getItem( $this->mCode, 'rtl' );
1880         }
1881
1882         /**
1883          * Return the correct HTML 'dir' attribute value for this language.
1884          * @return String
1885          */
1886         function getDir() {
1887                 return $this->isRTL() ? 'rtl' : 'ltr';
1888         }
1889
1890         /**
1891          * Return 'left' or 'right' as appropriate alignment for line-start
1892          * for this language's text direction.
1893          *
1894          * Should be equivalent to CSS3 'start' text-align value....
1895          *
1896          * @return String
1897          */
1898         function alignStart() {
1899                 return $this->isRTL() ? 'right' : 'left';
1900         }
1901
1902         /**
1903          * Return 'right' or 'left' as appropriate alignment for line-end
1904          * for this language's text direction.
1905          *
1906          * Should be equivalent to CSS3 'end' text-align value....
1907          *
1908          * @return String
1909          */
1910         function alignEnd() {
1911                 return $this->isRTL() ? 'left' : 'right';
1912         }
1913
1914         /**
1915          * A hidden direction mark (LRM or RLM), depending on the language direction
1916          *
1917          * @return string
1918          */
1919         function getDirMark() {
1920                 return $this->isRTL() ? "\xE2\x80\x8F" : "\xE2\x80\x8E";
1921         }
1922
1923         function capitalizeAllNouns() {
1924                 return self::$dataCache->getItem( $this->mCode, 'capitalizeAllNouns' );
1925         }
1926
1927         /**
1928          * An arrow, depending on the language direction
1929          *
1930          * @return string
1931          */
1932         function getArrow() {
1933                 return $this->isRTL() ? '←' : '→';
1934         }
1935
1936         /**
1937          * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1938          *
1939          * @return bool
1940          */
1941         function linkPrefixExtension() {
1942                 return self::$dataCache->getItem( $this->mCode, 'linkPrefixExtension' );
1943         }
1944
1945         function getMagicWords() {
1946                 return self::$dataCache->getItem( $this->mCode, 'magicWords' );
1947         }
1948
1949         # Fill a MagicWord object with data from here
1950         function getMagic( $mw ) {
1951                 if ( !$this->mMagicHookDone ) {
1952                         $this->mMagicHookDone = true;
1953                         wfProfileIn( 'LanguageGetMagic' );
1954                         wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
1955                         wfProfileOut( 'LanguageGetMagic' );
1956                 }
1957                 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
1958                         $rawEntry = $this->mMagicExtensions[$mw->mId];
1959                 } else {
1960                         $magicWords = $this->getMagicWords();
1961                         if ( isset( $magicWords[$mw->mId] ) ) {
1962                                 $rawEntry = $magicWords[$mw->mId];
1963                         } else {
1964                                 $rawEntry = false;
1965                         }
1966                 }
1967
1968                 if( !is_array( $rawEntry ) ) {
1969                         error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1970                 } else {
1971                         $mw->mCaseSensitive = $rawEntry[0];
1972                         $mw->mSynonyms = array_slice( $rawEntry, 1 );
1973                 }
1974         }
1975
1976         /**
1977          * Add magic words to the extension array
1978          */
1979         function addMagicWordsByLang( $newWords ) {
1980                 $code = $this->getCode();
1981                 $fallbackChain = array();
1982                 while ( $code && !in_array( $code, $fallbackChain ) ) {
1983                         $fallbackChain[] = $code;
1984                         $code = self::getFallbackFor( $code );
1985                 }
1986                 if ( !in_array( 'en', $fallbackChain ) ) {
1987                         $fallbackChain[] = 'en';
1988                 }
1989                 $fallbackChain = array_reverse( $fallbackChain );
1990                 foreach ( $fallbackChain as $code ) {
1991                         if ( isset( $newWords[$code] ) ) {
1992                                 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
1993                         }
1994                 }
1995         }
1996
1997         /**
1998          * Get special page names, as an associative array
1999          *   case folded alias => real name
2000          */
2001         function getSpecialPageAliases() {
2002                 // Cache aliases because it may be slow to load them
2003                 if ( is_null( $this->mExtendedSpecialPageAliases ) ) {
2004                         // Initialise array
2005                         $this->mExtendedSpecialPageAliases =
2006                                 self::$dataCache->getItem( $this->mCode, 'specialPageAliases' );
2007                         wfRunHooks( 'LanguageGetSpecialPageAliases',
2008                                 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
2009                 }
2010
2011                 return $this->mExtendedSpecialPageAliases;
2012         }
2013
2014         /**
2015          * Italic is unsuitable for some languages
2016          *
2017          * @param $text String: the text to be emphasized.
2018          * @return string
2019          */
2020         function emphasize( $text ) {
2021                 return "<em>$text</em>";
2022         }
2023
2024          /**
2025           * Normally we output all numbers in plain en_US style, that is
2026           * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
2027           * point twohundredthirtyfive. However this is not sutable for all
2028           * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
2029           * Icelandic just want to use commas instead of dots, and dots instead
2030           * of commas like "293.291,235".
2031           *
2032           * An example of this function being called:
2033           * <code>
2034           * wfMsg( 'message', $wgLang->formatNum( $num ) )
2035           * </code>
2036           *
2037           * See LanguageGu.php for the Gujarati implementation and
2038           * $separatorTransformTable on MessageIs.php for
2039           * the , => . and . => , implementation.
2040           *
2041           * @todo check if it's viable to use localeconv() for the decimal
2042           *       separator thing.
2043           * @param $number Mixed: the string to be formatted, should be an integer
2044           *        or a floating point number.
2045           * @param $nocommafy Bool: set to true for special numbers like dates
2046           * @return string
2047           */
2048         function formatNum( $number, $nocommafy = false ) {
2049                 global $wgTranslateNumerals;
2050                 if (!$nocommafy) {
2051                         $number = $this->commafy($number);
2052                         $s = $this->separatorTransformTable();
2053                         if ($s) { $number = strtr($number, $s); }
2054                 }
2055
2056                 if ($wgTranslateNumerals) {
2057                         $s = $this->digitTransformTable();
2058                         if ($s) { $number = strtr($number, $s); }
2059                 }
2060
2061                 return $number;
2062         }
2063
2064         function parseFormattedNumber( $number ) {
2065                 $s = $this->digitTransformTable();
2066                 if ($s) { $number = strtr($number, array_flip($s)); }
2067
2068                 $s = $this->separatorTransformTable();
2069                 if ($s) { $number = strtr($number, array_flip($s)); }
2070
2071                 $number = strtr( $number, array (',' => '') );
2072                 return $number;
2073         }
2074
2075         /**
2076          * Adds commas to a given number
2077          *
2078          * @param $_ mixed
2079          * @return string
2080          */
2081         function commafy($_) {
2082                 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
2083         }
2084
2085         function digitTransformTable() {
2086                 return self::$dataCache->getItem( $this->mCode, 'digitTransformTable' );
2087         }
2088
2089         function separatorTransformTable() {
2090                 return self::$dataCache->getItem( $this->mCode, 'separatorTransformTable' );
2091         }
2092
2093
2094         /**
2095          * Take a list of strings and build a locale-friendly comma-separated
2096          * list, using the local comma-separator message.
2097          * The last two strings are chained with an "and".
2098          *
2099          * @param $l Array
2100          * @return string
2101          */
2102         function listToText( $l ) {
2103                 $s = '';
2104                 $m = count( $l ) - 1;
2105                 if( $m == 1 ) {
2106                         return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
2107                 }
2108                 else {
2109                         for ( $i = $m; $i >= 0; $i-- ) {
2110                                 if ( $i == $m ) {
2111                                         $s = $l[$i];
2112                                 } else if( $i == $m - 1 ) {
2113                                         $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
2114                                 } else {
2115                                         $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
2116                                 }
2117                         }
2118                         return $s;
2119                 }
2120         }
2121
2122         /**
2123          * Take a list of strings and build a locale-friendly comma-separated
2124          * list, using the local comma-separator message.
2125          * @param $list array of strings to put in a comma list
2126          * @return string
2127          */
2128         function commaList( $list ) {
2129                 return implode(
2130                         $list,
2131                         wfMsgExt( 'comma-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
2132         }
2133
2134         /**
2135          * Take a list of strings and build a locale-friendly semicolon-separated
2136          * list, using the local semicolon-separator message.
2137          * @param $list array of strings to put in a semicolon list
2138          * @return string
2139          */
2140         function semicolonList( $list ) {
2141                 return implode(
2142                         $list,
2143                         wfMsgExt( 'semicolon-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
2144         }
2145
2146         /**
2147          * Same as commaList, but separate it with the pipe instead.
2148          * @param $list array of strings to put in a pipe list
2149          * @return string
2150          */
2151         function pipeList( $list ) {
2152                 return implode(
2153                         $list,
2154                         wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) ) );
2155         }
2156
2157         /**
2158          * Truncate a string to a specified length in bytes, appending an optional
2159          * string (e.g. for ellipses)
2160          *
2161          * The database offers limited byte lengths for some columns in the database;
2162          * multi-byte character sets mean we need to ensure that only whole characters
2163          * are included, otherwise broken characters can be passed to the user
2164          *
2165          * If $length is negative, the string will be truncated from the beginning
2166          *
2167          * @param $string String to truncate
2168          * @param $length Int: maximum length (excluding ellipses)
2169          * @param $ellipsis String to append to the truncated text
2170          * @return string
2171          */
2172         function truncate( $string, $length, $ellipsis = '...' ) {
2173                 # Use the localized ellipsis character
2174                 if( $ellipsis == '...' ) {
2175                         $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
2176                 }
2177
2178                 if( $length == 0 ) {
2179                         return $ellipsis;
2180                 }
2181                 if ( strlen( $string ) <= abs( $length ) ) {
2182                         return $string;
2183                 }
2184                 $stringOriginal = $string;
2185                 if( $length > 0 ) {
2186                         $string = substr( $string, 0, $length );
2187                         $char = ord( $string[strlen( $string ) - 1] );
2188                         $m = array();
2189                         if ($char >= 0xc0) {
2190                                 # We got the first byte only of a multibyte char; remove it.
2191                                 $string = substr( $string, 0, -1 );
2192                         } elseif( $char >= 0x80 &&
2193                                   preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
2194                                               '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
2195                                 # We chopped in the middle of a character; remove it
2196                                 $string = $m[1];
2197                         }
2198                         $string = $string . $ellipsis;
2199
2200                 } else {
2201                         $string = substr( $string, $length );
2202                         $char = ord( $string[0] );
2203                         if( $char >= 0x80 && $char < 0xc0 ) {
2204                                 # We chopped in the middle of a character; remove the whole thing
2205                                 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
2206                         }
2207                         $string = $ellipsis . $string;
2208                 }
2209                 # Do not truncate if the ellipsis actually make the string longer. Bug 22181
2210                 if ( strlen( $string ) < strlen( $stringOriginal ) ) {
2211                         return $string;
2212                 } else {
2213                         return $stringOriginal;
2214                 }
2215         }
2216
2217         /**
2218          * Grammatical transformations, needed for inflected languages
2219          * Invoked by putting {{grammar:case|word}} in a message
2220          *
2221          * @param $word string
2222          * @param $case string
2223          * @return string
2224          */
2225         function convertGrammar( $word, $case ) {
2226                 global $wgGrammarForms;
2227                 if ( isset($wgGrammarForms[$this->getCode()][$case][$word]) ) {
2228                         return $wgGrammarForms[$this->getCode()][$case][$word];
2229                 }
2230                 return $word;
2231         }
2232
2233         /**
2234          * Provides an alternative text depending on specified gender.
2235          * Usage {{gender:username|masculine|feminine|neutral}}.
2236          * username is optional, in which case the gender of current user is used,
2237          * but only in (some) interface messages; otherwise default gender is used.
2238          * If second or third parameter are not specified, masculine is used.
2239          * These details may be overriden per language.
2240          */
2241         function gender( $gender, $forms ) {
2242                 if ( !count($forms) ) { return ''; }
2243                 $forms = $this->preConvertPlural( $forms, 2 );
2244                 if ( $gender === 'male' ) return $forms[0];
2245                 if ( $gender === 'female' ) return $forms[1];
2246                 return isset($forms[2]) ? $forms[2] : $forms[0];
2247         }
2248
2249         /**
2250          * Plural form transformations, needed for some languages.
2251          * For example, there are 3 form of plural in Russian and Polish,
2252          * depending on "count mod 10". See [[w:Plural]]
2253          * For English it is pretty simple.
2254          *
2255          * Invoked by putting {{plural:count|wordform1|wordform2}}
2256          * or {{plural:count|wordform1|wordform2|wordform3}}
2257          *
2258          * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
2259          *
2260          * @param $count Integer: non-localized number
2261          * @param $forms Array: different plural forms
2262          * @return string Correct form of plural for $count in this language
2263          */
2264         function convertPlural( $count, $forms ) {
2265                 if ( !count($forms) ) { return ''; }
2266                 $forms = $this->preConvertPlural( $forms, 2 );
2267
2268                 return ( $count == 1 ) ? $forms[0] : $forms[1];
2269         }
2270
2271         /**
2272          * Checks that convertPlural was given an array and pads it to requested
2273          * amound of forms by copying the last one.
2274          *
2275          * @param $count Integer: How many forms should there be at least
2276          * @param $forms Array of forms given to convertPlural
2277          * @return array Padded array of forms or an exception if not an array
2278          */
2279         protected function preConvertPlural( /* Array */ $forms, $count ) {
2280                 while ( count($forms) < $count ) {
2281                         $forms[] = $forms[count($forms)-1];
2282                 }
2283                 return $forms;
2284         }
2285
2286         /**
2287          * For translaing of expiry times
2288          * @param $str String: the validated block time in English
2289          * @return Somehow translated block time
2290          * @see LanguageFi.php for example implementation
2291          */
2292         function translateBlockExpiry( $str ) {
2293
2294                 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
2295
2296                 if ( $scBlockExpiryOptions == '-') {
2297                         return $str;
2298                 }
2299
2300                 foreach (explode(',', $scBlockExpiryOptions) as $option) {
2301                         if ( strpos($option, ":") === false )
2302                                 continue;
2303                         list($show, $value) = explode(":", $option);
2304                         if ( strcmp ( $str, $value) == 0 ) {
2305                                 return htmlspecialchars( trim( $show ) );
2306                         }
2307                 }
2308
2309                 return $str;
2310         }
2311
2312         /**
2313          * languages like Chinese need to be segmented in order for the diff
2314          * to be of any use
2315          *
2316          * @param $text String
2317          * @return String
2318          */
2319         function segmentForDiff( $text ) {
2320                 return $text;
2321         }
2322
2323         /**
2324          * and unsegment to show the result
2325          *
2326          * @param $text String
2327          * @return String
2328          */
2329         function unsegmentForDiff( $text ) {
2330                 return $text;
2331         }
2332
2333         # convert text to all supported variants
2334         function autoConvertToAllVariants($text) {
2335                 return $this->mConverter->autoConvertToAllVariants($text);
2336         }
2337
2338         # convert text to different variants of a language.
2339         function convert( $text ) {
2340                 return $this->mConverter->convert( $text );
2341         }
2342
2343         # Convert a Title object to a string in the preferred variant
2344         function convertTitle( $title ) {
2345                 return $this->mConverter->convertTitle( $title );
2346         }
2347
2348         # Check if this is a language with variants
2349         function hasVariants(){
2350                 return sizeof($this->getVariants())>1;
2351         }
2352
2353         # Put custom tags (e.g. -{ }-) around math to prevent conversion
2354         function armourMath($text){
2355                 return $this->mConverter->armourMath($text);
2356         }
2357
2358
2359         /**
2360          * Perform output conversion on a string, and encode for safe HTML output.
2361          * @param $text String text to be converted
2362          * @param $isTitle Bool whether this conversion is for the article title
2363          * @return string
2364          * @todo this should get integrated somewhere sane
2365          */
2366         function convertHtml( $text, $isTitle = false ) {
2367                 return htmlspecialchars( $this->convert( $text, $isTitle ) );
2368         }
2369
2370         function convertCategoryKey( $key ) {
2371                 return $this->mConverter->convertCategoryKey( $key );
2372         }
2373
2374         /**
2375          * get the list of variants supported by this langauge
2376          * see sample implementation in LanguageZh.php
2377          *
2378          * @return array an array of language codes
2379          */
2380         function getVariants() {
2381                 return $this->mConverter->getVariants();
2382         }
2383
2384
2385         function getPreferredVariant( $fromUser = true, $fromHeader = false ) {
2386                 return $this->mConverter->getPreferredVariant( $fromUser, $fromHeader );
2387         }
2388
2389         /**
2390          * if a language supports multiple variants, it is
2391          * possible that non-existing link in one variant
2392          * actually exists in another variant. this function
2393          * tries to find it. See e.g. LanguageZh.php
2394          *
2395          * @param $link String: the name of the link
2396          * @param $nt Mixed: the title object of the link
2397          * @param boolean $ignoreOtherCond: to disable other conditions when
2398          *      we need to transclude a template or update a category's link
2399          * @return null the input parameters may be modified upon return
2400          */
2401         function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
2402                 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
2403         }
2404
2405         /**
2406          * If a language supports multiple variants, converts text
2407          * into an array of all possible variants of the text:
2408          *  'variant' => text in that variant
2409          */
2410         function convertLinkToAllVariants($text){
2411                 return $this->mConverter->convertLinkToAllVariants($text);
2412         }
2413
2414
2415         /**
2416          * returns language specific options used by User::getPageRenderHash()
2417          * for example, the preferred language variant
2418          *
2419          * @return string
2420          */
2421         function getExtraHashOptions() {
2422                 return $this->mConverter->getExtraHashOptions();
2423         }
2424
2425         /**
2426          * for languages that support multiple variants, the title of an
2427          * article may be displayed differently in different variants. this
2428          * function returns the apporiate title defined in the body of the article.
2429          *
2430          * @return string
2431          */
2432         function getParsedTitle() {
2433                 return $this->mConverter->getParsedTitle();
2434         }
2435
2436         /**
2437          * Enclose a string with the "no conversion" tag. This is used by
2438          * various functions in the Parser
2439          *
2440          * @param $text String: text to be tagged for no conversion
2441          * @param $noParse
2442          * @return string the tagged text
2443          */
2444         function markNoConversion( $text, $noParse=false ) {
2445                 return $this->mConverter->markNoConversion( $text, $noParse );
2446         }
2447
2448         /**
2449          * A regular expression to match legal word-trailing characters
2450          * which should be merged onto a link of the form [[foo]]bar.
2451          *
2452          * @return string
2453          */
2454         function linkTrail() {
2455                 return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
2456         }
2457
2458         function getLangObj() {
2459                 return $this;
2460         }
2461
2462         /**
2463          * Get the RFC 3066 code for this language object
2464          */
2465         function getCode() {
2466                 return $this->mCode;
2467         }
2468
2469         function setCode( $code ) {
2470                 $this->mCode = $code;
2471         }
2472
2473         /**
2474          * Get the name of a file for a certain language code
2475          * @param $prefix string Prepend this to the filename
2476          * @param $code string Language code
2477          * @param $suffix string Append this to the filename
2478          * @return string $prefix . $mangledCode . $suffix
2479          */
2480         static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
2481                 // Protect against path traversal
2482                 if ( !Language::isValidCode( $code ) 
2483                         || strcspn( $code, "/\\\000" ) !== strlen( $code ) ) 
2484                 {
2485                         throw new MWException( "Invalid language code \"$code\"" );
2486                 }
2487                 
2488                 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
2489         }
2490
2491         /**
2492          * Get the language code from a file name. Inverse of getFileName()
2493          * @param $filename string $prefix . $languageCode . $suffix
2494          * @param $prefix string Prefix before the language code
2495          * @param $suffix string Suffix after the language code
2496          * @return Language code, or false if $prefix or $suffix isn't found
2497          */
2498         static function getCodeFromFileName( $filename, $prefix = 'Language', $suffix = '.php' ) {
2499                 $m = null;
2500                 preg_match( '/' . preg_quote( $prefix, '/' ) . '([A-Z][a-z_]+)' .
2501                         preg_quote( $suffix, '/' ) . '/', $filename, $m );
2502                 if ( !count( $m ) ) {
2503                         return false;
2504                 }
2505                 return str_replace( '_', '-', strtolower( $m[1] ) );
2506         }
2507
2508         static function getMessagesFileName( $code ) {
2509                 global $IP;
2510                 return self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
2511         }
2512
2513         static function getClassFileName( $code ) {
2514                 global $IP;
2515                 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
2516         }
2517
2518         /**
2519          * Get the fallback for a given language
2520          */
2521         static function getFallbackFor( $code ) {
2522                 if ( $code === 'en' ) {
2523                         // Shortcut
2524                         return false;
2525                 } else {
2526                         return self::getLocalisationCache()->getItem( $code, 'fallback' );
2527                 }
2528         }
2529
2530         /**
2531          * Get all messages for a given language
2532          * WARNING: this may take a long time
2533          */
2534         static function getMessagesFor( $code ) {
2535                 return self::getLocalisationCache()->getItem( $code, 'messages' );
2536         }
2537
2538         /**
2539          * Get a message for a given language
2540          */
2541         static function getMessageFor( $key, $code ) {
2542                 return self::getLocalisationCache()->getSubitem( $code, 'messages', $key );
2543         }
2544
2545         function fixVariableInNamespace( $talk ) {
2546                 if ( strpos( $talk, '$1' ) === false ) return $talk;
2547
2548                 global $wgMetaNamespace;
2549                 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
2550
2551                 # Allow grammar transformations
2552                 # Allowing full message-style parsing would make simple requests
2553                 # such as action=raw much more expensive than they need to be.
2554                 # This will hopefully cover most cases.
2555                 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
2556                         array( &$this, 'replaceGrammarInNamespace' ), $talk );
2557                 return str_replace( ' ', '_', $talk );
2558         }
2559
2560         function replaceGrammarInNamespace( $m ) {
2561                 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
2562         }
2563
2564         static function getCaseMaps() {
2565                 static $wikiUpperChars, $wikiLowerChars;
2566                 if ( isset( $wikiUpperChars ) ) {
2567                         return array( $wikiUpperChars, $wikiLowerChars );
2568                 }
2569
2570                 wfProfileIn( __METHOD__ );
2571                 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
2572                 if ( $arr === false ) {
2573                         throw new MWException(
2574                                 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
2575                 }
2576                 extract( $arr );
2577                 wfProfileOut( __METHOD__ );
2578                 return array( $wikiUpperChars, $wikiLowerChars );
2579         }
2580
2581         function formatTimePeriod( $seconds ) {
2582                 if ( $seconds < 10 ) {
2583                         return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
2584                 } elseif ( $seconds < 60 ) {
2585                         return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
2586                 } elseif ( $seconds < 3600 ) {
2587                         return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
2588                                 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
2589                 } else {
2590                         $hours = floor( $seconds / 3600 );
2591                         $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
2592                         $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
2593                         return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
2594                                 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
2595                                 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
2596                 }
2597         }
2598
2599         function formatBitrate( $bps ) {
2600                 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
2601                 if ( $bps <= 0 ) {
2602                         return $this->formatNum( $bps ) . $units[0];
2603                 }
2604                 $unitIndex = floor( log10( $bps ) / 3 );
2605                 $mantissa = $bps / pow( 1000, $unitIndex );
2606                 if ( $mantissa < 10 ) {
2607                         $mantissa = round( $mantissa, 1 );
2608                 } else {
2609                         $mantissa = round( $mantissa );
2610                 }
2611                 return $this->formatNum( $mantissa ) . $units[$unitIndex];
2612         }
2613
2614         /**
2615          * Format a size in bytes for output, using an appropriate
2616          * unit (B, KB, MB or GB) according to the magnitude in question
2617          *
2618          * @param $size Size to format
2619          * @return string Plain text (not HTML)
2620          */
2621         function formatSize( $size ) {
2622                 // For small sizes no decimal places necessary
2623                 $round = 0;
2624                 if( $size > 1024 ) {
2625                         $size = $size / 1024;
2626                         if( $size > 1024 ) {
2627                                 $size = $size / 1024;
2628                                 // For MB and bigger two decimal places are smarter
2629                                 $round = 2;
2630                                 if( $size > 1024 ) {
2631                                         $size = $size / 1024;
2632                                         $msg = 'size-gigabytes';
2633                                 } else {
2634                                         $msg = 'size-megabytes';
2635                                 }
2636                         } else {
2637                                 $msg = 'size-kilobytes';
2638                         }
2639                 } else {
2640                         $msg = 'size-bytes';
2641                 }
2642                 $size = round( $size, $round );
2643                 $text = $this->getMessageFromDB( $msg );
2644                 return str_replace( '$1', $this->formatNum( $size ), $text );
2645         }
2646
2647         /**
2648          * Get the conversion rule title, if any.
2649          */
2650         function getConvRuleTitle() {
2651                 return $this->mConverter->getConvRuleTitle();
2652         }
2653 }