follow up r50804, fix undefined variable error.
[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 # Minguo dates up to Showa period
1158 # Deduct years from the Gregorian calendar
1159 # depending on the nengo periods
1160 # Months and days are identical
1161 if (($gy < 1989) || (($gy == 1989) && ($gm == 1) && ($gd < 8))) {
1162 # Shōwa period
1163 $gy_gannen = $gy - 1926 + 1;
1164 $gy_offset = $gy_gannen;
1165 if ($gy_gannen == 1)
1166 $gy_offset = '元';
1167 $gy_offset = '昭和'.$gy_offset;
1168 } else {
1169 # Heisei period
1170 $gy_gannen = $gy - 1989 + 1;
1171 $gy_offset = $gy_gannen;
1172 if ($gy_gannen == 1)
1173 $gy_offset = '元';
1174 $gy_offset = '平成'.$gy_offset;
1175 }
1176 } else {
1177 $gy_offset = $gy;
1178 }
1179
1180 return array( $gy_offset, $gm, $gd );
1181 }
1182
1183 /**
1184 * Roman number formatting up to 3000
1185 */
1186 static function romanNumeral( $num ) {
1187 static $table = array(
1188 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
1189 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
1190 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
1191 array( '', 'M', 'MM', 'MMM' )
1192 );
1193
1194 $num = intval( $num );
1195 if ( $num > 3000 || $num <= 0 ) {
1196 return $num;
1197 }
1198
1199 $s = '';
1200 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1201 if ( $num >= $pow10 ) {
1202 $s .= $table[$i][floor($num / $pow10)];
1203 }
1204 $num = $num % $pow10;
1205 }
1206 return $s;
1207 }
1208
1209 /**
1210 * Hebrew Gematria number formatting up to 9999
1211 */
1212 static function hebrewNumeral( $num ) {
1213 static $table = array(
1214 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' ),
1215 array( '', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ', 'ק' ),
1216 array( '', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק', 'תתר' ),
1217 array( '', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט', 'י' )
1218 );
1219
1220 $num = intval( $num );
1221 if ( $num > 9999 || $num <= 0 ) {
1222 return $num;
1223 }
1224
1225 $s = '';
1226 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
1227 if ( $num >= $pow10 ) {
1228 if ( $num == 15 || $num == 16 ) {
1229 $s .= $table[0][9] . $table[0][$num - 9];
1230 $num = 0;
1231 } else {
1232 $s .= $table[$i][intval( ( $num / $pow10 ) )];
1233 if( $pow10 == 1000 ) {
1234 $s .= "'";
1235 }
1236 }
1237 }
1238 $num = $num % $pow10;
1239 }
1240 if( strlen( $s ) == 2 ) {
1241 $str = $s . "'";
1242 } else {
1243 $str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
1244 $str .= substr( $s, strlen( $s ) - 2, 2 );
1245 }
1246 $start = substr( $str, 0, strlen( $str ) - 2 );
1247 $end = substr( $str, strlen( $str ) - 2 );
1248 switch( $end ) {
1249 case 'כ':
1250 $str = $start . 'ך';
1251 break;
1252 case 'מ':
1253 $str = $start . 'ם';
1254 break;
1255 case 'נ':
1256 $str = $start . 'ן';
1257 break;
1258 case 'פ':
1259 $str = $start . 'ף';
1260 break;
1261 case 'צ':
1262 $str = $start . 'ץ';
1263 break;
1264 }
1265 return $str;
1266 }
1267
1268 /**
1269 * This is meant to be used by time(), date(), and timeanddate() to get
1270 * the date preference they're supposed to use, it should be used in
1271 * all children.
1272 *
1273 *<code>
1274 * function timeanddate([...], $format = true) {
1275 * $datePreference = $this->dateFormat($format);
1276 * [...]
1277 * }
1278 *</code>
1279 *
1280 * @param $usePrefs Mixed: if true, the user's preference is used
1281 * if false, the site/language default is used
1282 * if int/string, assumed to be a format.
1283 * @return string
1284 */
1285 function dateFormat( $usePrefs = true ) {
1286 global $wgUser;
1287
1288 if( is_bool( $usePrefs ) ) {
1289 if( $usePrefs ) {
1290 $datePreference = $wgUser->getDatePreference();
1291 } else {
1292 $options = User::getDefaultOptions();
1293 $datePreference = (string)$options['date'];
1294 }
1295 } else {
1296 $datePreference = (string)$usePrefs;
1297 }
1298
1299 // return int
1300 if( $datePreference == '' ) {
1301 return 'default';
1302 }
1303
1304 return $datePreference;
1305 }
1306
1307 /**
1308 * @param $ts Mixed: the time format which needs to be turned into a
1309 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1310 * @param $adj Bool: whether to adjust the time output according to the
1311 * user configured offset ($timecorrection)
1312 * @param $format Mixed: true to use user's date format preference
1313 * @param $timecorrection String: the time offset as returned by
1314 * validateTimeZone() in Special:Preferences
1315 * @return string
1316 */
1317 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
1318 $this->load();
1319 if ( $adj ) {
1320 $ts = $this->userAdjust( $ts, $timecorrection );
1321 }
1322
1323 $pref = $this->dateFormat( $format );
1324 if( $pref == 'default' || !isset( $this->dateFormats["$pref date"] ) ) {
1325 $pref = $this->defaultDateFormat;
1326 }
1327 return $this->sprintfDate( $this->dateFormats["$pref date"], $ts );
1328 }
1329
1330 /**
1331 * @param $ts Mixed: the time format which needs to be turned into a
1332 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1333 * @param $adj Bool: whether to adjust the time output according to the
1334 * user configured offset ($timecorrection)
1335 * @param $format Mixed: true to use user's date format preference
1336 * @param $timecorrection String: the time offset as returned by
1337 * validateTimeZone() in Special:Preferences
1338 * @return string
1339 */
1340 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
1341 $this->load();
1342 if ( $adj ) {
1343 $ts = $this->userAdjust( $ts, $timecorrection );
1344 }
1345
1346 $pref = $this->dateFormat( $format );
1347 if( $pref == 'default' || !isset( $this->dateFormats["$pref time"] ) ) {
1348 $pref = $this->defaultDateFormat;
1349 }
1350 return $this->sprintfDate( $this->dateFormats["$pref time"], $ts );
1351 }
1352
1353 /**
1354 * @param $ts Mixed: the time format which needs to be turned into a
1355 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
1356 * @param $adj Bool: whether to adjust the time output according to the
1357 * user configured offset ($timecorrection)
1358 * @param $format Mixed: what format to return, if it's false output the
1359 * default one (default true)
1360 * @param $timecorrection String: the time offset as returned by
1361 * validateTimeZone() in Special:Preferences
1362 * @return string
1363 */
1364 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
1365 $this->load();
1366
1367 $ts = wfTimestamp( TS_MW, $ts );
1368
1369 if ( $adj ) {
1370 $ts = $this->userAdjust( $ts, $timecorrection );
1371 }
1372
1373 $pref = $this->dateFormat( $format );
1374 if( $pref == 'default' || !isset( $this->dateFormats["$pref both"] ) ) {
1375 $pref = $this->defaultDateFormat;
1376 }
1377
1378 return $this->sprintfDate( $this->dateFormats["$pref both"], $ts );
1379 }
1380
1381 function getMessage( $key ) {
1382 $this->load();
1383 return isset( $this->messages[$key] ) ? $this->messages[$key] : null;
1384 }
1385
1386 function getAllMessages() {
1387 $this->load();
1388 return $this->messages;
1389 }
1390
1391 function iconv( $in, $out, $string ) {
1392 # For most languages, this is a wrapper for iconv
1393 return iconv( $in, $out . '//IGNORE', $string );
1394 }
1395
1396 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
1397 function ucwordbreaksCallbackAscii($matches){
1398 return $this->ucfirst($matches[1]);
1399 }
1400
1401 function ucwordbreaksCallbackMB($matches){
1402 return mb_strtoupper($matches[0]);
1403 }
1404
1405 function ucCallback($matches){
1406 list( $wikiUpperChars ) = self::getCaseMaps();
1407 return strtr( $matches[1], $wikiUpperChars );
1408 }
1409
1410 function lcCallback($matches){
1411 list( , $wikiLowerChars ) = self::getCaseMaps();
1412 return strtr( $matches[1], $wikiLowerChars );
1413 }
1414
1415 function ucwordsCallbackMB($matches){
1416 return mb_strtoupper($matches[0]);
1417 }
1418
1419 function ucwordsCallbackWiki($matches){
1420 list( $wikiUpperChars ) = self::getCaseMaps();
1421 return strtr( $matches[0], $wikiUpperChars );
1422 }
1423
1424 function ucfirst( $str ) {
1425 if ( empty($str) ) return $str;
1426 if ( ord($str[0]) < 128 ) return ucfirst($str);
1427 else return self::uc($str,true); // fall back to more complex logic in case of multibyte strings
1428 }
1429
1430 function uc( $str, $first = false ) {
1431 if ( function_exists( 'mb_strtoupper' ) ) {
1432 if ( $first ) {
1433 if ( self::isMultibyte( $str ) ) {
1434 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1435 } else {
1436 return ucfirst( $str );
1437 }
1438 } else {
1439 return self::isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
1440 }
1441 } else {
1442 if ( self::isMultibyte( $str ) ) {
1443 list( $wikiUpperChars ) = $this->getCaseMaps();
1444 $x = $first ? '^' : '';
1445 return preg_replace_callback(
1446 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1447 array($this,"ucCallback"),
1448 $str
1449 );
1450 } else {
1451 return $first ? ucfirst( $str ) : strtoupper( $str );
1452 }
1453 }
1454 }
1455
1456 function lcfirst( $str ) {
1457 if ( empty($str) ) return $str;
1458 if ( is_string( $str ) && ord($str[0]) < 128 ) {
1459 // editing string in place = cool
1460 $str[0]=strtolower($str[0]);
1461 return $str;
1462 }
1463 else return self::lc( $str, true );
1464 }
1465
1466 function lc( $str, $first = false ) {
1467 if ( function_exists( 'mb_strtolower' ) )
1468 if ( $first )
1469 if ( self::isMultibyte( $str ) )
1470 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
1471 else
1472 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
1473 else
1474 return self::isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
1475 else
1476 if ( self::isMultibyte( $str ) ) {
1477 list( , $wikiLowerChars ) = self::getCaseMaps();
1478 $x = $first ? '^' : '';
1479 return preg_replace_callback(
1480 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
1481 array($this,"lcCallback"),
1482 $str
1483 );
1484 } else
1485 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
1486 }
1487
1488 function isMultibyte( $str ) {
1489 return (bool)preg_match( '/[\x80-\xff]/', $str );
1490 }
1491
1492 function ucwords($str) {
1493 if ( self::isMultibyte( $str ) ) {
1494 $str = self::lc($str);
1495
1496 // regexp to find first letter in each word (i.e. after each space)
1497 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1498
1499 // function to use to capitalize a single char
1500 if ( function_exists( 'mb_strtoupper' ) )
1501 return preg_replace_callback(
1502 $replaceRegexp,
1503 array($this,"ucwordsCallbackMB"),
1504 $str
1505 );
1506 else
1507 return preg_replace_callback(
1508 $replaceRegexp,
1509 array($this,"ucwordsCallbackWiki"),
1510 $str
1511 );
1512 }
1513 else
1514 return ucwords( strtolower( $str ) );
1515 }
1516
1517 # capitalize words at word breaks
1518 function ucwordbreaks($str){
1519 if (self::isMultibyte( $str ) ) {
1520 $str = self::lc($str);
1521
1522 // since \b doesn't work for UTF-8, we explicitely define word break chars
1523 $breaks= "[ \-\(\)\}\{\.,\?!]";
1524
1525 // find first letter after word break
1526 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
1527
1528 if ( function_exists( 'mb_strtoupper' ) )
1529 return preg_replace_callback(
1530 $replaceRegexp,
1531 array($this,"ucwordbreaksCallbackMB"),
1532 $str
1533 );
1534 else
1535 return preg_replace_callback(
1536 $replaceRegexp,
1537 array($this,"ucwordsCallbackWiki"),
1538 $str
1539 );
1540 }
1541 else
1542 return preg_replace_callback(
1543 '/\b([\w\x80-\xff]+)\b/',
1544 array($this,"ucwordbreaksCallbackAscii"),
1545 $str );
1546 }
1547
1548 /**
1549 * Return a case-folded representation of $s
1550 *
1551 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
1552 * and $s2 are the same except for the case of their characters. It is not
1553 * necessary for the value returned to make sense when displayed.
1554 *
1555 * Do *not* perform any other normalisation in this function. If a caller
1556 * uses this function when it should be using a more general normalisation
1557 * function, then fix the caller.
1558 */
1559 function caseFold( $s ) {
1560 return $this->uc( $s );
1561 }
1562
1563 function checkTitleEncoding( $s ) {
1564 if( is_array( $s ) ) {
1565 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
1566 }
1567 # Check for non-UTF-8 URLs
1568 $ishigh = preg_match( '/[\x80-\xff]/', $s);
1569 if(!$ishigh) return $s;
1570
1571 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1572 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
1573 if( $isutf8 ) return $s;
1574
1575 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
1576 }
1577
1578 function fallback8bitEncoding() {
1579 $this->load();
1580 return $this->fallback8bitEncoding;
1581 }
1582
1583 /**
1584 * Some languages have special punctuation to strip out
1585 * or characters which need to be converted for MySQL's
1586 * indexing to grok it correctly. Make such changes here.
1587 *
1588 * @param $string String
1589 * @return String
1590 */
1591 function stripForSearch( $string ) {
1592 global $wgDBtype;
1593 if ( $wgDBtype != 'mysql' ) {
1594 return $string;
1595 }
1596
1597
1598 wfProfileIn( __METHOD__ );
1599
1600 // MySQL fulltext index doesn't grok utf-8, so we
1601 // need to fold cases and convert to hex
1602 $out = preg_replace_callback(
1603 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
1604 array( $this, 'stripForSearchCallback' ),
1605 $this->lc( $string ) );
1606
1607 // And to add insult to injury, the default indexing
1608 // ignores short words... Pad them so we can pass them
1609 // through without reconfiguring the server...
1610 $minLength = $this->minSearchLength();
1611 if( $minLength > 1 ) {
1612 $n = $minLength-1;
1613 $out = preg_replace(
1614 "/\b(\w{1,$n})\b/",
1615 "$1u800",
1616 $out );
1617 }
1618
1619 // Periods within things like hostnames and IP addresses
1620 // are also important -- we want a search for "example.com"
1621 // or "192.168.1.1" to work sanely.
1622 //
1623 // MySQL's search seems to ignore them, so you'd match on
1624 // "example.wikipedia.com" and "192.168.83.1" as well.
1625 $out = preg_replace(
1626 "/(\w)\.(\w|\*)/u",
1627 "$1u82e$2",
1628 $out );
1629
1630 wfProfileOut( __METHOD__ );
1631 return $out;
1632 }
1633
1634 /**
1635 * Armor a case-folded UTF-8 string to get through MySQL's
1636 * fulltext search without being mucked up by funny charset
1637 * settings or anything else of the sort.
1638 */
1639 protected function stripForSearchCallback( $matches ) {
1640 return 'u8' . bin2hex( $matches[1] );
1641 }
1642
1643 /**
1644 * Check MySQL server's ft_min_word_len setting so we know
1645 * if we need to pad short words...
1646 */
1647 protected function minSearchLength() {
1648 if( !isset( $this->minSearchLength ) ) {
1649 $sql = "show global variables like 'ft\\_min\\_word\\_len'";
1650 $dbr = wfGetDB( DB_SLAVE );
1651 $result = $dbr->query( $sql );
1652 $row = $result->fetchObject();
1653 $result->free();
1654
1655 if( $row && $row->Variable_name == 'ft_min_word_len' ) {
1656 $this->minSearchLength = intval( $row->Value );
1657 } else {
1658 $this->minSearchLength = 0;
1659 }
1660 }
1661 return $this->minSearchLength;
1662 }
1663
1664 function convertForSearchResult( $termsArray ) {
1665 # some languages, e.g. Chinese, need to do a conversion
1666 # in order for search results to be displayed correctly
1667 return $termsArray;
1668 }
1669
1670 /**
1671 * Get the first character of a string.
1672 *
1673 * @param $s string
1674 * @return string
1675 */
1676 function firstChar( $s ) {
1677 $matches = array();
1678 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1679 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1680
1681 if ( isset( $matches[1] ) ) {
1682 if ( strlen( $matches[1] ) != 3 ) {
1683 return $matches[1];
1684 }
1685
1686 // Break down Hangul syllables to grab the first jamo
1687 $code = utf8ToCodepoint( $matches[1] );
1688 if ( $code < 0xac00 || 0xd7a4 <= $code) {
1689 return $matches[1];
1690 } elseif ( $code < 0xb098 ) {
1691 return "\xe3\x84\xb1";
1692 } elseif ( $code < 0xb2e4 ) {
1693 return "\xe3\x84\xb4";
1694 } elseif ( $code < 0xb77c ) {
1695 return "\xe3\x84\xb7";
1696 } elseif ( $code < 0xb9c8 ) {
1697 return "\xe3\x84\xb9";
1698 } elseif ( $code < 0xbc14 ) {
1699 return "\xe3\x85\x81";
1700 } elseif ( $code < 0xc0ac ) {
1701 return "\xe3\x85\x82";
1702 } elseif ( $code < 0xc544 ) {
1703 return "\xe3\x85\x85";
1704 } elseif ( $code < 0xc790 ) {
1705 return "\xe3\x85\x87";
1706 } elseif ( $code < 0xcc28 ) {
1707 return "\xe3\x85\x88";
1708 } elseif ( $code < 0xce74 ) {
1709 return "\xe3\x85\x8a";
1710 } elseif ( $code < 0xd0c0 ) {
1711 return "\xe3\x85\x8b";
1712 } elseif ( $code < 0xd30c ) {
1713 return "\xe3\x85\x8c";
1714 } elseif ( $code < 0xd558 ) {
1715 return "\xe3\x85\x8d";
1716 } else {
1717 return "\xe3\x85\x8e";
1718 }
1719 } else {
1720 return "";
1721 }
1722 }
1723
1724 function initEncoding() {
1725 # Some languages may have an alternate char encoding option
1726 # (Esperanto X-coding, Japanese furigana conversion, etc)
1727 # If this language is used as the primary content language,
1728 # an override to the defaults can be set here on startup.
1729 }
1730
1731 function recodeForEdit( $s ) {
1732 # For some languages we'll want to explicitly specify
1733 # which characters make it into the edit box raw
1734 # or are converted in some way or another.
1735 # Note that if wgOutputEncoding is different from
1736 # wgInputEncoding, this text will be further converted
1737 # to wgOutputEncoding.
1738 global $wgEditEncoding;
1739 if( $wgEditEncoding == '' or
1740 $wgEditEncoding == 'UTF-8' ) {
1741 return $s;
1742 } else {
1743 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1744 }
1745 }
1746
1747 function recodeInput( $s ) {
1748 # Take the previous into account.
1749 global $wgEditEncoding;
1750 if($wgEditEncoding != "") {
1751 $enc = $wgEditEncoding;
1752 } else {
1753 $enc = 'UTF-8';
1754 }
1755 if( $enc == 'UTF-8' ) {
1756 return $s;
1757 } else {
1758 return $this->iconv( $enc, 'UTF-8', $s );
1759 }
1760 }
1761
1762 /**
1763 * For right-to-left language support
1764 *
1765 * @return bool
1766 */
1767 function isRTL() {
1768 $this->load();
1769 return $this->rtl;
1770 }
1771
1772 /**
1773 * A hidden direction mark (LRM or RLM), depending on the language direction
1774 *
1775 * @return string
1776 */
1777 function getDirMark() {
1778 return $this->isRTL() ? "\xE2\x80\x8F" : "\xE2\x80\x8E";
1779 }
1780
1781 /**
1782 * An arrow, depending on the language direction
1783 *
1784 * @return string
1785 */
1786 function getArrow() {
1787 return $this->isRTL() ? '←' : '→';
1788 }
1789
1790 /**
1791 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1792 *
1793 * @return bool
1794 */
1795 function linkPrefixExtension() {
1796 $this->load();
1797 return $this->linkPrefixExtension;
1798 }
1799
1800 function &getMagicWords() {
1801 $this->load();
1802 return $this->magicWords;
1803 }
1804
1805 # Fill a MagicWord object with data from here
1806 function getMagic( &$mw ) {
1807 if ( !$this->mMagicHookDone ) {
1808 $this->mMagicHookDone = true;
1809 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
1810 }
1811 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
1812 $rawEntry = $this->mMagicExtensions[$mw->mId];
1813 } else {
1814 $magicWords =& $this->getMagicWords();
1815 if ( isset( $magicWords[$mw->mId] ) ) {
1816 $rawEntry = $magicWords[$mw->mId];
1817 } else {
1818 # Fall back to English if local list is incomplete
1819 $magicWords =& Language::getMagicWords();
1820 if ( !isset($magicWords[$mw->mId]) ) {
1821 throw new MWException("Magic word '{$mw->mId}' not found" );
1822 }
1823 $rawEntry = $magicWords[$mw->mId];
1824 }
1825 }
1826
1827 if( !is_array( $rawEntry ) ) {
1828 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1829 } else {
1830 $mw->mCaseSensitive = $rawEntry[0];
1831 $mw->mSynonyms = array_slice( $rawEntry, 1 );
1832 }
1833 }
1834
1835 /**
1836 * Add magic words to the extension array
1837 */
1838 function addMagicWordsByLang( $newWords ) {
1839 $code = $this->getCode();
1840 $fallbackChain = array();
1841 while ( $code && !in_array( $code, $fallbackChain ) ) {
1842 $fallbackChain[] = $code;
1843 $code = self::getFallbackFor( $code );
1844 }
1845 if ( !in_array( 'en', $fallbackChain ) ) {
1846 $fallbackChain[] = 'en';
1847 }
1848 $fallbackChain = array_reverse( $fallbackChain );
1849 foreach ( $fallbackChain as $code ) {
1850 if ( isset( $newWords[$code] ) ) {
1851 $this->mMagicExtensions = $newWords[$code] + $this->mMagicExtensions;
1852 }
1853 }
1854 }
1855
1856 /**
1857 * Get special page names, as an associative array
1858 * case folded alias => real name
1859 */
1860 function getSpecialPageAliases() {
1861 $this->load();
1862
1863 // Cache aliases because it may be slow to load them
1864 if ( !isset( $this->mExtendedSpecialPageAliases ) ) {
1865
1866 // Initialise array
1867 $this->mExtendedSpecialPageAliases = $this->specialPageAliases;
1868
1869 global $wgExtensionAliasesFiles;
1870 foreach ( $wgExtensionAliasesFiles as $file ) {
1871
1872 // Fail fast
1873 if ( !file_exists($file) )
1874 throw new MWException( "Aliases file does not exist: $file" );
1875
1876 $aliases = array();
1877 require($file);
1878
1879 // Check the availability of aliases
1880 if ( !isset($aliases['en']) )
1881 throw new MWException( "Malformed aliases file: $file" );
1882
1883 // Merge all aliases in fallback chain
1884 $code = $this->getCode();
1885 do {
1886 if ( !isset($aliases[$code]) ) continue;
1887
1888 $aliases[$code] = $this->fixSpecialPageAliases( $aliases[$code] );
1889 /* Merge the aliases, THIS will break if there is special page name
1890 * which looks like a numerical key, thanks to PHP...
1891 * See the array_merge_recursive manual entry */
1892 $this->mExtendedSpecialPageAliases = array_merge_recursive(
1893 $this->mExtendedSpecialPageAliases, $aliases[$code] );
1894
1895 } while ( $code = self::getFallbackFor( $code ) );
1896 }
1897
1898 wfRunHooks( 'LanguageGetSpecialPageAliases',
1899 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
1900 }
1901
1902 return $this->mExtendedSpecialPageAliases;
1903 }
1904
1905 /**
1906 * Function to fix special page aliases. Will convert the first letter to
1907 * upper case and spaces to underscores. Can be given a full aliases array,
1908 * in which case it will recursively fix all aliases.
1909 */
1910 public function fixSpecialPageAliases( $mixed ) {
1911 // Work recursively until in string level
1912 if ( is_array($mixed) ) {
1913 $callback = array( $this, 'fixSpecialPageAliases' );
1914 return array_map( $callback, $mixed );
1915 }
1916 return str_replace( ' ', '_', $this->ucfirst( $mixed ) );
1917 }
1918
1919 /**
1920 * Italic is unsuitable for some languages
1921 *
1922 * @param $text String: the text to be emphasized.
1923 * @return string
1924 */
1925 function emphasize( $text ) {
1926 return "<em>$text</em>";
1927 }
1928
1929 /**
1930 * Normally we output all numbers in plain en_US style, that is
1931 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1932 * point twohundredthirtyfive. However this is not sutable for all
1933 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1934 * Icelandic just want to use commas instead of dots, and dots instead
1935 * of commas like "293.291,235".
1936 *
1937 * An example of this function being called:
1938 * <code>
1939 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1940 * </code>
1941 *
1942 * See LanguageGu.php for the Gujarati implementation and
1943 * $separatorTransformTable on MessageIs.php for
1944 * the , => . and . => , implementation.
1945 *
1946 * @todo check if it's viable to use localeconv() for the decimal
1947 * separator thing.
1948 * @param $number Mixed: the string to be formatted, should be an integer
1949 * or a floating point number.
1950 * @param $nocommafy Bool: set to true for special numbers like dates
1951 * @return string
1952 */
1953 function formatNum( $number, $nocommafy = false ) {
1954 global $wgTranslateNumerals;
1955 if (!$nocommafy) {
1956 $number = $this->commafy($number);
1957 $s = $this->separatorTransformTable();
1958 if ($s) { $number = strtr($number, $s); }
1959 }
1960
1961 if ($wgTranslateNumerals) {
1962 $s = $this->digitTransformTable();
1963 if ($s) { $number = strtr($number, $s); }
1964 }
1965
1966 return $number;
1967 }
1968
1969 function parseFormattedNumber( $number ) {
1970 $s = $this->digitTransformTable();
1971 if ($s) { $number = strtr($number, array_flip($s)); }
1972
1973 $s = $this->separatorTransformTable();
1974 if ($s) { $number = strtr($number, array_flip($s)); }
1975
1976 $number = strtr( $number, array (',' => '') );
1977 return $number;
1978 }
1979
1980 /**
1981 * Adds commas to a given number
1982 *
1983 * @param $_ mixed
1984 * @return string
1985 */
1986 function commafy($_) {
1987 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1988 }
1989
1990 function digitTransformTable() {
1991 $this->load();
1992 return $this->digitTransformTable;
1993 }
1994
1995 function separatorTransformTable() {
1996 $this->load();
1997 return $this->separatorTransformTable;
1998 }
1999
2000
2001 /**
2002 * Take a list of strings and build a locale-friendly comma-separated
2003 * list, using the local comma-separator message.
2004 * The last two strings are chained with an "and".
2005 *
2006 * @param $l Array
2007 * @return string
2008 */
2009 function listToText( $l ) {
2010 $s = '';
2011 $m = count( $l ) - 1;
2012 if( $m == 1 ) {
2013 return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
2014 }
2015 else {
2016 for ( $i = $m; $i >= 0; $i-- ) {
2017 if ( $i == $m ) {
2018 $s = $l[$i];
2019 } else if( $i == $m - 1 ) {
2020 $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
2021 } else {
2022 $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
2023 }
2024 }
2025 return $s;
2026 }
2027 }
2028
2029 /**
2030 * Take a list of strings and build a locale-friendly comma-separated
2031 * list, using the local comma-separator message.
2032 * @param $list array of strings to put in a comma list
2033 * @return string
2034 */
2035 function commaList( $list ) {
2036 return implode(
2037 $list,
2038 wfMsgExt( 'comma-separator', array( 'escapenoentities', 'language' => $this ) ) );
2039 }
2040
2041 /**
2042 * Take a list of strings and build a locale-friendly semicolon-separated
2043 * list, using the local semicolon-separator message.
2044 * @param $list array of strings to put in a semicolon list
2045 * @return string
2046 */
2047 function semicolonList( $list ) {
2048 return implode(
2049 $list,
2050 wfMsgExt( 'semicolon-separator', array( 'escapenoentities', 'language' => $this ) ) );
2051 }
2052
2053 /**
2054 * Same as commaList, but separate it with the pipe instead.
2055 * @param $list array of strings to put in a pipe list
2056 * @return string
2057 */
2058 function pipeList( $list ) {
2059 return implode(
2060 $list,
2061 wfMsgExt( 'pipe-separator', array( 'escapenoentities', 'language' => $this ) ) );
2062 }
2063
2064 /**
2065 * Truncate a string to a specified length in bytes, appending an optional
2066 * string (e.g. for ellipses)
2067 *
2068 * The database offers limited byte lengths for some columns in the database;
2069 * multi-byte character sets mean we need to ensure that only whole characters
2070 * are included, otherwise broken characters can be passed to the user
2071 *
2072 * If $length is negative, the string will be truncated from the beginning
2073 *
2074 * @param $string String to truncate
2075 * @param $length Int: maximum length (excluding ellipses)
2076 * @param $ellipsis String to append to the truncated text
2077 * @return string
2078 */
2079 function truncate( $string, $length, $ellipsis = '...' ) {
2080 # Use the localized ellipsis character
2081 if( $ellipsis == '...' ) {
2082 $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
2083 }
2084
2085 if( $length == 0 ) {
2086 return $ellipsis;
2087 }
2088 if ( strlen( $string ) <= abs( $length ) ) {
2089 return $string;
2090 }
2091 if( $length > 0 ) {
2092 $string = substr( $string, 0, $length );
2093 $char = ord( $string[strlen( $string ) - 1] );
2094 $m = array();
2095 if ($char >= 0xc0) {
2096 # We got the first byte only of a multibyte char; remove it.
2097 $string = substr( $string, 0, -1 );
2098 } elseif( $char >= 0x80 &&
2099 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
2100 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
2101 # We chopped in the middle of a character; remove it
2102 $string = $m[1];
2103 }
2104 return $string . $ellipsis;
2105 } else {
2106 $string = substr( $string, $length );
2107 $char = ord( $string[0] );
2108 if( $char >= 0x80 && $char < 0xc0 ) {
2109 # We chopped in the middle of a character; remove the whole thing
2110 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
2111 }
2112 return $ellipsis . $string;
2113 }
2114 }
2115
2116 /**
2117 * Grammatical transformations, needed for inflected languages
2118 * Invoked by putting {{grammar:case|word}} in a message
2119 *
2120 * @param $word string
2121 * @param $case string
2122 * @return string
2123 */
2124 function convertGrammar( $word, $case ) {
2125 global $wgGrammarForms;
2126 if ( isset($wgGrammarForms[$this->getCode()][$case][$word]) ) {
2127 return $wgGrammarForms[$this->getCode()][$case][$word];
2128 }
2129 return $word;
2130 }
2131
2132 /**
2133 * Provides an alternative text depending on specified gender.
2134 * Usage {{gender:username|masculine|feminine|neutral}}.
2135 * username is optional, in which case the gender of current user is used,
2136 * but only in (some) interface messages; otherwise default gender is used.
2137 * If second or third parameter are not specified, masculine is used.
2138 * These details may be overriden per language.
2139 */
2140 function gender( $gender, $forms ) {
2141 if ( !count($forms) ) { return ''; }
2142 $forms = $this->preConvertPlural( $forms, 2 );
2143 if ( $gender === 'male' ) return $forms[0];
2144 if ( $gender === 'female' ) return $forms[1];
2145 return isset($forms[2]) ? $forms[2] : $forms[0];
2146 }
2147
2148 /**
2149 * Plural form transformations, needed for some languages.
2150 * For example, there are 3 form of plural in Russian and Polish,
2151 * depending on "count mod 10". See [[w:Plural]]
2152 * For English it is pretty simple.
2153 *
2154 * Invoked by putting {{plural:count|wordform1|wordform2}}
2155 * or {{plural:count|wordform1|wordform2|wordform3}}
2156 *
2157 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
2158 *
2159 * @param $count Integer: non-localized number
2160 * @param $forms Array: different plural forms
2161 * @return string Correct form of plural for $count in this language
2162 */
2163 function convertPlural( $count, $forms ) {
2164 if ( !count($forms) ) { return ''; }
2165 $forms = $this->preConvertPlural( $forms, 2 );
2166
2167 return ( $count == 1 ) ? $forms[0] : $forms[1];
2168 }
2169
2170 /**
2171 * Checks that convertPlural was given an array and pads it to requested
2172 * amound of forms by copying the last one.
2173 *
2174 * @param $count Integer: How many forms should there be at least
2175 * @param $forms Array of forms given to convertPlural
2176 * @return array Padded array of forms or an exception if not an array
2177 */
2178 protected function preConvertPlural( /* Array */ $forms, $count ) {
2179 while ( count($forms) < $count ) {
2180 $forms[] = $forms[count($forms)-1];
2181 }
2182 return $forms;
2183 }
2184
2185 /**
2186 * For translaing of expiry times
2187 * @param $str String: the validated block time in English
2188 * @return Somehow translated block time
2189 * @see LanguageFi.php for example implementation
2190 */
2191 function translateBlockExpiry( $str ) {
2192
2193 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
2194
2195 if ( $scBlockExpiryOptions == '-') {
2196 return $str;
2197 }
2198
2199 foreach (explode(',', $scBlockExpiryOptions) as $option) {
2200 if ( strpos($option, ":") === false )
2201 continue;
2202 list($show, $value) = explode(":", $option);
2203 if ( strcmp ( $str, $value) == 0 ) {
2204 return htmlspecialchars( trim( $show ) );
2205 }
2206 }
2207
2208 return $str;
2209 }
2210
2211 /**
2212 * languages like Chinese need to be segmented in order for the diff
2213 * to be of any use
2214 *
2215 * @param $text String
2216 * @return String
2217 */
2218 function segmentForDiff( $text ) {
2219 return $text;
2220 }
2221
2222 /**
2223 * and unsegment to show the result
2224 *
2225 * @param $text String
2226 * @return String
2227 */
2228 function unsegmentForDiff( $text ) {
2229 return $text;
2230 }
2231
2232 # convert text to different variants of a language.
2233 function convert( $text, $isTitle = false, $variant = null ) {
2234 return $this->mConverter->convert($text, $isTitle, $variant);
2235 }
2236
2237 # Convert text from within Parser
2238 function parserConvert( $text, &$parser ) {
2239 return $this->mConverter->parserConvert( $text, $parser );
2240 }
2241
2242 # Check if this is a language with variants
2243 function hasVariants(){
2244 return sizeof($this->getVariants())>1;
2245 }
2246
2247 # Put custom tags (e.g. -{ }-) around math to prevent conversion
2248 function armourMath($text){
2249 return $this->mConverter->armourMath($text);
2250 }
2251
2252
2253 /**
2254 * Perform output conversion on a string, and encode for safe HTML output.
2255 * @param $text String
2256 * @param $isTitle Bool -- wtf?
2257 * @return string
2258 * @todo this should get integrated somewhere sane
2259 */
2260 function convertHtml( $text, $isTitle = false ) {
2261 return htmlspecialchars( $this->convert( $text, $isTitle ) );
2262 }
2263
2264 function convertCategoryKey( $key ) {
2265 return $this->mConverter->convertCategoryKey( $key );
2266 }
2267
2268 /**
2269 * get the list of variants supported by this langauge
2270 * see sample implementation in LanguageZh.php
2271 *
2272 * @return array an array of language codes
2273 */
2274 function getVariants() {
2275 return $this->mConverter->getVariants();
2276 }
2277
2278
2279 function getPreferredVariant( $fromUser = true ) {
2280 return $this->mConverter->getPreferredVariant( $fromUser );
2281 }
2282
2283 /**
2284 * if a language supports multiple variants, it is
2285 * possible that non-existing link in one variant
2286 * actually exists in another variant. this function
2287 * tries to find it. See e.g. LanguageZh.php
2288 *
2289 * @param $link String: the name of the link
2290 * @param $nt Mixed: the title object of the link
2291 * @param boolean $ignoreOtherCond: to disable other conditions when
2292 * we need to transclude a template or update a category's link
2293 * @return null the input parameters may be modified upon return
2294 */
2295 function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
2296 $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
2297 }
2298
2299 /**
2300 * If a language supports multiple variants, converts text
2301 * into an array of all possible variants of the text:
2302 * 'variant' => text in that variant
2303 */
2304 function convertLinkToAllVariants($text){
2305 return $this->mConverter->convertLinkToAllVariants($text);
2306 }
2307
2308
2309 /**
2310 * returns language specific options used by User::getPageRenderHash()
2311 * for example, the preferred language variant
2312 *
2313 * @return string
2314 */
2315 function getExtraHashOptions() {
2316 return $this->mConverter->getExtraHashOptions();
2317 }
2318
2319 /**
2320 * for languages that support multiple variants, the title of an
2321 * article may be displayed differently in different variants. this
2322 * function returns the apporiate title defined in the body of the article.
2323 *
2324 * @return string
2325 */
2326 function getParsedTitle() {
2327 return $this->mConverter->getParsedTitle();
2328 }
2329
2330 /**
2331 * Enclose a string with the "no conversion" tag. This is used by
2332 * various functions in the Parser
2333 *
2334 * @param $text String: text to be tagged for no conversion
2335 * @param $noParse
2336 * @return string the tagged text
2337 */
2338 function markNoConversion( $text, $noParse=false ) {
2339 return $this->mConverter->markNoConversion( $text, $noParse );
2340 }
2341
2342 /**
2343 * Callback function for magicword 'groupconvert'
2344 *
2345 * @param string $group: the group name called for
2346 * @return blank string
2347 */
2348 function groupConvert( $group ) {
2349 return $this->mConverter->groupConvert( $group );
2350 }
2351
2352 /**
2353 * A regular expression to match legal word-trailing characters
2354 * which should be merged onto a link of the form [[foo]]bar.
2355 *
2356 * @return string
2357 */
2358 function linkTrail() {
2359 $this->load();
2360 return $this->linkTrail;
2361 }
2362
2363 function getLangObj() {
2364 return $this;
2365 }
2366
2367 /**
2368 * Get the RFC 3066 code for this language object
2369 */
2370 function getCode() {
2371 return $this->mCode;
2372 }
2373
2374 function setCode( $code ) {
2375 $this->mCode = $code;
2376 }
2377
2378 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
2379 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
2380 }
2381
2382 static function getMessagesFileName( $code ) {
2383 global $IP;
2384 return self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
2385 }
2386
2387 static function getClassFileName( $code ) {
2388 global $IP;
2389 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
2390 }
2391
2392 static function getLocalisationArray( $code, $disableCache = false ) {
2393 self::loadLocalisation( $code, $disableCache );
2394 return self::$mLocalisationCache[$code];
2395 }
2396
2397 /**
2398 * Load localisation data for a given code into the static cache
2399 *
2400 * @return array Dependencies, map of filenames to mtimes
2401 */
2402 static function loadLocalisation( $code, $disableCache = false ) {
2403 static $recursionGuard = array();
2404 global $wgMemc, $wgEnableSerializedMessages, $wgCheckSerialized;
2405
2406 if ( !$code ) {
2407 throw new MWException( "Invalid language code requested" );
2408 }
2409
2410 if ( !$disableCache ) {
2411 # Try the per-process cache
2412 if ( isset( self::$mLocalisationCache[$code] ) ) {
2413 return self::$mLocalisationCache[$code]['deps'];
2414 }
2415
2416 wfProfileIn( __METHOD__ );
2417
2418 # Try the serialized directory
2419 if( $wgEnableSerializedMessages ) {
2420 $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) );
2421 if ( $cache ) {
2422 if ( $wgCheckSerialized && self::isLocalisationOutOfDate( $cache ) ) {
2423 $cache = false;
2424 wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" );
2425 } else {
2426 self::$mLocalisationCache[$code] = $cache;
2427 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
2428 wfProfileOut( __METHOD__ );
2429 return self::$mLocalisationCache[$code]['deps'];
2430 }
2431 }
2432 } else {
2433 $cache = false;
2434 }
2435
2436 # Try the global cache
2437 $memcKey = wfMemcKey('localisation', $code );
2438 $fbMemcKey = wfMemcKey('fallback', $cache['fallback'] );
2439 $cache = $wgMemc->get( $memcKey );
2440 if ( $cache ) {
2441 if ( self::isLocalisationOutOfDate( $cache ) ) {
2442 $wgMemc->delete( $memcKey );
2443 $wgMemc->delete( $fbMemcKey );
2444 $cache = false;
2445 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired\n" );
2446 } else {
2447 self::$mLocalisationCache[$code] = $cache;
2448 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
2449 wfProfileOut( __METHOD__ );
2450 return $cache['deps'];
2451 }
2452 }
2453 } else {
2454 wfProfileIn( __METHOD__ );
2455 }
2456
2457 # Default fallback, may be overridden when the messages file is included
2458 if ( $code != 'en' ) {
2459 $fallback = 'en';
2460 } else {
2461 $fallback = false;
2462 }
2463
2464 # Load the primary localisation from the source file
2465 $filename = self::getMessagesFileName( $code );
2466 if ( !file_exists( $filename ) ) {
2467 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
2468 $cache = compact( self::$mLocalisationKeys ); // Set correct fallback
2469 $deps = array();
2470 } else {
2471 $deps = array( $filename => filemtime( $filename ) );
2472 require( $filename );
2473 $cache = compact( self::$mLocalisationKeys );
2474 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
2475 }
2476
2477 # Load magic word source file
2478 global $IP;
2479 $filename = "$IP/includes/MagicWord.php";
2480 $newDeps = array( $filename => filemtime( $filename ) );
2481 $deps = array_merge( $deps, $newDeps );
2482
2483 if ( !empty( $fallback ) ) {
2484 # Load the fallback localisation, with a circular reference guard
2485 if ( isset( $recursionGuard[$code] ) ) {
2486 throw new MWException( "Error: Circular fallback reference in language code $code" );
2487 }
2488 $recursionGuard[$code] = true;
2489 $newDeps = self::loadLocalisation( $fallback, $disableCache );
2490 unset( $recursionGuard[$code] );
2491
2492 $secondary = self::$mLocalisationCache[$fallback];
2493 $deps = array_merge( $deps, $newDeps );
2494
2495 # Merge the fallback localisation with the current localisation
2496 foreach ( self::$mLocalisationKeys as $key ) {
2497 if ( isset( $cache[$key] ) ) {
2498 if ( isset( $secondary[$key] ) ) {
2499 if ( in_array( $key, self::$mMergeableMapKeys ) ) {
2500 $cache[$key] = $cache[$key] + $secondary[$key];
2501 } elseif ( in_array( $key, self::$mMergeableListKeys ) ) {
2502 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
2503 } elseif ( in_array( $key, self::$mMergeableAliasListKeys ) ) {
2504 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
2505 }
2506 }
2507 } else {
2508 $cache[$key] = $secondary[$key];
2509 }
2510 }
2511
2512 # Merge bookstore lists if requested
2513 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
2514 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
2515 }
2516 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
2517 unset( $cache['bookstoreList']['inherit'] );
2518 }
2519 }
2520
2521 # Add dependencies to the cache entry
2522 $cache['deps'] = $deps;
2523
2524 # Replace spaces with underscores in namespace names
2525 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
2526
2527 # And do the same for specialpage aliases. $page is an array.
2528 foreach ( $cache['specialPageAliases'] as &$page ) {
2529 $page = str_replace( ' ', '_', $page );
2530 }
2531 # Decouple the reference to prevent accidental damage
2532 unset($page);
2533
2534 # Save to both caches
2535 self::$mLocalisationCache[$code] = $cache;
2536 if ( !$disableCache ) {
2537 $wgMemc->set( $memcKey, $cache );
2538 $wgMemc->set( $fbMemcKey, (string) $cache['fallback'] );
2539 }
2540
2541 wfProfileOut( __METHOD__ );
2542 return $deps;
2543 }
2544
2545 /**
2546 * Test if a given localisation cache is out of date with respect to the
2547 * source Messages files. This is done automatically for the global cache
2548 * in $wgMemc, but is only done on certain occasions for the serialized
2549 * data file.
2550 *
2551 * @param $cache mixed Either a language code or a cache array
2552 */
2553 static function isLocalisationOutOfDate( $cache ) {
2554 if ( !is_array( $cache ) ) {
2555 self::loadLocalisation( $cache );
2556 $cache = self::$mLocalisationCache[$cache];
2557 }
2558 // At least one language file and the MagicWord file needed
2559 if( count($cache['deps']) < 2 ) {
2560 return true;
2561 }
2562 $expired = false;
2563 foreach ( $cache['deps'] as $file => $mtime ) {
2564 if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
2565 $expired = true;
2566 break;
2567 }
2568 }
2569 return $expired;
2570 }
2571
2572 /**
2573 * Get the fallback for a given language
2574 */
2575 static function getFallbackFor( $code ) {
2576 // Shortcut
2577 if ( $code === 'en' ) return false;
2578
2579 // Local cache
2580 static $cache = array();
2581 // Quick return
2582 if ( isset($cache[$code]) ) return $cache[$code];
2583
2584 // Try memcache
2585 global $wgMemc;
2586 $memcKey = wfMemcKey( 'fallback', $code );
2587 $fbcode = $wgMemc->get( $memcKey );
2588
2589 if ( is_string($fbcode) ) {
2590 // False is stored as a string to detect failures in memcache properly
2591 if ( $fbcode === '' ) $fbcode = false;
2592
2593 // Update local cache and return
2594 $cache[$code] = $fbcode;
2595 return $fbcode;
2596 }
2597
2598 // Nothing in caches, load and and update both caches
2599 self::loadLocalisation( $code );
2600 $fbcode = self::$mLocalisationCache[$code]['fallback'];
2601
2602 $cache[$code] = $fbcode;
2603 $wgMemc->set( $memcKey, (string) $fbcode );
2604
2605 return $fbcode;
2606 }
2607
2608 /**
2609 * Get all messages for a given language
2610 */
2611 static function getMessagesFor( $code ) {
2612 self::loadLocalisation( $code );
2613 return self::$mLocalisationCache[$code]['messages'];
2614 }
2615
2616 /**
2617 * Get a message for a given language
2618 */
2619 static function getMessageFor( $key, $code ) {
2620 self::loadLocalisation( $code );
2621 return isset( self::$mLocalisationCache[$code]['messages'][$key] ) ? self::$mLocalisationCache[$code]['messages'][$key] : null;
2622 }
2623
2624 /**
2625 * Load localisation data for this object
2626 */
2627 function load() {
2628 if ( !$this->mLoaded ) {
2629 self::loadLocalisation( $this->getCode() );
2630 $cache =& self::$mLocalisationCache[$this->getCode()];
2631 foreach ( self::$mLocalisationKeys as $key ) {
2632 $this->$key = $cache[$key];
2633 }
2634 $this->mLoaded = true;
2635
2636 $this->fixUpSettings();
2637 }
2638 }
2639
2640 /**
2641 * Do any necessary post-cache-load settings adjustment
2642 */
2643 function fixUpSettings() {
2644 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
2645 $wgNamespaceAliases, $wgAmericanDates;
2646 wfProfileIn( __METHOD__ );
2647 if ( $wgExtraNamespaces ) {
2648 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames;
2649 }
2650
2651 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
2652 if ( $wgMetaNamespaceTalk ) {
2653 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
2654 } else {
2655 $talk = $this->namespaceNames[NS_PROJECT_TALK];
2656 $this->namespaceNames[NS_PROJECT_TALK] =
2657 $this->fixVariableInNamespace( $talk );
2658 }
2659
2660 # The above mixing may leave namespaces out of canonical order.
2661 # Re-order by namespace ID number...
2662 ksort( $this->namespaceNames );
2663
2664 # Put namespace names and aliases into a hashtable.
2665 # If this is too slow, then we should arrange it so that it is done
2666 # before caching. The catch is that at pre-cache time, the above
2667 # class-specific fixup hasn't been done.
2668 $this->mNamespaceIds = array();
2669 foreach ( $this->namespaceNames as $index => $name ) {
2670 $this->mNamespaceIds[$this->lc($name)] = $index;
2671 }
2672 if ( $this->namespaceAliases ) {
2673 foreach ( $this->namespaceAliases as $name => $index ) {
2674 if ( $index === NS_PROJECT_TALK ) {
2675 unset( $this->namespaceAliases[$name] );
2676 $name = $this->fixVariableInNamespace( $name );
2677 $this->namespaceAliases[$name] = $index;
2678 }
2679 $this->mNamespaceIds[$this->lc($name)] = $index;
2680 }
2681 }
2682 if ( $wgNamespaceAliases ) {
2683 foreach ( $wgNamespaceAliases as $name => $index ) {
2684 $this->mNamespaceIds[$this->lc($name)] = $index;
2685 }
2686 }
2687
2688 if ( $this->defaultDateFormat == 'dmy or mdy' ) {
2689 $this->defaultDateFormat = $wgAmericanDates ? 'mdy' : 'dmy';
2690 }
2691 wfProfileOut( __METHOD__ );
2692 }
2693
2694 function fixVariableInNamespace( $talk ) {
2695 if ( strpos( $talk, '$1' ) === false ) return $talk;
2696
2697 global $wgMetaNamespace;
2698 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
2699
2700 # Allow grammar transformations
2701 # Allowing full message-style parsing would make simple requests
2702 # such as action=raw much more expensive than they need to be.
2703 # This will hopefully cover most cases.
2704 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
2705 array( &$this, 'replaceGrammarInNamespace' ), $talk );
2706 return str_replace( ' ', '_', $talk );
2707 }
2708
2709 function replaceGrammarInNamespace( $m ) {
2710 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
2711 }
2712
2713 static function getCaseMaps() {
2714 static $wikiUpperChars, $wikiLowerChars;
2715 if ( isset( $wikiUpperChars ) ) {
2716 return array( $wikiUpperChars, $wikiLowerChars );
2717 }
2718
2719 wfProfileIn( __METHOD__ );
2720 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
2721 if ( $arr === false ) {
2722 throw new MWException(
2723 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
2724 }
2725 extract( $arr );
2726 wfProfileOut( __METHOD__ );
2727 return array( $wikiUpperChars, $wikiLowerChars );
2728 }
2729
2730 function formatTimePeriod( $seconds ) {
2731 if ( $seconds < 10 ) {
2732 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
2733 } elseif ( $seconds < 60 ) {
2734 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
2735 } elseif ( $seconds < 3600 ) {
2736 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
2737 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
2738 } else {
2739 $hours = floor( $seconds / 3600 );
2740 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
2741 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
2742 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
2743 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
2744 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
2745 }
2746 }
2747
2748 function formatBitrate( $bps ) {
2749 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
2750 if ( $bps <= 0 ) {
2751 return $this->formatNum( $bps ) . $units[0];
2752 }
2753 $unitIndex = floor( log10( $bps ) / 3 );
2754 $mantissa = $bps / pow( 1000, $unitIndex );
2755 if ( $mantissa < 10 ) {
2756 $mantissa = round( $mantissa, 1 );
2757 } else {
2758 $mantissa = round( $mantissa );
2759 }
2760 return $this->formatNum( $mantissa ) . $units[$unitIndex];
2761 }
2762
2763 /**
2764 * Format a size in bytes for output, using an appropriate
2765 * unit (B, KB, MB or GB) according to the magnitude in question
2766 *
2767 * @param $size Size to format
2768 * @return string Plain text (not HTML)
2769 */
2770 function formatSize( $size ) {
2771 // For small sizes no decimal places necessary
2772 $round = 0;
2773 if( $size > 1024 ) {
2774 $size = $size / 1024;
2775 if( $size > 1024 ) {
2776 $size = $size / 1024;
2777 // For MB and bigger two decimal places are smarter
2778 $round = 2;
2779 if( $size > 1024 ) {
2780 $size = $size / 1024;
2781 $msg = 'size-gigabytes';
2782 } else {
2783 $msg = 'size-megabytes';
2784 }
2785 } else {
2786 $msg = 'size-kilobytes';
2787 }
2788 } else {
2789 $msg = 'size-bytes';
2790 }
2791 $size = round( $size, $round );
2792 $text = $this->getMessageFromDB( $msg );
2793 return str_replace( '$1', $this->formatNum( $size ), $text );
2794 }
2795 }