# Read language names
global $wgLanguageNames;
-require_once( __DIR__ . '/Names.php' );
+require_once __DIR__ . '/Names.php';
if ( function_exists( 'mb_strtoupper' ) ) {
mb_internal_encoding( 'UTF-8' );
$alpha = '[a-z]';
$digit = '[0-9]';
$alphanum = '[a-z0-9]';
- $x = 'x' ; # private use singleton
+ $x = 'x'; # private use singleton
$singleton = '[a-wy-z]'; # other singleton
$s = $lenient ? '[-_]' : '-';
* @return bool
*/
public static function isValidCode( $code ) {
- return
- // People think language codes are html safe, so enforce it.
- // Ideally we should only allow a-zA-Z0-9-
- // but, .+ and other chars are often used for {{int:}} hacks
- // see bugs 37564, 37587, 36938
+ static $cache = array();
+ if ( isset( $cache[$code] ) ) {
+ return $cache[$code];
+ }
+ // People think language codes are html safe, so enforce it.
+ // Ideally we should only allow a-zA-Z0-9-
+ // but, .+ and other chars are often used for {{int:}} hacks
+ // see bugs 37564, 37587, 36938
+ $cache[$code] =
strcspn( $code, ":/\\\000&<>'\"" ) === strlen( $code )
&& !preg_match( Title::getTitleInvalidRegex(), $code );
+
+ return $cache[$code];
}
/**
static $coreLanguageNames;
if ( $coreLanguageNames === null ) {
- include( MWInit::compiledPath( 'languages/Names.php' ) );
+ include MWInit::compiledPath( 'languages/Names.php' );
}
if ( isset( $coreLanguageNames[$tag] )
return;
}
- if ( !defined( 'MW_COMPILED' ) ) {
- if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
- include_once( "$IP/languages/classes/$class.php" );
- }
+ if ( file_exists( "$IP/languages/classes/$class.php" ) ) {
+ include_once "$IP/languages/classes/$class.php";
}
}
}
/**
+ * Returns an array of localised namespaces indexed by their numbers. If the namespace is not
+ * available in localised form, it will be included in English.
+ *
* @return array
*/
public function getNamespaces() {
static $coreLanguageNames;
if ( $coreLanguageNames === null ) {
- include( MWInit::compiledPath( 'languages/Names.php' ) );
+ include MWInit::compiledPath( 'languages/Names.php' );
}
$names = array();
* @param $zone DateTimeZone: Timezone of $ts
* @todo handling of "o" format character for Iranian, Hebrew, Hijri & Thai?
*
+ * @throws MWException
* @return string
*/
function sprintfDate( $format, $ts, DateTimeZone $zone = null ) {
$thai = false;
$minguo = false;
$tenno = false;
+
+ if ( strlen( $ts ) !== 14 ) {
+ throw new MWException( __METHOD__ . ": The timestamp $ts should have 14 characters" );
+ }
+
+ if ( !ctype_digit( $ts ) ) {
+ throw new MWException( __METHOD__ . ": The timestamp $ts should be a number" );
+ }
+
for ( $p = 0; $p < strlen( $format ); $p++ ) {
$num = false;
$code = $format[$p];
$s .= $this->getMonthNameGen( substr( $ts, 4, 2 ) );
break;
case 'xjx':
- if ( !$hebrew ) $hebrew = self::tsToHebrew( $ts );
+ if ( !$hebrew ) {
+ $hebrew = self::tsToHebrew( $ts );
+ }
$s .= $this->getHebrewCalendarMonthNameGen( $hebrew[1] );
break;
case 'd':
}
if ( strlen( $s ) == 2 ) {
$str = $s . "'";
- } else {
+ } else {
$str = substr( $s, 0, strlen( $s ) - 2 ) . '"';
$str .= substr( $s, strlen( $s ) - 2, 2 );
}
$start = substr( $str, 0, strlen( $str ) - 2 );
$end = substr( $str, strlen( $str ) - 2 );
- switch( $end ) {
+ switch ( $end ) {
case 'כ':
$str = $start . 'ך';
break;
# 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 ) ), # Hours
- (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
- (int)substr( $ts, 12, 2 ), # Seconds
- (int)substr( $ts, 4, 2 ), # Month
- (int)substr( $ts, 6, 2 ), # Day
- (int)substr( $ts, 0, 4 ) ); # Year
+ (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
+ (int)substr( $ts, 6, 2 ), # Day
+ (int)substr( $ts, 0, 4 ) ); # Year
$date = date( 'YmdHis', $t );
wfRestoreWarnings();
* @param $type string May be date, time or both
* @param $pref string The format name as it appears in Messages*.php
*
+ * @since 1.22 New type 'pretty' that provides a more readable timestamp format
+ *
* @return string
*/
function getDateFormatString( $type, $pref ) {
$df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
} else {
$df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
- if ( is_null( $df ) ) {
+
+ if ( $type === 'pretty' && $df === null ) {
+ $df = $this->getDateFormatString( 'date', $pref );
+ }
+
+ if ( $df === null ) {
$pref = $this->getDefaultDateFormat();
$df = self::$dataCache->getSubitem( $this->mCode, 'dateFormats', "$pref $type" );
}
return $this->internalUserTimeAndDate( 'both', $ts, $user, $options );
}
+ /**
+ * Convert an MWTimestamp into a pretty human-readable timestamp using
+ * the given user preferences and relative base time.
+ *
+ * DO NOT USE THIS FUNCTION DIRECTLY. Instead, call MWTimestamp::getHumanTimestamp
+ * on your timestamp object, which will then call this function. Calling
+ * this function directly will cause hooks to be skipped over.
+ *
+ * @see MWTimestamp::getHumanTimestamp
+ * @param MWTimestamp $ts Timestamp to prettify
+ * @param MWTimestamp $relativeTo Base timestamp
+ * @param User $user User preferences to use
+ * @return string Human timestamp
+ * @since 1.21
+ */
+ public function getHumanTimestamp( MWTimestamp $ts, MWTimestamp $relativeTo, User $user ) {
+ $diff = $ts->diff( $relativeTo );
+ $diffDay = (bool)( (int)$ts->timestamp->format( 'w' ) - (int)$relativeTo->timestamp->format( 'w' ) );
+ $days = $diff->days ?: (int)$diffDay;
+ if ( $diff->invert || $days > 5 && $ts->timestamp->format( 'Y' ) !== $relativeTo->timestamp->format( 'Y' ) ) {
+ // Timestamps are in different years: use full timestamp
+ // Also do full timestamp for future dates
+ /**
+ * @FIXME Add better handling of future timestamps.
+ */
+ $format = $this->getDateFormatString( 'both', $user->getDatePreference() ?: 'default' );
+ $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
+ } elseif ( $days > 5 ) {
+ // Timestamps are in same year, but more than 5 days ago: show day and month only.
+ $format = $this->getDateFormatString( 'pretty', $user->getDatePreference() ?: 'default' );
+ $ts = $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) );
+ } elseif ( $days > 1 ) {
+ // Timestamp within the past week: show the day of the week and time
+ $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
+ $weekday = self::$mWeekdayMsgs[$ts->timestamp->format( 'w' )];
+ $ts = wfMessage( "$weekday-at" )
+ ->inLanguage( $this )
+ ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
+ ->text();
+ } elseif ( $days == 1 ) {
+ // Timestamp was yesterday: say 'yesterday' and the time.
+ $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
+ $ts = wfMessage( 'yesterday-at' )
+ ->inLanguage( $this )
+ ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
+ ->text();
+ } elseif ( $diff->h > 1 || $diff->h == 1 && $diff->i > 30 ) {
+ // Timestamp was today, but more than 90 minutes ago: say 'today' and the time.
+ $format = $this->getDateFormatString( 'time', $user->getDatePreference() ?: 'default' );
+ $ts = wfMessage( 'today-at' )
+ ->inLanguage( $this )
+ ->params( $this->sprintfDate( $format, $ts->getTimestamp( TS_MW ) ) )
+ ->text();
+
+ // From here on in, the timestamp was soon enough ago so that we can simply say
+ // XX units ago, e.g., "2 hours ago" or "5 minutes ago"
+ } elseif ( $diff->h == 1 ) {
+ // Less than 90 minutes, but more than an hour ago.
+ $ts = wfMessage( 'hours-ago' )->inLanguage( $this )->numParams( 1 )->text();
+ } elseif ( $diff->i >= 1 ) {
+ // A few minutes ago.
+ $ts = wfMessage( 'minutes-ago' )->inLanguage( $this )->numParams( $diff->i )->text();
+ } elseif ( $diff->s >= 30 ) {
+ // Less than a minute, but more than 30 sec ago.
+ $ts = wfMessage( 'seconds-ago' )->inLanguage( $this )->numParams( $diff->s )->text();
+ } else {
+ // Less than 30 seconds ago.
+ $ts = wfMessage( 'just-now' )->text();
+ }
+
+ return $ts;
+ }
+
/**
* @param $key string
* @return array|null
* @since 1.20
*/
function getDirMarkEntity( $opposite = false ) {
- if ( $opposite ) { return $this->isRTL() ? '‎' : '‏'; }
+ if ( $opposite ) {
+ return $this->isRTL() ? '‎' : '‏';
+ }
return $this->isRTL() ? '‏' : '‎';
}
function getDirMark( $opposite = false ) {
$lrm = "\xE2\x80\x8E"; # LEFT-TO-RIGHT MARK, commonly abbreviated LRM
$rlm = "\xE2\x80\x8F"; # RIGHT-TO-LEFT MARK, commonly abbreviated RLM
- if ( $opposite ) { return $this->isRTL() ? $lrm : $rlm; }
+ if ( $opposite ) {
+ return $this->isRTL() ? $lrm : $rlm;
+ }
return $this->isRTL() ? $rlm : $lrm;
}
$numMatches = preg_match_all( "/(#+)/", $digitGroupingPattern, $matches );
preg_match( "/\d+/", $number, $integerPart );
preg_match( "/\.\d*/", $number, $decimalPart );
- $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0]:"";
+ $groupedNumber = ( count( $decimalPart ) > 0 ) ? $decimalPart[0] : "";
if ( $groupedNumber === $number ) {
// the string does not have any number part. Eg: .12345
return $sign . $groupedNumber;
}
$start = $end = strlen( $integerPart[0] );
while ( $start > 0 ) {
- $match = $matches[0][$numMatches -1] ;
+ $match = $matches[0][$numMatches - 1];
$matchLen = strlen( $match );
$start = $end - $matchLen;
if ( $start < 0 ) {
$start = 0;
}
- $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber ;
+ $groupedNumber = substr( $number, $start, $end -$start ) . $groupedNumber;
$end = $start;
if ( $numMatches > 1 ) {
// use the last pattern for the rest of the number
# We got the first byte only of a multibyte char; remove it.
$string = substr( $string, 0, -1 );
} elseif ( $char >= 0x80 &&
- preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
- '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m ) )
- {
+ preg_match( '/^(.*)(?:[\xe0-\xef][\x80-\xbf]|' .
+ '[\xf0-\xf7][\x80-\xbf]{1,2})$/', $string, $m )
+ ) {
# We chopped in the middle of a character; remove it
$string = $m[1];
}
break;
}
}
- if ( $pos >= $textLen ) break; // extra iteration just for above checks
+ if ( $pos >= $textLen ) {
+ break; // extra iteration just for above checks
+ }
# Read the next char...
$ch = $text[$pos];
function getGrammarForms() {
global $wgGrammarForms;
if ( isset( $wgGrammarForms[$this->getCode()] ) && is_array( $wgGrammarForms[$this->getCode()] ) ) {
- return $wgGrammarForms[$this->getCode()];
+ return $wgGrammarForms[$this->getCode()];
}
return array();
}
* @since 1.18
*/
public function formatExpiry( $expiry, $format = true ) {
- static $infinity, $infinityMsg;
+ static $infinity;
if ( $infinity === null ) {
- $infinityMsg = wfMessage( 'infiniteblock' );
$infinity = wfGetDB( DB_SLAVE )->getInfinity();
}
if ( $expiry == '' || $expiry == $infinity ) {
return $format === true
- ? $infinityMsg
+ ? $this->getMessageFromDB( 'infiniteblock' )
: $infinity;
} else {
return $format === true