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