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