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