function parserConvert($t, $p) {return $t;}
function getVariants() { return array( $this->mLang->getCode() ); }
function getPreferredVariant() {return $this->mLang->getCode(); }
- function findVariantLink(&$l, &$n, $forTemplate = false) {}
+ function findVariantLink(&$l, &$n, $ignoreOtherCond = false) {}
function getExtraHashOptions() {return '';}
function getParsedTitle() {return '';}
function markNoConversion($text, $noParse=false) {return $text;}
function convertCategoryKey( $key ) {return $key; }
function convertLinkToAllVariants($text){ return array( $this->mLang->getCode() => $text); }
function armourMath($text){ return $text; }
+ function groupConvert($group) {return '';}
}
/**
var $mConverter, $mVariants, $mCode, $mLoaded = false;
var $mMagicExtensions = array(), $mMagicHookDone = false;
- static public $mLocalisationKeys = array( 'fallback', 'namespaceNames',
- 'skinNames', 'mathNames',
- 'bookstoreList', 'magicWords', 'messages', 'rtl', 'digitTransformTable',
+ static public $mLocalisationKeys = array(
+ 'fallback', 'namespaceNames', 'mathNames', 'bookstoreList',
+ 'magicWords', 'messages', 'rtl', 'digitTransformTable',
'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
*/
static function factory( $code ) {
if ( !isset( self::$mLangObjCache[$code] ) ) {
+ if( count( self::$mLangObjCache ) > 10 ) {
+ // Don't keep a billion objects around, that's stupid.
+ self::$mLangObjCache = array();
+ }
self::$mLangObjCache[$code] = self::newFromCode( $code );
}
return self::$mLangObjCache[$code];
if ( isset( $aliases[$name][0] ) ) {
$name = $aliases[$name][0];
}
- return $this->getNsText(NS_SPECIAL) . ':' . $name;
+ return $this->getNsText( NS_SPECIAL ) . ':' . $name;
}
function getQuickbarSettings() {
);
}
- function getSkinNames() {
- $this->load();
- return $this->skinNames;
- }
-
function getMathNames() {
$this->load();
return $this->mathNames;
function userAdjust( $ts, $tz = false ) {
global $wgUser, $wgLocalTZoffset;
- if (!$tz) {
+ if ( $tz === false ) {
$tz = $wgUser->getOption( 'timecorrection' );
}
- # minutes and hours differences:
- $minDiff = 0;
- $hrDiff = 0;
+ $data = explode( '|', $tz, 3 );
- if ( $tz === '' ) {
- # Global offset in minutes.
- if( isset($wgLocalTZoffset) ) {
- if( $wgLocalTZoffset >= 0 ) {
- $hrDiff = floor($wgLocalTZoffset / 60);
- } else {
- $hrDiff = ceil($wgLocalTZoffset / 60);
- }
- $minDiff = $wgLocalTZoffset % 60;
+ if ( $data[0] == 'ZoneInfo' ) {
+ if ( function_exists( 'timezone_open' ) && @timezone_open( $data[2] ) !== false ) {
+ $date = date_create( $ts, timezone_open( 'UTC' ) );
+ date_timezone_set( $date, timezone_open( $data[2] ) );
+ $date = date_format( $date, 'YmdHis' );
+ return $date;
}
- } elseif ( strpos( $tz, ':' ) !== false ) {
- $tzArray = explode( ':', $tz );
- $hrDiff = intval($tzArray[0]);
- $minDiff = intval($hrDiff < 0 ? -$tzArray[1] : $tzArray[1]);
+ # Unrecognized timezone, default to 'Offset' with the stored offset.
+ $data[0] = 'Offset';
+ }
+
+ $minDiff = 0;
+ if ( $data[0] == 'System' || $tz == '' ) {
+ # Global offset in minutes.
+ if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset;
+ } else if ( $data[0] == 'Offset' ) {
+ $minDiff = intval( $data[1] );
} else {
- $hrDiff = intval( $tz );
+ $data = explode( ':', $tz );
+ if( count( $data ) == 2 ) {
+ $data[0] = intval( $data[0] );
+ $data[1] = intval( $data[1] );
+ $minDiff = abs( $data[0] ) * 60 + $data[1];
+ if ( $data[0] < 0 ) $minDiff = -$minDiff;
+ } else {
+ $minDiff = intval( $data[0] ) * 60;
+ }
}
# No difference ? Return time unchanged
- if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
+ if ( 0 == $minDiff ) return $ts;
wfSuppressWarnings(); // E_STRICT system time bitching
- # Generate an adjusted date
+ # Generate an adjusted date; take advantage of the fact that mktime
+ # will normalize out-of-range values so we don't have to split $minDiff
+ # into hours and minutes.
$t = mktime( (
- (int)substr( $ts, 8, 2) ) + $hrDiff, # Hours
+ (int)substr( $ts, 8, 2) ), # Hours
(int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
(int)substr( $ts, 12, 2 ), # Seconds
(int)substr( $ts, 4, 2 ), # Month
* internationalisation, a reduced set of format characters, and a better
* escaping format.
*
- * Supported format characters are dDjlNwzWFmMntLYyaAgGhHiscrU. See the
- * PHP manual for definitions. There are a number of extensions, which
- * start with "x":
+ * Supported format characters are dDjlNwzWFmMntLoYyaAgGhHiscrU. See the
+ * PHP manual for definitions. "o" format character is supported since
+ * PHP 5.1.0, previous versions return literal o.
+ * There are a number of extensions, which start with "x":
*
* xn Do not translate digits of the next numeric format character
* xN Toggle raw digit (xn) flag, stays set until explicitly unset
* xjn n (month number) in Hebrew calendar
* xjY Y (full year) in Hebrew calendar
*
- * xmj j (day number) in Hijri calendar
- * xmF F (month name) in Hijri calendar
- * xmn n (month number) in Hijri calendar
- * xmY Y (full year) in Hijri calendar
+ * xmj j (day number) in Hijri calendar
+ * xmF F (month name) in Hijri calendar
+ * xmn n (month number) in Hijri calendar
+ * xmY Y (full year) in Hijri calendar
*
* xkY Y (full year) in Thai solar calendar. Months and days are
* identical to the Gregorian calendar
+ * xoY Y (full year) in Minguo calendar or Juche year.
+ * Months and days are identical to the
+ * Gregorian calendar
+ * xtY Y (full year) in Japanese nengo. Months and days are
+ * identical to the Gregorian calendar
*
* Characters enclosed in double quotes will be considered literal (with
* the quotes themselves removed). Unmatched quotes will be considered
* @param $ts String: 14-character timestamp
* YYYYMMDDHHMMSS
* 01234567890123
+ * @todo emulation of "o" format character for PHP pre 5.1.0
+ * @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
*/
function sprintfDate( $format, $ts ) {
$s = '';
$hebrew = false;
$hijri = false;
$thai = false;
+ $minguo = false;
+ $tenno = false;
for ( $p = 0; $p < strlen( $format ); $p++ ) {
$num = false;
$code = $format[$p];
$code .= $format[++$p];
}
- if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' ) && $p < strlen( $format ) - 1 ) {
+ if ( ( $code === 'xi' || $code == 'xj' || $code == 'xk' || $code == 'xm' || $code == 'xo' || $code == 'xt' ) && $p < strlen( $format ) - 1 ) {
$code .= $format[++$p];
}
if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
$num = gmdate( 'L', $unix );
break;
+ # 'o' is supported since PHP 5.1.0
+ # return literal if not supported
+ # TODO: emulation for pre 5.1.0 versions
+ case 'o':
+ if ( !$unix ) $unix = wfTimestamp( TS_UNIX, $ts );
+ if ( version_compare(PHP_VERSION, '5.1.0') === 1 )
+ $num = date( 'o', $unix );
+ else
+ $s .= 'o';
+ break;
case 'Y':
$num = substr( $ts, 0, 4 );
break;
$num = $hebrew[0];
break;
case 'xkY':
- if ( !$thai ) $thai = self::tsToThai( $ts );
+ if ( !$thai ) $thai = self::tsToYear( $ts, 'thai' );
$num = $thai[0];
break;
+ case 'xoY':
+ if ( !$minguo ) $minguo = self::tsToYear( $ts, 'minguo' );
+ $num = $minguo[0];
+ break;
+ case 'xtY':
+ if ( !$tenno ) $tenno = self::tsToYear( $ts, 'tenno' );
+ $num = $tenno[0];
+ break;
case 'y':
$num = substr( $ts, 2, 2 );
break;
}
/**
- * Algorithm to convert Gregorian dates to Thai solar dates.
+ * Algorithm to convert Gregorian dates to Thai solar dates,
+ * Minguo dates or Minguo dates.
*
* Link: http://en.wikipedia.org/wiki/Thai_solar_calendar
+ * http://en.wikipedia.org/wiki/Minguo_calendar
+ * http://en.wikipedia.org/wiki/Japanese_era_name
*
- * @param $ts String: 14-character timestamp
+ * @param $ts String: 14-character timestamp, calender name
* @return array converted year, month, day
*/
- private static function tsToThai( $ts ) {
+ private static function tsToYear( $ts, $cName ) {
$gy = substr( $ts, 0, 4 );
$gm = substr( $ts, 4, 2 );
$gd = substr( $ts, 6, 2 );
- # Add 543 years to the Gregorian calendar
- # Months and days are identical
- $gy_thai = $gy + 543;
+ if (!strcmp($cName,'thai')) {
+ # Thai solar dates
+ # Add 543 years to the Gregorian calendar
+ # Months and days are identical
+ $gy_offset = $gy + 543;
+ } else if ((!strcmp($cName,'minguo')) || !strcmp($cName,'juche')) {
+ # Minguo dates
+ # Deduct 1911 years from the Gregorian calendar
+ # Months and days are identical
+ $gy_offset = $gy - 1911;
+ } else if (!strcmp($cName,'tenno')) {
+ # Nengō dates up to Meiji period
+ # Deduct years from the Gregorian calendar
+ # depending on the nengo periods
+ # Months and days are identical
+ if (($gy < 1912) || (($gy == 1912) && ($gm < 7)) || (($gy == 1912) && ($gm == 7) && ($gd < 31))) {
+ # Meiji period
+ $gy_gannen = $gy - 1868 + 1;
+ $gy_offset = $gy_gannen;
+ if ($gy_gannen == 1)
+ $gy_offset = '元';
+ $gy_offset = '明治'.$gy_offset;
+ } 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))) {
+ # Taishō period
+ $gy_gannen = $gy - 1912 + 1;
+ $gy_offset = $gy_gannen;
+ if ($gy_gannen == 1)
+ $gy_offset = '元';
+ $gy_offset = '大正'.$gy_offset;
+ } else if ((($gy == 1926) && ($gm == 12) && ($gd >= 26)) || (($gy > 1926) && ($gy < 1989)) || (($gy == 1989) && ($gm == 1) && ($gd < 8))) {
+ # Shōwa period
+ $gy_gannen = $gy - 1926 + 1;
+ $gy_offset = $gy_gannen;
+ if ($gy_gannen == 1)
+ $gy_offset = '元';
+ $gy_offset = '昭和'.$gy_offset;
+ } else {
+ # Heisei period
+ $gy_gannen = $gy - 1989 + 1;
+ $gy_offset = $gy_gannen;
+ if ($gy_gannen == 1)
+ $gy_offset = '元';
+ $gy_offset = '平成'.$gy_offset;
+ }
+ } else {
+ $gy_offset = $gy;
+ }
- return array( $gy_thai, $gm, $gd );
+ return array( $gy_offset, $gm, $gd );
}
-
/**
* Roman number formatting up to 3000
*/
$n = $minLength-1;
$out = preg_replace(
"/\b(\w{1,$n})\b/",
- "$1U800",
+ "$1u800",
$out );
}
+ // Periods within things like hostnames and IP addresses
+ // are also important -- we want a search for "example.com"
+ // or "192.168.1.1" to work sanely.
+ //
+ // MySQL's search seems to ignore them, so you'd match on
+ // "example.wikipedia.com" and "192.168.83.1" as well.
+ $out = preg_replace(
+ "/(\w)\.(\w|\*)/u",
+ "$1u82e$2",
+ $out );
+
wfProfileOut( __METHOD__ );
return $out;
}
* settings or anything else of the sort.
*/
protected function stripForSearchCallback( $matches ) {
- return 'U8' . bin2hex( $matches[1] );
+ return 'u8' . bin2hex( $matches[1] );
}
/**
if (!$nocommafy) {
$number = $this->commafy($number);
$s = $this->separatorTransformTable();
- if (!is_null($s)) { $number = strtr($number, $s); }
+ if ($s) { $number = strtr($number, $s); }
}
if ($wgTranslateNumerals) {
$s = $this->digitTransformTable();
- if (!is_null($s)) { $number = strtr($number, $s); }
+ if ($s) { $number = strtr($number, $s); }
}
return $number;
function parseFormattedNumber( $number ) {
$s = $this->digitTransformTable();
- if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
+ if ($s) { $number = strtr($number, array_flip($s)); }
$s = $this->separatorTransformTable();
- if (!is_null($s)) { $number = strtr($number, array_flip($s)); }
+ if ($s) { $number = strtr($number, array_flip($s)); }
$number = strtr( $number, array (',' => '') );
return $number;
/**
- * For the credit list in includes/Credits.php (action=credits)
+ * Take a list of strings and build a locale-friendly comma-separated
+ * list, using the local comma-separator message.
+ * The last two strings are chained with an "and".
*
* @param $l Array
* @return string
*/
function listToText( $l ) {
$s = '';
- $m = count($l) - 1;
- for ($i = $m; $i >= 0; $i--) {
- if ($i == $m) {
- $s = $l[$i];
- } else if ($i == $m - 1) {
- $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
- } else {
- $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
+ $m = count( $l ) - 1;
+ if( $m == 1 ) {
+ return $l[0] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $l[1];
+ }
+ else {
+ for ( $i = $m; $i >= 0; $i-- ) {
+ if ( $i == $m ) {
+ $s = $l[$i];
+ } else if( $i == $m - 1 ) {
+ $s = $l[$i] . $this->getMessageFromDB( 'and' ) . $this->getMessageFromDB( 'word-separator' ) . $s;
+ } else {
+ $s = $l[$i] . $this->getMessageFromDB( 'comma-separator' ) . $s;
+ }
}
+ return $s;
}
- return $s;
}
-
+
/**
* Take a list of strings and build a locale-friendly comma-separated
* list, using the local comma-separator message.
* @param $list array of strings to put in a comma list
* @return string
*/
- function commaList( $list, $forContent = false ) {
+ function commaList( $list ) {
return implode(
$list,
- wfMsgExt( 'comma-separator', array( 'escapenoentities', 'language' => $this ) ) );
+ wfMsgExt( 'comma-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
}
-
+
+ /**
+ * Take a list of strings and build a locale-friendly semicolon-separated
+ * list, using the local semicolon-separator message.
+ * @param $list array of strings to put in a semicolon list
+ * @return string
+ */
+ function semicolonList( $list ) {
+ return implode(
+ $list,
+ wfMsgExt( 'semicolon-separator', array( 'parsemag', 'escapenoentities', 'language' => $this ) ) );
+ }
+
/**
* Same as commaList, but separate it with the pipe instead.
* @param $list array of strings to put in a pipe list
* @param $ellipsis String to append to the truncated text
* @return string
*/
- function truncate( $string, $length, $ellipsis = "" ) {
+ function truncate( $string, $length, $ellipsis = '...' ) {
+ # Use the localized ellipsis character
+ if( $ellipsis == '...' ) {
+ $ellipsis = wfMsgExt( 'ellipsis', array( 'escapenoentities', 'language' => $this ) );
+ }
+
if( $length == 0 ) {
return $ellipsis;
}
} elseif( $char >= 0x80 &&
preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
'[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) ) {
- # We chopped in the middle of a character; remove it
+ # We chopped in the middle of a character; remove it
$string = $m[1];
}
return $string . $ellipsis;
return $word;
}
+ /**
+ * Provides an alternative text depending on specified gender.
+ * Usage {{gender:username|masculine|feminine|neutral}}.
+ * username is optional, in which case the gender of current user is used,
+ * but only in (some) interface messages; otherwise default gender is used.
+ * If second or third parameter are not specified, masculine is used.
+ * These details may be overriden per language.
+ */
+ function gender( $gender, $forms ) {
+ if ( !count($forms) ) { return ''; }
+ $forms = $this->preConvertPlural( $forms, 2 );
+ if ( $gender === 'male' ) return $forms[0];
+ if ( $gender === 'female' ) return $forms[1];
+ return isset($forms[2]) ? $forms[2] : $forms[0];
+ }
+
/**
* Plural form transformations, needed for some languages.
* For example, there are 3 form of plural in Russian and Polish,
*
* @param $link String: the name of the link
* @param $nt Mixed: the title object of the link
+ * @param boolean $ignoreOtherCond: to disable other conditions when
+ * we need to transclude a template or update a category's link
* @return null the input parameters may be modified upon return
*/
- function findVariantLink( &$link, &$nt, $forTemplate = false ) {
- $this->mConverter->findVariantLink($link, $nt, $forTemplate );
+ function findVariantLink( &$link, &$nt, $ignoreOtherCond = false ) {
+ $this->mConverter->findVariantLink( $link, $nt, $ignoreOtherCond );
}
/**
* into an array of all possible variants of the text:
* 'variant' => text in that variant
*/
-
function convertLinkToAllVariants($text){
return $this->mConverter->convertLinkToAllVariants($text);
}
function markNoConversion( $text, $noParse=false ) {
return $this->mConverter->markNoConversion( $text, $noParse );
}
+
+ /**
+ * Callback function for magicword 'groupconvert'
+ *
+ * @param string $group: the group name called for
+ * @return blank string
+ */
+ function groupConvert( $group ) {
+ return $this->mConverter->groupConvert( $group );
+ }
/**
* A regular expression to match legal word-trailing characters
*/
static function loadLocalisation( $code, $disableCache = false ) {
static $recursionGuard = array();
- global $wgMemc, $wgCheckSerialized;
+ global $wgMemc, $wgEnableSerializedMessages, $wgCheckSerialized;
if ( !$code ) {
throw new MWException( "Invalid language code requested" );
wfProfileIn( __METHOD__ );
# Try the serialized directory
- $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) );
- if ( $cache ) {
- if ( $wgCheckSerialized && self::isLocalisationOutOfDate( $cache ) ) {
- $cache = false;
- wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" );
- } else {
- self::$mLocalisationCache[$code] = $cache;
- wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
- wfProfileOut( __METHOD__ );
- return self::$mLocalisationCache[$code]['deps'];
+ if( $wgEnableSerializedMessages ) {
+ $cache = wfGetPrecompiledData( self::getFileName( "Messages", $code, '.ser' ) );
+ if ( $cache ) {
+ if ( $wgCheckSerialized && self::isLocalisationOutOfDate( $cache ) ) {
+ $cache = false;
+ wfDebug( "Language::loadLocalisation(): precompiled data file for $code is out of date\n" );
+ } else {
+ self::$mLocalisationCache[$code] = $cache;
+ wfDebug( "Language::loadLocalisation(): got localisation for $code from precompiled data file\n" );
+ wfProfileOut( __METHOD__ );
+ return self::$mLocalisationCache[$code]['deps'];
+ }
}
+ } else {
+ $cache = false;
}
# Try the global cache
$cache = compact( self::$mLocalisationKeys );
wfDebug( "Language::loadLocalisation(): got localisation for $code from source\n" );
}
+
+ # Load magic word source file
+ global $IP;
+ $filename = "$IP/includes/MagicWord.php";
+ $newDeps = array( $filename => filemtime( $filename ) );
+ $deps = array_merge( $deps, $newDeps );
if ( !empty( $fallback ) ) {
# Load the fallback localisation, with a circular reference guard
self::loadLocalisation( $cache );
$cache = self::$mLocalisationCache[$cache];
}
+ // At least one language file and the MagicWord file needed
+ if( count($cache['deps']) < 2 ) {
+ return true;
+ }
$expired = false;
foreach ( $cache['deps'] as $file => $mtime ) {
if ( !file_exists( $file ) || filemtime( $file ) > $mtime ) {
$this->namespaceNames[NS_PROJECT_TALK] = $wgMetaNamespaceTalk;
} else {
$talk = $this->namespaceNames[NS_PROJECT_TALK];
- $talk = str_replace( '$1', $wgMetaNamespace, $talk );
-
- # Allow grammar transformations
- # Allowing full message-style parsing would make simple requests
- # such as action=raw much more expensive than they need to be.
- # This will hopefully cover most cases.
- $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
- array( &$this, 'replaceGrammarInNamespace' ), $talk );
- $talk = str_replace( ' ', '_', $talk );
- $this->namespaceNames[NS_PROJECT_TALK] = $talk;
+ $this->namespaceNames[NS_PROJECT_TALK] =
+ $this->fixVariableInNamespace( $talk );
}
# The above mixing may leave namespaces out of canonical order.
}
if ( $this->namespaceAliases ) {
foreach ( $this->namespaceAliases as $name => $index ) {
+ if ( $index === NS_PROJECT_TALK ) {
+ unset( $this->namespaceAliases[$name] );
+ $name = $this->fixVariableInNamespace( $name );
+ $this->namespaceAliases[$name] = $index;
+ }
$this->mNamespaceIds[$this->lc($name)] = $index;
}
}
wfProfileOut( __METHOD__ );
}
+ function fixVariableInNamespace( $talk ) {
+ if ( strpos( $talk, '$1' ) === false ) return $talk;
+
+ global $wgMetaNamespace;
+ $talk = str_replace( '$1', $wgMetaNamespace, $talk );
+
+ # Allow grammar transformations
+ # Allowing full message-style parsing would make simple requests
+ # such as action=raw much more expensive than they need to be.
+ # This will hopefully cover most cases.
+ $talk = preg_replace_callback( '/{{grammar:(.*?)\|(.*?)}}/i',
+ array( &$this, 'replaceGrammarInNamespace' ), $talk );
+ return str_replace( ' ', '_', $talk );
+ }
+
function replaceGrammarInNamespace( $m ) {
return $this->convertGrammar( trim( $m[2] ), trim( $m[1] ) );
}