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