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