Update.
[lhc/web/wiklou.git] / languages / Language.php
1 <?php
2 /**
3 * @addtogroup Language
4 */
5
6 if( !defined( 'MEDIAWIKI' ) ) {
7 echo "This file is part of MediaWiki, it is not a valid entry point.\n";
8 exit( 1 );
9 }
10
11 #
12 # In general you should not make customizations in these language files
13 # directly, but should use the MediaWiki: special namespace to customize
14 # user interface messages through the wiki.
15 # See http://meta.wikipedia.org/wiki/MediaWiki_namespace
16 #
17 # NOTE TO TRANSLATORS: Do not copy this whole file when making translations!
18 # A lot of common constants and a base class with inheritable methods are
19 # defined here, which should not be redefined. See the other LanguageXx.php
20 # files for examples.
21 #
22
23 # Read language names
24 global $wgLanguageNames;
25 require_once( dirname(__FILE__) . '/Names.php' ) ;
26
27 global $wgInputEncoding, $wgOutputEncoding;
28
29 /**
30 * These are always UTF-8, they exist only for backwards compatibility
31 */
32 $wgInputEncoding = "UTF-8";
33 $wgOutputEncoding = "UTF-8";
34
35 if( function_exists( 'mb_strtoupper' ) ) {
36 mb_internal_encoding('UTF-8');
37 }
38
39 /* a fake language converter */
40 class FakeConverter {
41 var $mLang;
42 function FakeConverter($langobj) {$this->mLang = $langobj;}
43 function convert($t, $i) {return $t;}
44 function parserConvert($t, $p) {return $t;}
45 function getVariants() { return array( $this->mLang->getCode() ); }
46 function getPreferredVariant() {return $this->mLang->getCode(); }
47 function findVariantLink(&$l, &$n) {}
48 function getExtraHashOptions() {return '';}
49 function getParsedTitle() {return '';}
50 function markNoConversion($text, $noParse=false) {return $text;}
51 function convertCategoryKey( $key ) {return $key; }
52 function convertLinkToAllVariants($text){ return array( $this->mLang->getCode() => $text); }
53 function armourMath($text){ return $text; }
54 }
55
56 #--------------------------------------------------------------------------
57 # Internationalisation code
58 #--------------------------------------------------------------------------
59
60 class Language {
61 var $mConverter, $mVariants, $mCode, $mLoaded = false;
62
63 static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
64 'skinNames', 'mathNames',
65 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable',
66 'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
67 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
68 'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
69 'defaultDateFormat', 'extraUserToggles', 'specialPageAliases' );
70
71 static public $mMergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
72 'dateFormats', 'defaultUserOptionOverrides', 'magicWords' );
73
74 static public $mMergeableListKeys = array( 'extraUserToggles' );
75
76 static public $mMergeableAliasListKeys = array( 'specialPageAliases' );
77
78 static public $mLocalisationCache = 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 /**
105 * Create a language object for a given language code
106 */
107 static function factory( $code ) {
108 global $IP;
109 static $recursionLevel = 0;
110
111 if ( $code == 'en' ) {
112 $class = 'Language';
113 } else {
114 $class = 'Language' . str_replace( '-', '_', ucfirst( $code ) );
115 // Preload base classes to work around APC/PHP5 bug
116 if ( file_exists( "$IP/languages/classes/$class.deps.php" ) ) {
117 include_once("$IP/languages/classes/$class.deps.php");
118 }
119 if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
120 include_once("$IP/languages/classes/$class.php");
121 }
122 }
123
124 if ( $recursionLevel > 5 ) {
125 throw new MWException( "Language fallback loop detected when creating class $class\n" );
126 }
127
128 if( ! class_exists( $class ) ) {
129 $fallback = Language::getFallbackFor( $code );
130 ++$recursionLevel;
131 $lang = Language::factory( $fallback );
132 --$recursionLevel;
133 $lang->setCode( $code );
134 } else {
135 $lang = new $class;
136 }
137
138 return $lang;
139 }
140
141 function __construct() {
142 $this->mConverter = new FakeConverter($this);
143 // Set the code to the name of the descendant
144 if ( get_class( $this ) == 'Language' ) {
145 $this->mCode = 'en';
146 } else {
147 $this->mCode = str_replace( '_', '-', strtolower( substr( get_class( $this ), 8 ) ) );
148 }
149 }
150
151 /**
152 * Hook which will be called if this is the content language.
153 * Descendants can use this to register hook functions or modify globals
154 */
155 function initContLang() {}
156
157 /**
158 * @deprecated
159 * @return array
160 */
161 function getDefaultUserOptions() {
162 return User::getDefaultOptions();
163 }
164
165 function getFallbackLanguageCode() {
166 $this->load();
167 return $this->fallback;
168 }
169
170 /**
171 * Exports $wgBookstoreListEn
172 * @return array
173 */
174 function getBookstoreList() {
175 $this->load();
176 return $this->bookstoreList;
177 }
178
179 /**
180 * @return array
181 */
182 function getNamespaces() {
183 $this->load();
184 return $this->namespaceNames;
185 }
186
187 /**
188 * A convenience function that returns the same thing as
189 * getNamespaces() except with the array values changed to ' '
190 * where it found '_', useful for producing output to be displayed
191 * e.g. in <select> forms.
192 *
193 * @return array
194 */
195 function getFormattedNamespaces() {
196 $ns = $this->getNamespaces();
197 foreach($ns as $k => $v) {
198 $ns[$k] = strtr($v, '_', ' ');
199 }
200 return $ns;
201 }
202
203 /**
204 * Get a namespace value by key
205 * <code>
206 * $mw_ns = $wgContLang->getNsText( NS_MEDIAWIKI );
207 * echo $mw_ns; // prints 'MediaWiki'
208 * </code>
209 *
210 * @param int $index the array key of the namespace to return
211 * @return mixed, string if the namespace value exists, otherwise false
212 */
213 function getNsText( $index ) {
214 $ns = $this->getNamespaces();
215 return isset( $ns[$index] ) ? $ns[$index] : false;
216 }
217
218 /**
219 * A convenience function that returns the same thing as
220 * getNsText() except with '_' changed to ' ', useful for
221 * producing output.
222 *
223 * @return array
224 */
225 function getFormattedNsText( $index ) {
226 $ns = $this->getNsText( $index );
227 return strtr($ns, '_', ' ');
228 }
229
230 /**
231 * Get a namespace key by value, case insensitive.
232 * Only matches namespace names for the current language, not the
233 * canonical ones defined in Namespace.php.
234 *
235 * @param string $text
236 * @return mixed An integer if $text is a valid value otherwise false
237 */
238 function getLocalNsIndex( $text ) {
239 $this->load();
240 $lctext = $this->lc($text);
241 return isset( $this->mNamespaceIds[$lctext] ) ? $this->mNamespaceIds[$lctext] : false;
242 }
243
244 /**
245 * Get a namespace key by value, case insensitive. Canonical namespace
246 * names override custom ones defined for the current language.
247 *
248 * @param string $text
249 * @return mixed An integer if $text is a valid value otherwise false
250 */
251 function getNsIndex( $text ) {
252 $this->load();
253 $lctext = $this->lc($text);
254 if( ( $ns = Namespace::getCanonicalIndex( $lctext ) ) !== null ) return $ns;
255 return isset( $this->mNamespaceIds[$lctext] ) ? $this->mNamespaceIds[$lctext] : false;
256 }
257
258 /**
259 * short names for language variants used for language conversion links.
260 *
261 * @param string $code
262 * @return string
263 */
264 function getVariantname( $code ) {
265 return $this->getMessageFromDB( "variantname-$code" );
266 }
267
268 function specialPage( $name ) {
269 $aliases = $this->getSpecialPageAliases();
270 if ( isset( $aliases[$name][0] ) ) {
271 $name = $aliases[$name][0];
272 }
273 return $this->getNsText(NS_SPECIAL) . ':' . $name;
274 }
275
276 function getQuickbarSettings() {
277 return array(
278 $this->getMessage( 'qbsettings-none' ),
279 $this->getMessage( 'qbsettings-fixedleft' ),
280 $this->getMessage( 'qbsettings-fixedright' ),
281 $this->getMessage( 'qbsettings-floatingleft' ),
282 $this->getMessage( 'qbsettings-floatingright' )
283 );
284 }
285
286 function getSkinNames() {
287 $this->load();
288 return $this->skinNames;
289 }
290
291 function getMathNames() {
292 $this->load();
293 return $this->mathNames;
294 }
295
296 function getDatePreferences() {
297 $this->load();
298 return $this->datePreferences;
299 }
300
301 function getDateFormats() {
302 $this->load();
303 return $this->dateFormats;
304 }
305
306 function getDefaultDateFormat() {
307 $this->load();
308 return $this->defaultDateFormat;
309 }
310
311 function getDatePreferenceMigrationMap() {
312 $this->load();
313 return $this->datePreferenceMigrationMap;
314 }
315
316 function getDefaultUserOptionOverrides() {
317 $this->load();
318 return $this->defaultUserOptionOverrides;
319 }
320
321 function getExtraUserToggles() {
322 $this->load();
323 return $this->extraUserToggles;
324 }
325
326 function getUserToggle( $tog ) {
327 return $this->getMessageFromDB( "tog-$tog" );
328 }
329
330 /**
331 * Get language names, indexed by code.
332 * If $customisedOnly is true, only returns codes with a messages file
333 */
334 public static function getLanguageNames( $customisedOnly = false ) {
335 global $wgLanguageNames;
336 if ( !$customisedOnly ) {
337 return $wgLanguageNames;
338 }
339
340 global $IP;
341 $names = array();
342 $dir = opendir( "$IP/languages/messages" );
343 while( false !== ( $file = readdir( $dir ) ) ) {
344 $m = array();
345 if( preg_match( '/Messages([A-Z][a-z_]+)\.php$/', $file, $m ) ) {
346 $code = str_replace( '_', '-', strtolower( $m[1] ) );
347 if ( isset( $wgLanguageNames[$code] ) ) {
348 $names[$code] = $wgLanguageNames[$code];
349 }
350 }
351 }
352 closedir( $dir );
353 return $names;
354 }
355
356 /**
357 * Ugly hack to get a message maybe from the MediaWiki namespace, if this
358 * language object is the content or user language.
359 */
360 function getMessageFromDB( $msg ) {
361 global $wgContLang, $wgLang;
362 if ( $wgContLang->getCode() == $this->getCode() ) {
363 # Content language
364 return wfMsgForContent( $msg );
365 } elseif ( $wgLang->getCode() == $this->getCode() ) {
366 # User language
367 return wfMsg( $msg );
368 } else {
369 # Neither, get from localisation
370 return $this->getMessage( $msg );
371 }
372 }
373
374 function getLanguageName( $code ) {
375 global $wgLanguageNames;
376 if ( ! array_key_exists( $code, $wgLanguageNames ) ) {
377 return '';
378 }
379 return $wgLanguageNames[$code];
380 }
381
382 function getMonthName( $key ) {
383 return $this->getMessageFromDB( self::$mMonthMsgs[$key-1] );
384 }
385
386 function getMonthNameGen( $key ) {
387 return $this->getMessageFromDB( self::$mMonthGenMsgs[$key-1] );
388 }
389
390 function getMonthAbbreviation( $key ) {
391 return $this->getMessageFromDB( self::$mMonthAbbrevMsgs[$key-1] );
392 }
393
394 function getWeekdayName( $key ) {
395 return $this->getMessageFromDB( self::$mWeekdayMsgs[$key-1] );
396 }
397
398 function getWeekdayAbbreviation( $key ) {
399 return $this->getMessageFromDB( self::$mWeekdayAbbrevMsgs[$key-1] );
400 }
401
402 /**
403 * Used by date() and time() to adjust the time output.
404 * @public
405 * @param int $ts the time in date('YmdHis') format
406 * @param mixed $tz adjust the time by this amount (default false,
407 * mean we get user timecorrection setting)
408 * @return int
409 */
410 function userAdjust( $ts, $tz = false ) {
411 global $wgUser, $wgLocalTZoffset;
412
413 if (!$tz) {
414 $tz = $wgUser->getOption( 'timecorrection' );
415 }
416
417 # minutes and hours differences:
418 $minDiff = 0;
419 $hrDiff = 0;
420
421 if ( $tz === '' ) {
422 # Global offset in minutes.
423 if( isset($wgLocalTZoffset) ) {
424 if( $wgLocalTZoffset >= 0 ) {
425 $hrDiff = floor($wgLocalTZoffset / 60);
426 } else {
427 $hrDiff = ceil($wgLocalTZoffset / 60);
428 }
429 $minDiff = $wgLocalTZoffset % 60;
430 }
431 } elseif ( strpos( $tz, ':' ) !== false ) {
432 $tzArray = explode( ':', $tz );
433 $hrDiff = intval($tzArray[0]);
434 $minDiff = intval($hrDiff < 0 ? -$tzArray[1] : $tzArray[1]);
435 } else {
436 $hrDiff = intval( $tz );
437 }
438
439 # No difference ? Return time unchanged
440 if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
441
442 wfSuppressWarnings(); // E_STRICT system time bitching
443 # Generate an adjusted date
444 $t = mktime( (
445 (int)substr( $ts, 8, 2) ) + $hrDiff, # Hours
446 (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
447 (int)substr( $ts, 12, 2 ), # Seconds
448 (int)substr( $ts, 4, 2 ), # Month
449 (int)substr( $ts, 6, 2 ), # Day
450 (int)substr( $ts, 0, 4 ) ); #Year
451
452 $date = date( 'YmdHis', $t );
453 wfRestoreWarnings();
454
455 return $date;
456 }
457
458 /**
459 * This is a workalike of PHP's date() function, but with better
460 * internationalisation, a reduced set of format characters, and a better
461 * escaping format.
462 *
463 * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the
464 * PHP manual for definitions. There are a number of extensions, which
465 * start with "x":
466 *
467 * xn Do not translate digits of the next numeric format character
468 * xN Toggle raw digit (xn) flag, stays set until explicitly unset
469 * xr Use roman numerals for the next numeric format character
470 * xx Literal x
471 * xg Genitive month name
472 *
473 * Characters enclosed in double quotes will be considered literal (with
474 * the quotes themselves removed). Unmatched quotes will be considered
475 * literal quotes. Example:
476 *
477 * "The month is" F => The month is January
478 * i's" => 20'11"
479 *
480 * Backslash escaping is also supported.
481 *
482 * Input timestamp is assumed to be pre-normalized to the desired local
483 * time zone, if any.
484 *
485 * @param string $format
486 * @param string $ts 14-character timestamp
487 * YYYYMMDDHHMMSS
488 * 01234567890123
489 */
490 function sprintfDate( $format, $ts ) {
491 $s = '';
492 $raw = false;
493 $roman = false;
494 $unix = false;
495 $rawToggle = false;
496 for ( $p = 0; $p < strlen( $format ); $p++ ) {
497 $num = false;
498 $code = $format[$p];
499 if ( $code == 'x' && $p < strlen( $format ) - 1 ) {
500 $code .= $format[++$p];
501 }
502
503 switch ( $code ) {
504 case 'xx':
505 $s .= 'x';
506 break;
507 case 'xn':
508 $raw = true;
509 break;
510 case 'xN':
511 $rawToggle = !$rawToggle;
512 break;
513 case 'xr':
514 $roman = true;
515 break;
516 case 'xg':
517 $s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
518 break;
519 case 'd':
520 $num = substr( $ts, 6, 2 );
521 break;
522 case 'D':
523 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
524 $s .= $this->getWeekdayAbbreviation( gmdate( 'w', $unix ) + 1 );
525 break;
526 case 'j':
527 $num = intval( substr( $ts, 6, 2 ) );
528 break;
529 case 'l':
530 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
531 $s .= $this->getWeekdayName( gmdate( 'w', $unix ) + 1 );
532 break;
533 case 'N':
534 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
535 $w = gmdate( 'w', $unix );
536 $num = $w ? $w : 7;
537 break;
538 case 'w':
539 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
540 $num = gmdate( 'w', $unix );
541 break;
542 case 'z':
543 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
544 $num = gmdate( 'z', $unix );
545 break;
546 case 'W':
547 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
548 $num = gmdate( 'W', $unix );
549 break;
550 case 'F':
551 $s .= $this->getMonthName( substr( $ts, 4, 2 ) );
552 break;
553 case 'm':
554 $num = substr( $ts, 4, 2 );
555 break;
556 case 'M':
557 $s .= $this->getMonthAbbreviation( substr( $ts, 4, 2 ) );
558 break;
559 case 'n':
560 $num = intval( substr( $ts, 4, 2 ) );
561 break;
562 case 't':
563 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
564 $num = gmdate( 't', $unix );
565 break;
566 case 'L':
567 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
568 $num = gmdate( 'L', $unix );
569 break;
570 case 'Y':
571 $num = substr( $ts, 0, 4 );
572 break;
573 case 'y':
574 $num = substr( $ts, 2, 2 );
575 break;
576 case 'a':
577 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'am' : 'pm';
578 break;
579 case 'A':
580 $s .= intval( substr( $ts, 8, 2 ) ) < 12 ? 'AM' : 'PM';
581 break;
582 case 'g':
583 $h = substr( $ts, 8, 2 );
584 $num = $h % 12 ? $h % 12 : 12;
585 break;
586 case 'G':
587 $num = intval( substr( $ts, 8, 2 ) );
588 break;
589 case 'h':
590 $h = substr( $ts, 8, 2 );
591 $num = sprintf( '%02d', $h % 12 ? $h % 12 : 12 );
592 break;
593 case 'H':
594 $num = substr( $ts, 8, 2 );
595 break;
596 case 'i':
597 $num = substr( $ts, 10, 2 );
598 break;
599 case 's':
600 $num = substr( $ts, 12, 2 );
601 break;
602 case 'c':
603 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
604 $s .= gmdate( 'c', $unix );
605 break;
606 case 'r':
607 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
608 $s .= gmdate( 'r', $unix );
609 break;
610 case 'U':
611 if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
612 $num = $unix;
613 break;
614 case '\\':
615 # Backslash escaping
616 if ( $p < strlen( $format ) - 1 ) {
617 $s .= $format[++$p];
618 } else {
619 $s .= '\\';
620 }
621 break;
622 case '"':
623 # Quoted literal
624 if ( $p < strlen( $format ) - 1 ) {
625 $endQuote = strpos( $format, '"', $p + 1 );
626 if ( $endQuote === false ) {
627 # No terminating quote, assume literal "
628 $s .= '"';
629 } else {
630 $s .= substr( $format, $p + 1, $endQuote - $p - 1 );
631 $p = $endQuote;
632 }
633 } else {
634 # Quote at end of string, assume literal "
635 $s .= '"';
636 }
637 break;
638 default:
639 $s .= $format[$p];
640 }
641 if ( $num !== false ) {
642 if ( $rawToggle || $raw ) {
643 $s .= $num;
644 $raw = false;
645 } elseif ( $roman ) {
646 $s .= self::romanNumeral( $num );
647 $roman = false;
648 } else {
649 $s .= $this->formatNum( $num, true );
650 }
651 $num = false;
652 }
653 }
654 return $s;
655 }
656
657 /**
658 * Roman number formatting up to 3000
659 */
660 static function romanNumeral( $num ) {
661 static $table = array(
662 array( '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X' ),
663 array( '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC', 'C' ),
664 array( '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM', 'M' ),
665 array( '', 'M', 'MM', 'MMM' )
666 );
667
668 $num = intval( $num );
669 if ( $num > 3000 || $num <= 0 ) {
670 return $num;
671 }
672
673 $s = '';
674 for ( $pow10 = 1000, $i = 3; $i >= 0; $pow10 /= 10, $i-- ) {
675 if ( $num >= $pow10 ) {
676 $s .= $table[$i][floor($num / $pow10)];
677 }
678 $num = $num % $pow10;
679 }
680 return $s;
681 }
682
683 /**
684 * This is meant to be used by time(), date(), and timeanddate() to get
685 * the date preference they're supposed to use, it should be used in
686 * all children.
687 *
688 *<code>
689 * function timeanddate([...], $format = true) {
690 * $datePreference = $this->dateFormat($format);
691 * [...]
692 * }
693 *</code>
694 *
695 * @param mixed $usePrefs: if true, the user's preference is used
696 * if false, the site/language default is used
697 * if int/string, assumed to be a format.
698 * @return string
699 */
700 function dateFormat( $usePrefs = true ) {
701 global $wgUser;
702
703 if( is_bool( $usePrefs ) ) {
704 if( $usePrefs ) {
705 $datePreference = $wgUser->getDatePreference();
706 } else {
707 $options = User::getDefaultOptions();
708 $datePreference = (string)$options['date'];
709 }
710 } else {
711 $datePreference = (string)$usePrefs;
712 }
713
714 // return int
715 if( $datePreference == '' ) {
716 return 'default';
717 }
718
719 return $datePreference;
720 }
721
722 /**
723 * @public
724 * @param mixed $ts the time format which needs to be turned into a
725 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
726 * @param bool $adj whether to adjust the time output according to the
727 * user configured offset ($timecorrection)
728 * @param mixed $format true to use user's date format preference
729 * @param string $timecorrection the time offset as returned by
730 * validateTimeZone() in Special:Preferences
731 * @return string
732 */
733 function date( $ts, $adj = false, $format = true, $timecorrection = false ) {
734 $this->load();
735 if ( $adj ) {
736 $ts = $this->userAdjust( $ts, $timecorrection );
737 }
738
739 $pref = $this->dateFormat( $format );
740 if( $pref == 'default' || !isset( $this->dateFormats["$pref date"] ) ) {
741 $pref = $this->defaultDateFormat;
742 }
743 return $this->sprintfDate( $this->dateFormats["$pref date"], $ts );
744 }
745
746 /**
747 * @public
748 * @param mixed $ts the time format which needs to be turned into a
749 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
750 * @param bool $adj whether to adjust the time output according to the
751 * user configured offset ($timecorrection)
752 * @param mixed $format true to use user's date format preference
753 * @param string $timecorrection the time offset as returned by
754 * validateTimeZone() in Special:Preferences
755 * @return string
756 */
757 function time( $ts, $adj = false, $format = true, $timecorrection = false ) {
758 $this->load();
759 if ( $adj ) {
760 $ts = $this->userAdjust( $ts, $timecorrection );
761 }
762
763 $pref = $this->dateFormat( $format );
764 if( $pref == 'default' || !isset( $this->dateFormats["$pref time"] ) ) {
765 $pref = $this->defaultDateFormat;
766 }
767 return $this->sprintfDate( $this->dateFormats["$pref time"], $ts );
768 }
769
770 /**
771 * @public
772 * @param mixed $ts the time format which needs to be turned into a
773 * date('YmdHis') format with wfTimestamp(TS_MW,$ts)
774 * @param bool $adj whether to adjust the time output according to the
775 * user configured offset ($timecorrection)
776
777 * @param mixed $format what format to return, if it's false output the
778 * default one (default true)
779 * @param string $timecorrection the time offset as returned by
780 * validateTimeZone() in Special:Preferences
781 * @return string
782 */
783 function timeanddate( $ts, $adj = false, $format = true, $timecorrection = false) {
784 $this->load();
785
786 $ts = wfTimestamp( TS_MW, $ts );
787
788 if ( $adj ) {
789 $ts = $this->userAdjust( $ts, $timecorrection );
790 }
791
792 $pref = $this->dateFormat( $format );
793 if( $pref == 'default' || !isset( $this->dateFormats["$pref both"] ) ) {
794 $pref = $this->defaultDateFormat;
795 }
796
797 return $this->sprintfDate( $this->dateFormats["$pref both"], $ts );
798 }
799
800 function getMessage( $key ) {
801 $this->load();
802 return isset( $this->messages[$key] ) ? $this->messages[$key] : null;
803 }
804
805 function getAllMessages() {
806 $this->load();
807 return $this->messages;
808 }
809
810 function iconv( $in, $out, $string ) {
811 # For most languages, this is a wrapper for iconv
812 return iconv( $in, $out . '//IGNORE', $string );
813 }
814
815 // callback functions for uc(), lc(), ucwords(), ucwordbreaks()
816 function ucwordbreaksCallbackAscii($matches){
817 return $this->ucfirst($matches[1]);
818 }
819
820 function ucwordbreaksCallbackMB($matches){
821 return mb_strtoupper($matches[0]);
822 }
823
824 function ucCallback($matches){
825 list( $wikiUpperChars ) = self::getCaseMaps();
826 return strtr( $matches[1], $wikiUpperChars );
827 }
828
829 function lcCallback($matches){
830 list( , $wikiLowerChars ) = self::getCaseMaps();
831 return strtr( $matches[1], $wikiLowerChars );
832 }
833
834 function ucwordsCallbackMB($matches){
835 return mb_strtoupper($matches[0]);
836 }
837
838 function ucwordsCallbackWiki($matches){
839 list( $wikiUpperChars ) = self::getCaseMaps();
840 return strtr( $matches[0], $wikiUpperChars );
841 }
842
843 function ucfirst( $str ) {
844 return self::uc( $str, true );
845 }
846
847 function uc( $str, $first = false ) {
848 if ( function_exists( 'mb_strtoupper' ) ) {
849 if ( $first ) {
850 if ( self::isMultibyte( $str ) ) {
851 return mb_strtoupper( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
852 } else {
853 return ucfirst( $str );
854 }
855 } else {
856 return self::isMultibyte( $str ) ? mb_strtoupper( $str ) : strtoupper( $str );
857 }
858 } else {
859 if ( self::isMultibyte( $str ) ) {
860 list( $wikiUpperChars ) = $this->getCaseMaps();
861 $x = $first ? '^' : '';
862 return preg_replace_callback(
863 "/$x([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
864 array($this,"ucCallback"),
865 $str
866 );
867 } else {
868 return $first ? ucfirst( $str ) : strtoupper( $str );
869 }
870 }
871 }
872
873 function lcfirst( $str ) {
874 return self::lc( $str, true );
875 }
876
877 function lc( $str, $first = false ) {
878 if ( function_exists( 'mb_strtolower' ) )
879 if ( $first )
880 if ( self::isMultibyte( $str ) )
881 return mb_strtolower( mb_substr( $str, 0, 1 ) ) . mb_substr( $str, 1 );
882 else
883 return strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 );
884 else
885 return self::isMultibyte( $str ) ? mb_strtolower( $str ) : strtolower( $str );
886 else
887 if ( self::isMultibyte( $str ) ) {
888 list( , $wikiLowerChars ) = self::getCaseMaps();
889 $x = $first ? '^' : '';
890 return preg_replace_callback(
891 "/$x([A-Z]|[\\xc0-\\xff][\\x80-\\xbf]*)/",
892 array($this,"lcCallback"),
893 $str
894 );
895 } else
896 return $first ? strtolower( substr( $str, 0, 1 ) ) . substr( $str, 1 ) : strtolower( $str );
897 }
898
899 function isMultibyte( $str ) {
900 return (bool)preg_match( '/[\x80-\xff]/', $str );
901 }
902
903 function ucwords($str) {
904 if ( self::isMultibyte( $str ) ) {
905 $str = self::lc($str);
906
907 // regexp to find first letter in each word (i.e. after each space)
908 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)| ([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
909
910 // function to use to capitalize a single char
911 if ( function_exists( 'mb_strtoupper' ) )
912 return preg_replace_callback(
913 $replaceRegexp,
914 array($this,"ucwordsCallbackMB"),
915 $str
916 );
917 else
918 return preg_replace_callback(
919 $replaceRegexp,
920 array($this,"ucwordsCallbackWiki"),
921 $str
922 );
923 }
924 else
925 return ucwords( strtolower( $str ) );
926 }
927
928 # capitalize words at word breaks
929 function ucwordbreaks($str){
930 if (self::isMultibyte( $str ) ) {
931 $str = self::lc($str);
932
933 // since \b doesn't work for UTF-8, we explicitely define word break chars
934 $breaks= "[ \-\(\)\}\{\.,\?!]";
935
936 // find first letter after word break
937 $replaceRegexp = "/^([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)|$breaks([a-z]|[\\xc0-\\xff][\\x80-\\xbf]*)/";
938
939 if ( function_exists( 'mb_strtoupper' ) )
940 return preg_replace_callback(
941 $replaceRegexp,
942 array($this,"ucwordbreaksCallbackMB"),
943 $str
944 );
945 else
946 return preg_replace_callback(
947 $replaceRegexp,
948 array($this,"ucwordsCallbackWiki"),
949 $str
950 );
951 }
952 else
953 return preg_replace_callback(
954 '/\b([\w\x80-\xff]+)\b/',
955 array($this,"ucwordbreaksCallbackAscii"),
956 $str );
957 }
958
959 /**
960 * Return a case-folded representation of $s
961 *
962 * This is a representation such that caseFold($s1)==caseFold($s2) if $s1
963 * and $s2 are the same except for the case of their characters. It is not
964 * necessary for the value returned to make sense when displayed.
965 *
966 * Do *not* perform any other normalisation in this function. If a caller
967 * uses this function when it should be using a more general normalisation
968 * function, then fix the caller.
969 */
970 function caseFold( $s ) {
971 return $this->uc( $s );
972 }
973
974 function checkTitleEncoding( $s ) {
975 if( is_array( $s ) ) {
976 wfDebugDieBacktrace( 'Given array to checkTitleEncoding.' );
977 }
978 # Check for non-UTF-8 URLs
979 $ishigh = preg_match( '/[\x80-\xff]/', $s);
980 if(!$ishigh) return $s;
981
982 $isutf8 = preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
983 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})+$/', $s );
984 if( $isutf8 ) return $s;
985
986 return $this->iconv( $this->fallback8bitEncoding(), "utf-8", $s );
987 }
988
989 function fallback8bitEncoding() {
990 $this->load();
991 return $this->fallback8bitEncoding;
992 }
993
994 /**
995 * Some languages have special punctuation to strip out
996 * or characters which need to be converted for MySQL's
997 * indexing to grok it correctly. Make such changes here.
998 *
999 * @param string $in
1000 * @return string
1001 */
1002 function stripForSearch( $string ) {
1003 global $wgDBtype;
1004 if ( $wgDBtype != 'mysql' ) {
1005 return $string;
1006 }
1007
1008 # MySQL fulltext index doesn't grok utf-8, so we
1009 # need to fold cases and convert to hex
1010
1011 wfProfileIn( __METHOD__ );
1012 if( function_exists( 'mb_strtolower' ) ) {
1013 $out = preg_replace(
1014 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1015 "'U8' . bin2hex( \"$1\" )",
1016 mb_strtolower( $string ) );
1017 } else {
1018 list( , $wikiLowerChars ) = self::getCaseMaps();
1019 $out = preg_replace(
1020 "/([\\xc0-\\xff][\\x80-\\xbf]*)/e",
1021 "'U8' . bin2hex( strtr( \"\$1\", \$wikiLowerChars ) )",
1022 $string );
1023 }
1024 wfProfileOut( __METHOD__ );
1025 return $out;
1026 }
1027
1028 function convertForSearchResult( $termsArray ) {
1029 # some languages, e.g. Chinese, need to do a conversion
1030 # in order for search results to be displayed correctly
1031 return $termsArray;
1032 }
1033
1034 /**
1035 * Get the first character of a string.
1036 *
1037 * @param string $s
1038 * @return string
1039 */
1040 function firstChar( $s ) {
1041 $matches = array();
1042 preg_match( '/^([\x00-\x7f]|[\xc0-\xdf][\x80-\xbf]|' .
1043 '[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3})/', $s, $matches);
1044
1045 return isset( $matches[1] ) ? $matches[1] : "";
1046 }
1047
1048 function initEncoding() {
1049 # Some languages may have an alternate char encoding option
1050 # (Esperanto X-coding, Japanese furigana conversion, etc)
1051 # If this language is used as the primary content language,
1052 # an override to the defaults can be set here on startup.
1053 }
1054
1055 function recodeForEdit( $s ) {
1056 # For some languages we'll want to explicitly specify
1057 # which characters make it into the edit box raw
1058 # or are converted in some way or another.
1059 # Note that if wgOutputEncoding is different from
1060 # wgInputEncoding, this text will be further converted
1061 # to wgOutputEncoding.
1062 global $wgEditEncoding;
1063 if( $wgEditEncoding == '' or
1064 $wgEditEncoding == 'UTF-8' ) {
1065 return $s;
1066 } else {
1067 return $this->iconv( 'UTF-8', $wgEditEncoding, $s );
1068 }
1069 }
1070
1071 function recodeInput( $s ) {
1072 # Take the previous into account.
1073 global $wgEditEncoding;
1074 if($wgEditEncoding != "") {
1075 $enc = $wgEditEncoding;
1076 } else {
1077 $enc = 'UTF-8';
1078 }
1079 if( $enc == 'UTF-8' ) {
1080 return $s;
1081 } else {
1082 return $this->iconv( $enc, 'UTF-8', $s );
1083 }
1084 }
1085
1086 /**
1087 * For right-to-left language support
1088 *
1089 * @return bool
1090 */
1091 function isRTL() {
1092 $this->load();
1093 return $this->rtl;
1094 }
1095
1096 /**
1097 * A hidden direction mark (LRM or RLM), depending on the language direction
1098 *
1099 * @return string
1100 */
1101 function getDirMark() {
1102 return $this->isRTL() ? "\xE2\x80\x8F" : "\xE2\x80\x8E";
1103 }
1104
1105 /**
1106 * An arrow, depending on the language direction
1107 *
1108 * @return string
1109 */
1110 function getArrow() {
1111 return $this->isRTL() ? '←' : '→';
1112 }
1113
1114 /**
1115 * To allow "foo[[bar]]" to extend the link over the whole word "foobar"
1116 *
1117 * @return bool
1118 */
1119 function linkPrefixExtension() {
1120 $this->load();
1121 return $this->linkPrefixExtension;
1122 }
1123
1124 function &getMagicWords() {
1125 $this->load();
1126 return $this->magicWords;
1127 }
1128
1129 # Fill a MagicWord object with data from here
1130 function getMagic( &$mw ) {
1131 if ( !isset( $this->mMagicExtensions ) ) {
1132 $this->mMagicExtensions = array();
1133 wfRunHooks( 'LanguageGetMagic', array( &$this->mMagicExtensions, $this->getCode() ) );
1134 }
1135 if ( isset( $this->mMagicExtensions[$mw->mId] ) ) {
1136 $rawEntry = $this->mMagicExtensions[$mw->mId];
1137 } else {
1138 $magicWords =& $this->getMagicWords();
1139 if ( isset( $magicWords[$mw->mId] ) ) {
1140 $rawEntry = $magicWords[$mw->mId];
1141 } else {
1142 # Fall back to English if local list is incomplete
1143 $magicWords =& Language::getMagicWords();
1144 $rawEntry = $magicWords[$mw->mId];
1145 }
1146 }
1147
1148 if( !is_array( $rawEntry ) ) {
1149 error_log( "\"$rawEntry\" is not a valid magic thingie for \"$mw->mId\"" );
1150 }
1151 $mw->mCaseSensitive = $rawEntry[0];
1152 $mw->mSynonyms = array_slice( $rawEntry, 1 );
1153 }
1154
1155 /**
1156 * Get special page names, as an associative array
1157 * case folded alias => real name
1158 */
1159 function getSpecialPageAliases() {
1160 $this->load();
1161 if ( !isset( $this->mExtendedSpecialPageAliases ) ) {
1162 $this->mExtendedSpecialPageAliases = $this->specialPageAliases;
1163 wfRunHooks( 'LangugeGetSpecialPageAliases',
1164 array( &$this->mExtendedSpecialPageAliases, $this->getCode() ) );
1165 }
1166 return $this->mExtendedSpecialPageAliases;
1167 }
1168
1169 /**
1170 * Italic is unsuitable for some languages
1171 *
1172 * @public
1173 *
1174 * @param string $text The text to be emphasized.
1175 * @return string
1176 */
1177 function emphasize( $text ) {
1178 return "<em>$text</em>";
1179 }
1180
1181 /**
1182 * Normally we output all numbers in plain en_US style, that is
1183 * 293,291.235 for twohundredninetythreethousand-twohundredninetyone
1184 * point twohundredthirtyfive. However this is not sutable for all
1185 * languages, some such as Pakaran want ੨੯੩,੨੯੫.੨੩੫ and others such as
1186 * Icelandic just want to use commas instead of dots, and dots instead
1187 * of commas like "293.291,235".
1188 *
1189 * An example of this function being called:
1190 * <code>
1191 * wfMsg( 'message', $wgLang->formatNum( $num ) )
1192 * </code>
1193 *
1194 * See LanguageGu.php for the Gujarati implementation and
1195 * LanguageIs.php for the , => . and . => , implementation.
1196 *
1197 * @todo check if it's viable to use localeconv() for the decimal
1198 * seperator thing.
1199 * @public
1200 * @param mixed $number the string to be formatted, should be an integer or
1201 * a floating point number.
1202 * @param bool $nocommafy Set to true for special numbers like dates
1203 * @return string
1204 */
1205 function formatNum( $number, $nocommafy = false ) {
1206 global $wgTranslateNumerals;
1207 if (!$nocommafy) {
1208 $number = $this->commafy($number);
1209 $s = $this->separatorTransformTable();
1210 if (!is_null($s)) { $number = strtr($number, $s); }
1211 }
1212
1213 if ($wgTranslateNumerals) {
1214 $s = $this->digitTransformTable();
1215 if (!is_null($s)) { $number = strtr($number, $s); }
1216 }
1217
1218 return $number;
1219 }
1220
1221 function parseFormattedNumber( $number ) {
1222 $s = $this->digitTransformTable();
1223 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1224
1225 $s = $this->separatorTransformTable();
1226 if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
1227
1228 $number = strtr( $number, array (',' => '') );
1229 return $number;
1230 }
1231
1232 /**
1233 * Adds commas to a given number
1234 *
1235 * @param mixed $_
1236 * @return string
1237 */
1238 function commafy($_) {
1239 return strrev((string)preg_replace('/(\d{3})(?=\d)(?!\d*\.)/','$1,',strrev($_)));
1240 }
1241
1242 function digitTransformTable() {
1243 $this->load();
1244 return $this->digitTransformTable;
1245 }
1246
1247 function separatorTransformTable() {
1248 $this->load();
1249 return $this->separatorTransformTable;
1250 }
1251
1252
1253 /**
1254 * For the credit list in includes/Credits.php (action=credits)
1255 *
1256 * @param array $l
1257 * @return string
1258 */
1259 function listToText( $l ) {
1260 $s = '';
1261 $m = count($l) - 1;
1262 for ($i = $m; $i >= 0; $i--) {
1263 if ($i == $m) {
1264 $s = $l[$i];
1265 } else if ($i == $m - 1) {
1266 $s = $l[$i] . ' ' . $this->getMessageFromDB( 'and' ) . ' ' . $s;
1267 } else {
1268 $s = $l[$i] . ', ' . $s;
1269 }
1270 }
1271 return $s;
1272 }
1273
1274 # Crop a string from the beginning or end to a certain number of bytes.
1275 # (Bytes are used because our storage has limited byte lengths for some
1276 # columns in the database.) Multibyte charsets will need to make sure that
1277 # only whole characters are included!
1278 #
1279 # $length does not include the optional ellipsis.
1280 # If $length is negative, snip from the beginning
1281 function truncate( $string, $length, $ellipsis = "" ) {
1282 if( $length == 0 ) {
1283 return $ellipsis;
1284 }
1285 if ( strlen( $string ) <= abs( $length ) ) {
1286 return $string;
1287 }
1288 if( $length > 0 ) {
1289 $string = substr( $string, 0, $length );
1290 $char = ord( $string[strlen( $string ) - 1] );
1291 $m = array();
1292 if ($char >= 0xc0) {
1293 # We got the first byte only of a multibyte char; remove it.
1294 $string = substr( $string, 0, -1 );
1295 } elseif( $char >= 0x80 &&
1296 preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
1297 '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
1298 # We chopped in the middle of a character; remove it
1299 $string = $m[1];
1300 }
1301 return $string . $ellipsis;
1302 } else {
1303 $string = substr( $string, $length );
1304 $char = ord( $string[0] );
1305 if( $char >= 0x80 && $char < 0xc0 ) {
1306 # We chopped in the middle of a character; remove the whole thing
1307 $string = preg_replace( '/^[\x80-\xbf]+/', '', $string );
1308 }
1309 return $ellipsis . $string;
1310 }
1311 }
1312
1313 /**
1314 * Grammatical transformations, needed for inflected languages
1315 * Invoked by putting {{grammar:case|word}} in a message
1316 *
1317 * @param string $word
1318 * @param string $case
1319 * @return string
1320 */
1321 function convertGrammar( $word, $case ) {
1322 global $wgGrammarForms;
1323 if ( isset($wgGrammarForms['en'][$case][$word]) ) {
1324 return $wgGrammarForms['en'][$case][$word];
1325 }
1326 return $word;
1327 }
1328
1329 /**
1330 * Plural form transformations, needed for some languages.
1331 * For example, where are 3 form of plural in Russian and Polish,
1332 * depending on "count mod 10". See [[w:Plural]]
1333 * For English it is pretty simple.
1334 *
1335 * Invoked by putting {{plural:count|wordform1|wordform2}}
1336 * or {{plural:count|wordform1|wordform2|wordform3}}
1337 *
1338 * Example: {{plural:{{NUMBEROFARTICLES}}|article|articles}}
1339 *
1340 * @param integer $count
1341 * @param string $wordform1
1342 * @param string $wordform2
1343 * @param string $wordform3 (optional)
1344 * @param string $wordform4 (optional)
1345 * @param string $wordform5 (optional)
1346 * @return string
1347 */
1348 function convertPlural( $count, $w1, $w2, $w3, $w4, $w5) {
1349 return ( $count == '1' || $count == '-1' ) ? $w1 : $w2;
1350 }
1351
1352 /**
1353 * For translaing of expiry times
1354 * @param string The validated block time in English
1355 * @param $forContent, avoid html?
1356 * @return Somehow translated block time
1357 * @see LanguageFi.php for example implementation
1358 */
1359 function translateBlockExpiry( $str, $forContent=false ) {
1360
1361 $scBlockExpiryOptions = $this->getMessageFromDB( 'ipboptions' );
1362
1363 if ( $scBlockExpiryOptions == '-') {
1364 return $str;
1365 }
1366
1367 foreach (explode(',', $scBlockExpiryOptions) as $option) {
1368 if ( strpos($option, ":") === false )
1369 continue;
1370 list($show, $value) = explode(":", $option);
1371 if ( strcmp ( $str, $value) == 0 ) {
1372 if ( $forContent )
1373 return htmlspecialchars($str) . htmlspecialchars( trim( $show ) );
1374 else
1375 return '<span title="' . htmlspecialchars($str). '">' . htmlspecialchars( trim( $show ) ) . '</span>';
1376 }
1377 }
1378
1379 return $str;
1380 }
1381
1382 /**
1383 * languages like Chinese need to be segmented in order for the diff
1384 * to be of any use
1385 *
1386 * @param string $text
1387 * @return string
1388 */
1389 function segmentForDiff( $text ) {
1390 return $text;
1391 }
1392
1393 /**
1394 * and unsegment to show the result
1395 *
1396 * @param string $text
1397 * @return string
1398 */
1399 function unsegmentForDiff( $text ) {
1400 return $text;
1401 }
1402
1403 # convert text to different variants of a language.
1404 function convert( $text, $isTitle = false) {
1405 return $this->mConverter->convert($text, $isTitle);
1406 }
1407
1408 # Convert text from within Parser
1409 function parserConvert( $text, &$parser ) {
1410 return $this->mConverter->parserConvert( $text, $parser );
1411 }
1412
1413 # Check if this is a language with variants
1414 function hasVariants(){
1415 return sizeof($this->getVariants())>1;
1416 }
1417
1418 # Put custom tags (e.g. -{ }-) around math to prevent conversion
1419 function armourMath($text){
1420 return $this->mConverter->armourMath($text);
1421 }
1422
1423
1424 /**
1425 * Perform output conversion on a string, and encode for safe HTML output.
1426 * @param string $text
1427 * @param bool $isTitle -- wtf?
1428 * @return string
1429 * @todo this should get integrated somewhere sane
1430 */
1431 function convertHtml( $text, $isTitle = false ) {
1432 return htmlspecialchars( $this->convert( $text, $isTitle ) );
1433 }
1434
1435 function convertCategoryKey( $key ) {
1436 return $this->mConverter->convertCategoryKey( $key );
1437 }
1438
1439 /**
1440 * get the list of variants supported by this langauge
1441 * see sample implementation in LanguageZh.php
1442 *
1443 * @return array an array of language codes
1444 */
1445 function getVariants() {
1446 return $this->mConverter->getVariants();
1447 }
1448
1449
1450 function getPreferredVariant( $fromUser = true ) {
1451 return $this->mConverter->getPreferredVariant( $fromUser );
1452 }
1453
1454 /**
1455 * if a language supports multiple variants, it is
1456 * possible that non-existing link in one variant
1457 * actually exists in another variant. this function
1458 * tries to find it. See e.g. LanguageZh.php
1459 *
1460 * @param string $link the name of the link
1461 * @param mixed $nt the title object of the link
1462 * @return null the input parameters may be modified upon return
1463 */
1464 function findVariantLink( &$link, &$nt ) {
1465 $this->mConverter->findVariantLink($link, $nt);
1466 }
1467
1468 /**
1469 * If a language supports multiple variants, converts text
1470 * into an array of all possible variants of the text:
1471 * 'variant' => text in that variant
1472 */
1473
1474 function convertLinkToAllVariants($text){
1475 return $this->mConverter->convertLinkToAllVariants($text);
1476 }
1477
1478
1479 /**
1480 * returns language specific options used by User::getPageRenderHash()
1481 * for example, the preferred language variant
1482 *
1483 * @return string
1484 * @public
1485 */
1486 function getExtraHashOptions() {
1487 return $this->mConverter->getExtraHashOptions();
1488 }
1489
1490 /**
1491 * for languages that support multiple variants, the title of an
1492 * article may be displayed differently in different variants. this
1493 * function returns the apporiate title defined in the body of the article.
1494 *
1495 * @return string
1496 */
1497 function getParsedTitle() {
1498 return $this->mConverter->getParsedTitle();
1499 }
1500
1501 /**
1502 * Enclose a string with the "no conversion" tag. This is used by
1503 * various functions in the Parser
1504 *
1505 * @param string $text text to be tagged for no conversion
1506 * @return string the tagged text
1507 */
1508 function markNoConversion( $text, $noParse=false ) {
1509 return $this->mConverter->markNoConversion( $text, $noParse );
1510 }
1511
1512 /**
1513 * A regular expression to match legal word-trailing characters
1514 * which should be merged onto a link of the form [[foo]]bar.
1515 *
1516 * @return string
1517 * @public
1518 */
1519 function linkTrail() {
1520 $this->load();
1521 return $this->linkTrail;
1522 }
1523
1524 function getLangObj() {
1525 return $this;
1526 }
1527
1528 /**
1529 * Get the RFC 3066 code for this language object
1530 */
1531 function getCode() {
1532 return $this->mCode;
1533 }
1534
1535 function setCode( $code ) {
1536 $this->mCode = $code;
1537 }
1538
1539 static function getFileName( $prefix = 'Language', $code, $suffix = '.php' ) {
1540 return $prefix . str_replace( '-', '_', ucfirst( $code ) ) . $suffix;
1541 }
1542
1543 static function getMessagesFileName( $code ) {
1544 global $IP;
1545 return self::getFileName( "$IP/languages/messages/Messages", $code, '.php' );
1546 }
1547
1548 static function getClassFileName( $code ) {
1549 global $IP;
1550 return self::getFileName( "$IP/languages/classes/Language", $code, '.php' );
1551 }
1552
1553 static function getLocalisationArray( $code, $disableCache = false ) {
1554 self::loadLocalisation( $code, $disableCache );
1555 return self::$mLocalisationCache[$code];
1556 }
1557
1558 /**
1559 * Load localisation data for a given code into the static cache
1560 *
1561 * @return array Dependencies, map of filenames to mtimes
1562 */
1563 static function loadLocalisation( $code, $disableCache = false ) {
1564 static $recursionGuard = array();
1565 global $wgMemc;
1566
1567 if ( !$code ) {
1568 throw new MWException( "Invalid language code requested" );
1569 }
1570
1571 if ( !$disableCache ) {
1572 # Try the per-process cache
1573 if ( isset( self::$mLocalisationCache[$code] ) ) {
1574 return self::$mLocalisationCache[$code]['deps'];
1575 }
1576
1577 wfProfileIn( __METHOD__ );
1578
1579 # Try the serialized directory
1580 $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) );
1581 if ( $cache ) {
1582 self::$mLocalisationCache[$code] = $cache;
1583 wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
1584 wfProfileOut( __METHOD__ );
1585 return self::$mLocalisationCache[$code]['deps'];
1586 }
1587
1588 # Try the global cache
1589 $memcKey = wfMemcKey('localisation', $code );
1590 $cache = $wgMemc->get( $memcKey );
1591 if ( $cache ) {
1592 # Check file modification times
1593 foreach ( $cache['deps'] as $file => $mtime ) {
1594 if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
1595 break;
1596 }
1597 }
1598 if ( self::isLocalisationOutOfDate( $cache ) ) {
1599 $wgMemc->delete( $memcKey );
1600 $cache = false;
1601 wfDebug( "Language::loadLocalisation(): localisation cache for $code had expired due to update of $file\n" );
1602 } else {
1603 self::$mLocalisationCache[$code] = $cache;
1604 wfDebug( "Language::loadLocalisation(): got localisation for $code from cache\n" );
1605 wfProfileOut( __METHOD__ );
1606 return $cache['deps'];
1607 }
1608 }
1609 } else {
1610 wfProfileIn( __METHOD__ );
1611 }
1612
1613 # Default fallback, may be overridden when the messages file is included
1614 if ( $code != 'en' ) {
1615 $fallback = 'en';
1616 } else {
1617 $fallback = false;
1618 }
1619
1620 # Load the primary localisation from the source file
1621 $filename = self::getMessagesFileName( $code );
1622 if ( !file_exists( $filename ) ) {
1623 wfDebug( "Language::loadLocalisation(): no localisation file for $code, using implicit fallback to en\n" );
1624 $cache = array();
1625 $deps = array();
1626 } else {
1627 $deps = array( $filename => filemtime( $filename ) );
1628 require( $filename );
1629 $cache = compact( self::$mLocalisationKeys );
1630 wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
1631 }
1632
1633 if ( !empty( $fallback ) ) {
1634 # Load the fallback localisation, with a circular reference guard
1635 if ( isset( $recursionGuard[$code] ) ) {
1636 throw new MWException( "Error: Circular fallback reference in language code $code" );
1637 }
1638 $recursionGuard[$code] = true;
1639 $newDeps = self::loadLocalisation( $fallback, $disableCache );
1640 unset( $recursionGuard[$code] );
1641
1642 $secondary = self::$mLocalisationCache[$fallback];
1643 $deps = array_merge( $deps, $newDeps );
1644
1645 # Merge the fallback localisation with the current localisation
1646 foreach ( self::$mLocalisationKeys as $key ) {
1647 if ( isset( $cache[$key] ) ) {
1648 if ( isset( $secondary[$key] ) ) {
1649 if ( in_array( $key, self::$mMergeableMapKeys ) ) {
1650 $cache[$key] = $cache[$key] + $secondary[$key];
1651 } elseif ( in_array( $key, self::$mMergeableListKeys ) ) {
1652 $cache[$key] = array_merge( $secondary[$key], $cache[$key] );
1653 } elseif ( in_array( $key, self::$mMergeableAliasListKeys ) ) {
1654 $cache[$key] = array_merge_recursive( $cache[$key], $secondary[$key] );
1655 }
1656 }
1657 } else {
1658 $cache[$key] = $secondary[$key];
1659 }
1660 }
1661
1662 # Merge bookstore lists if requested
1663 if ( !empty( $cache['bookstoreList']['inherit'] ) ) {
1664 $cache['bookstoreList'] = array_merge( $cache['bookstoreList'], $secondary['bookstoreList'] );
1665 }
1666 if ( isset( $cache['bookstoreList']['inherit'] ) ) {
1667 unset( $cache['bookstoreList']['inherit'] );
1668 }
1669 }
1670
1671 # Add dependencies to the cache entry
1672 $cache['deps'] = $deps;
1673
1674 # Replace spaces with underscores in namespace names
1675 $cache['namespaceNames'] = str_replace( ' ', '_', $cache['namespaceNames'] );
1676
1677 # Save to both caches
1678 self::$mLocalisationCache[$code] = $cache;
1679 if ( !$disableCache ) {
1680 $wgMemc->set( $memcKey, $cache );
1681 }
1682
1683 wfProfileOut( __METHOD__ );
1684 return $deps;
1685 }
1686
1687 /**
1688 * Test if a given localisation cache is out of date with respect to the
1689 * source Messages files. This is done automatically for the global cache
1690 * in $wgMemc, but is only done on certain occasions for the serialized
1691 * data file.
1692 *
1693 * @param $cache mixed Either a language code or a cache array
1694 */
1695 static function isLocalisationOutOfDate( $cache ) {
1696 if ( !is_array( $cache ) ) {
1697 self::loadLocalisation( $cache );
1698 $cache = self::$mLocalisationCache[$cache];
1699 }
1700 $expired = false;
1701 foreach ( $cache['deps'] as $file => $mtime ) {
1702 if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
1703 $expired = true;
1704 break;
1705 }
1706 }
1707 return $expired;
1708 }
1709
1710 /**
1711 * Get the fallback for a given language
1712 */
1713 static function getFallbackFor( $code ) {
1714 self::loadLocalisation( $code );
1715 return self::$mLocalisationCache[$code]['fallback'];
1716 }
1717
1718 /**
1719 * Get all messages for a given language
1720 */
1721 static function getMessagesFor( $code ) {
1722 self::loadLocalisation( $code );
1723 return self::$mLocalisationCache[$code]['messages'];
1724 }
1725
1726 /**
1727 * Get a message for a given language
1728 */
1729 static function getMessageFor( $key, $code ) {
1730 self::loadLocalisation( $code );
1731 return isset( self::$mLocalisationCache[$code]['messages'][$key] ) ? self::$mLocalisationCache[$code]['messages'][$key] : null;
1732 }
1733
1734 /**
1735 * Load localisation data for this object
1736 */
1737 function load() {
1738 if ( !$this->mLoaded ) {
1739 self::loadLocalisation( $this->getCode() );
1740 $cache =& self::$mLocalisationCache[$this->getCode()];
1741 foreach ( self::$mLocalisationKeys as $key ) {
1742 $this->$key = $cache[$key];
1743 }
1744 $this->mLoaded = true;
1745
1746 $this->fixUpSettings();
1747 }
1748 }
1749
1750 /**
1751 * Do any necessary post-cache-load settings adjustment
1752 */
1753 function fixUpSettings() {
1754 global $wgExtraNamespaces, $wgMetaNamespace, $wgMetaNamespaceTalk,
1755 $wgNamespaceAliases, $wgAmericanDates;
1756 wfProfileIn( __METHOD__ );
1757 if ( $wgExtraNamespaces ) {
1758 $this->namespaceNames = $wgExtraNamespaces + $this->namespaceNames;
1759 }
1760
1761 $this->namespaceNames[NS_PROJECT] = $wgMetaNamespace;
1762 if ( $wgMetaNamespaceTalk ) {
1763 $this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
1764 } else {
1765 $talk = $this->namespaceNames[NS_PROJECT_TALK];
1766 $talk = str_replace( '$1', $wgMetaNamespace, $talk );
1767
1768 # Allow grammar transformations
1769 # Allowing full message-style parsing would make simple requests
1770 # such as action=raw much more expensive than they need to be.
1771 # This will hopefully cover most cases.
1772 $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
1773 array( &$this, 'replaceGrammarInNamespace' ), $talk );
1774 $talk = str_replace( ' ', '_', $talk );
1775 $this->namespaceNames[NS_PROJECT_TALK] = $talk;
1776 }
1777
1778 # The above mixing may leave namespaces out of canonical order.
1779 # Re-order by namespace ID number...
1780 ksort( $this->namespaceNames );
1781
1782 # Put namespace names and aliases into a hashtable.
1783 # If this is too slow, then we should arrange it so that it is done
1784 # before caching. The catch is that at pre-cache time, the above
1785 # class-specific fixup hasn't been done.
1786 $this->mNamespaceIds = array();
1787 foreach ( $this->namespaceNames as $index => $name ) {
1788 $this->mNamespaceIds[$this->lc($name)] = $index;
1789 }
1790 if ( $this->namespaceAliases ) {
1791 foreach ( $this->namespaceAliases as $name => $index ) {
1792 $this->mNamespaceIds[$this->lc($name)] = $index;
1793 }
1794 }
1795 if ( $wgNamespaceAliases ) {
1796 foreach ( $wgNamespaceAliases as $name => $index ) {
1797 $this->mNamespaceIds[$this->lc($name)] = $index;
1798 }
1799 }
1800
1801 if ( $this->defaultDateFormat == 'dmy or mdy' ) {
1802 $this->defaultDateFormat = $wgAmericanDates ? 'mdy' : 'dmy';
1803 }
1804 wfProfileOut( __METHOD__ );
1805 }
1806
1807 function replaceGrammarInNamespace( $m ) {
1808 return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
1809 }
1810
1811 static function getCaseMaps() {
1812 static $wikiUpperChars, $wikiLowerChars;
1813 if ( isset( $wikiUpperChars ) ) {
1814 return array( $wikiUpperChars, $wikiLowerChars );
1815 }
1816
1817 wfProfileIn( __METHOD__ );
1818 $arr = wfGetPrecompiledData( 'Utf8Case.ser' );
1819 if ( $arr === false ) {
1820 throw new MWException(
1821 "Utf8Case.ser is missing, please run \"make\" in the serialized directory\n" );
1822 }
1823 extract( $arr );
1824 wfProfileOut( __METHOD__ );
1825 return array( $wikiUpperChars, $wikiLowerChars );
1826 }
1827
1828 function formatTimePeriod( $seconds ) {
1829 if ( $seconds < 10 ) {
1830 return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
1831 } elseif ( $seconds < 60 ) {
1832 return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
1833 } elseif ( $seconds < 3600 ) {
1834 return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
1835 $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
1836 } else {
1837 $hours = floor( $seconds / 3600 );
1838 $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
1839 $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
1840 return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
1841 $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
1842 $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
1843 }
1844 }
1845
1846 function formatBitrate( $bps ) {
1847 $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
1848 if ( $bps <= 0 ) {
1849 return $this->formatNum( $bps ) . $units[0];
1850 }
1851 $unitIndex = floor( log10( $bps ) / 3 );
1852 $mantissa = $bps / pow( 1000, $unitIndex );
1853 if ( $mantissa < 10 ) {
1854 $mantissa = round( $mantissa, 1 );
1855 } else {
1856 $mantissa = round( $mantissa );
1857 }
1858 return $this->formatNum( $mantissa ) . $units[$unitIndex];
1859 }
1860
1861 /**
1862 * Format a size in bytes for output, using an appropriate
1863 * unit (B, KB, MB or GB) according to the magnitude in question
1864 *
1865 * @param $size Size to format
1866 * @return string Plain text (not HTML)
1867 */
1868 function formatSize( $size ) {
1869 // For small sizes no decimal places necessary
1870 $round = 0;
1871 if( $size > 1024 ) {
1872 $size = $size / 1024;
1873 if( $size > 1024 ) {
1874 $size = $size / 1024;
1875 // For MB and bigger two decimal places are smarter
1876 $round = 2;
1877 if( $size > 1024 ) {
1878 $size = $size / 1024;
1879 $msg = 'size-gigabytes';
1880 } else {
1881 $msg = 'size-megabytes';
1882 }
1883 } else {
1884 $msg = 'size-kilobytes';
1885 }
1886 } else {
1887 $msg = 'size-bytes';
1888 }
1889 $size = round( $size, $round );
1890 $text = $this->getMessageFromDB( $msg );
1891 return str_replace( '$1', $this->formatNum( $size ), $text );
1892 }
1893 }
1894
1895
1896