Template:Dir is one of the most used templates in Wikimedia Commons,
this tries to provide parts of its functionality in hope we can
perhaps simplify or get rid of the template eventually for clarity and
performance reasons.
As a convenience, `{{#dir}}` and `{{#dir:}}` are synonyms for
`{{#dir:{{PAGELANGUAGE}}}}`: they return the direction of the target
language. For articles, the target language is the content language;
for messages, the target language is the user language.
In addition, to avoid confusion between BCP-47 language codes and
MediaWiki-internal language codes, an optional second parameter can be
supplied. If the second parameter is the (localizable) string
'bcp47', the language code given in the first parameter will be
treated as a BCP-47 code. For example: `{{#dir:sr-Cyrl|bcp47}}`.
(See LanguageCode::bcp47ToInternal() for a description of the
differences and overlaps between MediaWiki internal and BCP-47
codes. These overlaps *so far* don't result in any case where
encouraging editors to be precise about which set of enumerated
string values they are using for consistency with other
language-related functions, and because MediaWiki internally
differentiates between BCP-47 codes and internal codes.)
Bug: T359761
Change-Id: I19c3e91a924e080f37dc95a0d4e61493583b533e
356 lines
12 KiB
PHP
356 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Magic variable implementations provided by MediaWiki core
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
* @file
|
|
* @ingroup Parser
|
|
*/
|
|
use MediaWiki\Config\ServiceOptions;
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\Parser\Parser;
|
|
use MediaWiki\Specials\SpecialVersion;
|
|
use MediaWiki\Utils\MWTimestamp;
|
|
use Psr\Log\LoggerInterface;
|
|
use Wikimedia\Timestamp\ConvertibleTimestamp;
|
|
|
|
/**
|
|
* Expansions of core magic variables, used by the parser.
|
|
* @internal
|
|
* @ingroup Parser
|
|
*/
|
|
class CoreMagicVariables {
|
|
/** Map of (word ID => cache TTL hint) */
|
|
private const CACHE_TTL_BY_ID = [
|
|
'currenttime' => 3600,
|
|
'localtime' => 3600,
|
|
'numberofarticles' => 3600,
|
|
'numberoffiles' => 3600,
|
|
'numberofedits' => 3600,
|
|
'numberofusers' => 3600,
|
|
'numberofactiveusers' => 3600,
|
|
'numberofpages' => 3600,
|
|
'currentversion' => 86400,
|
|
'currenttimestamp' => 3600,
|
|
'localtimestamp' => 3600,
|
|
'pagesinnamespace' => 3600,
|
|
'numberofadmins' => 3600,
|
|
'numberingroup' => 3600,
|
|
];
|
|
|
|
/** Map of (time unit => relative datetime specifier) */
|
|
private const DEADLINE_DATE_SPEC_BY_UNIT = [
|
|
'Y' => 'first day of January next year midnight',
|
|
'M' => 'first day of next month midnight',
|
|
'D' => 'next day midnight',
|
|
// Note that this relative datetime specifier does not zero out
|
|
// minutes/seconds, but we will do so manually in
|
|
// ::applyUnitTimestampDeadline() when given the unit 'H'
|
|
'H' => 'next hour'
|
|
];
|
|
/** Seconds of clock skew fudge factor for time-interval deadline TTLs */
|
|
private const DEADLINE_TTL_CLOCK_FUDGE = 1;
|
|
/** Max seconds to "randomly" add to time-interval deadline TTLs to avoid stampedes */
|
|
private const DEADLINE_TTL_STAGGER_MAX = 15;
|
|
/** Minimum time-interval deadline TTL */
|
|
private const MIN_DEADLINE_TTL = 15;
|
|
|
|
/**
|
|
* Expand the magic variable given by $index.
|
|
* @internal
|
|
* @param Parser $parser
|
|
* @param string $id The name of the variable, and equivalently, the magic
|
|
* word ID which was used to match the variable
|
|
* @param ConvertibleTimestamp $ts Timestamp to use when expanding magic variable
|
|
* @param ServiceOptions $svcOptions Service options for the parser
|
|
* @param LoggerInterface $logger
|
|
* @return string|null The expanded value, as wikitext, or null to
|
|
* indicate the given index wasn't a known magic variable.
|
|
*/
|
|
public static function expand(
|
|
// Fundamental options
|
|
Parser $parser,
|
|
string $id,
|
|
// Context passed over from the parser
|
|
ConvertibleTimestamp $ts,
|
|
ServiceOptions $svcOptions,
|
|
LoggerInterface $logger
|
|
): ?string {
|
|
$pageLang = $parser->getTargetLanguage();
|
|
|
|
$cacheTTL = self::CACHE_TTL_BY_ID[$id] ?? -1;
|
|
if ( $cacheTTL > -1 ) {
|
|
$parser->getOutput()->updateCacheExpiry( $cacheTTL );
|
|
}
|
|
|
|
switch ( $id ) {
|
|
case '!':
|
|
return '|';
|
|
case '=':
|
|
return '=';
|
|
case 'currentmonth':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'M' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $ts->format( 'm' ) );
|
|
case 'currentmonth1':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'M' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $ts->format( 'n' ) );
|
|
case 'currentmonthname':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'M' );
|
|
|
|
return $pageLang->getMonthName( (int)$ts->format( 'n' ) );
|
|
case 'currentmonthnamegen':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'M' );
|
|
|
|
return $pageLang->getMonthNameGen( (int)$ts->format( 'n' ) );
|
|
case 'currentmonthabbrev':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'M' );
|
|
|
|
return $pageLang->getMonthAbbreviation( (int)$ts->format( 'n' ) );
|
|
case 'currentday':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'D' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $ts->format( 'j' ) );
|
|
case 'currentday2':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'D' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $ts->format( 'd' ) );
|
|
case 'localmonth':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'M' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $localTs->format( 'm' ) );
|
|
case 'localmonth1':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'M' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $localTs->format( 'n' ) );
|
|
case 'localmonthname':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'M' );
|
|
|
|
return $pageLang->getMonthName( (int)$localTs->format( 'n' ) );
|
|
case 'localmonthnamegen':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'M' );
|
|
|
|
return $pageLang->getMonthNameGen( (int)$localTs->format( 'n' ) );
|
|
case 'localmonthabbrev':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'M' );
|
|
|
|
return $pageLang->getMonthAbbreviation( (int)$localTs->format( 'n' ) );
|
|
case 'localday':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'D' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $localTs->format( 'j' ) );
|
|
case 'localday2':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'D' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $localTs->format( 'd' ) );
|
|
case 'pagename':
|
|
case 'pagenamee':
|
|
case 'fullpagename':
|
|
case 'fullpagenamee':
|
|
case 'subpagename':
|
|
case 'subpagenamee':
|
|
case 'rootpagename':
|
|
case 'rootpagenamee':
|
|
case 'basepagename':
|
|
case 'basepagenamee':
|
|
case 'talkpagename':
|
|
case 'talkpagenamee':
|
|
case 'subjectpagename':
|
|
case 'subjectpagenamee':
|
|
case 'pageid':
|
|
case 'revisionid':
|
|
case 'revisionuser':
|
|
case 'revisionday':
|
|
case 'revisionday2':
|
|
case 'revisionmonth':
|
|
case 'revisionmonth1':
|
|
case 'revisionyear':
|
|
case 'revisiontimestamp':
|
|
case 'namespace':
|
|
case 'namespacee':
|
|
case 'namespacenumber':
|
|
case 'talkspace':
|
|
case 'talkspacee':
|
|
case 'subjectspace':
|
|
case 'subjectspacee':
|
|
case 'cascadingsources':
|
|
# First argument of the corresponding parser function
|
|
# (second argument of the PHP implementation) is
|
|
# "title".
|
|
|
|
# Note that for many of these {{FOO}} is subtly different
|
|
# from {{FOO:{{PAGENAME}}}}, so we can't pass $title here
|
|
# we have to explicitly use the "no arguments" form of the
|
|
# parser function by passing `null` to indicate a missing
|
|
# argument (which then defaults to the current page title).
|
|
return CoreParserFunctions::$id( $parser, null );
|
|
case 'revisionsize':
|
|
return (string)$parser->getRevisionSize();
|
|
case 'currentdayname':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'D' );
|
|
|
|
return $pageLang->getWeekdayName( (int)$ts->format( 'w' ) + 1 );
|
|
case 'currentyear':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'Y' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $ts->format( 'Y' ) );
|
|
case 'currenttime':
|
|
return $pageLang->time( $ts->getTimestamp( TS_MW ), false, false );
|
|
case 'currenthour':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'H' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $ts->format( 'H' ) );
|
|
case 'currentweek':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'D' );
|
|
// @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
|
|
// int to remove the padding
|
|
return $pageLang->formatNum( (int)$ts->format( 'W' ) );
|
|
case 'currentdow':
|
|
self::applyUnitTimestampDeadline( $parser, $ts, 'D' );
|
|
|
|
return $pageLang->formatNum( $ts->format( 'w' ) );
|
|
case 'localdayname':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'D' );
|
|
|
|
return $pageLang->getWeekdayName( (int)$localTs->format( 'w' ) + 1 );
|
|
case 'localyear':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'Y' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $localTs->format( 'Y' ) );
|
|
case 'localtime':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
|
|
return $pageLang->time(
|
|
$localTs->format( 'YmdHis' ),
|
|
false,
|
|
false
|
|
);
|
|
case 'localhour':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'H' );
|
|
|
|
return $pageLang->formatNumNoSeparators( $localTs->format( 'H' ) );
|
|
case 'localweek':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'D' );
|
|
// @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
|
|
// int to remove the padding
|
|
return $pageLang->formatNum( (int)$localTs->format( 'W' ) );
|
|
case 'localdow':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
self::applyUnitTimestampDeadline( $parser, $localTs, 'D' );
|
|
|
|
return $pageLang->formatNum( $localTs->format( 'w' ) );
|
|
case 'numberofarticles':
|
|
case 'numberoffiles':
|
|
case 'numberofusers':
|
|
case 'numberofactiveusers':
|
|
case 'numberofpages':
|
|
case 'numberofadmins':
|
|
case 'numberofedits':
|
|
# second argument is 'raw'; magic variables are "not raw"
|
|
return CoreParserFunctions::$id( $parser, null );
|
|
case 'currenttimestamp':
|
|
return $ts->getTimestamp( TS_MW );
|
|
case 'localtimestamp':
|
|
$localTs = self::makeTsLocal( $svcOptions, $ts );
|
|
|
|
return $localTs->format( 'YmdHis' );
|
|
case 'currentversion':
|
|
return SpecialVersion::getVersion();
|
|
case 'articlepath':
|
|
return (string)$svcOptions->get( MainConfigNames::ArticlePath );
|
|
case 'sitename':
|
|
return (string)$svcOptions->get( MainConfigNames::Sitename );
|
|
case 'server':
|
|
return (string)$svcOptions->get( MainConfigNames::Server );
|
|
case 'servername':
|
|
return (string)$svcOptions->get( MainConfigNames::ServerName );
|
|
case 'scriptpath':
|
|
return (string)$svcOptions->get( MainConfigNames::ScriptPath );
|
|
case 'stylepath':
|
|
return (string)$svcOptions->get( MainConfigNames::StylePath );
|
|
case 'directionmark':
|
|
return $pageLang->getDirMark();
|
|
case 'contentlanguage':
|
|
return $parser->getContentLanguage()->getCode();
|
|
case 'pagelanguage':
|
|
return $pageLang->getCode();
|
|
case 'dir':
|
|
# magic variables are the same as empty/default first argument
|
|
return CoreParserFunctions::$id( $parser );
|
|
default:
|
|
// This is not one of the core magic variables
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper to convert a timestamp instance to local time
|
|
* @see MWTimestamp::getLocalInstance()
|
|
* @param ServiceOptions $svcOptions Service options for the parser
|
|
* @param ConvertibleTimestamp $ts Timestamp to convert
|
|
* @return ConvertibleTimestamp
|
|
*/
|
|
private static function makeTsLocal( $svcOptions, $ts ) {
|
|
$localtimezone = $svcOptions->get( MainConfigNames::Localtimezone );
|
|
$ts->setTimezone( $localtimezone );
|
|
return $ts;
|
|
}
|
|
|
|
/**
|
|
* Adjust the cache expiry to account for a dynamic timestamp displayed in output
|
|
*
|
|
* @param Parser $parser
|
|
* @param ConvertibleTimestamp $ts Current timestamp with the display timezone
|
|
* @param string $unit The unit the timestamp is expressed in; one of ("Y", "M", "D", "H")
|
|
*/
|
|
private static function applyUnitTimestampDeadline(
|
|
Parser $parser,
|
|
ConvertibleTimestamp $ts,
|
|
string $unit
|
|
) {
|
|
$tsUnix = (int)$ts->getTimestamp( TS_UNIX );
|
|
|
|
$date = new DateTime( "@$tsUnix" );
|
|
$date->setTimezone( $ts->getTimezone() );
|
|
$date->modify( self::DEADLINE_DATE_SPEC_BY_UNIT[$unit] );
|
|
if ( $unit === 'H' ) {
|
|
// Zero out the minutes/seconds
|
|
$date->setTime( intval( $date->format( 'H' ), 10 ), 0, 0 );
|
|
} else {
|
|
$date->setTime( 0, 0, 0 );
|
|
}
|
|
$deadlineUnix = (int)$date->format( 'U' );
|
|
|
|
$ttl = max( $deadlineUnix - $tsUnix, self::MIN_DEADLINE_TTL );
|
|
$ttl += self::DEADLINE_TTL_CLOCK_FUDGE;
|
|
$ttl += ( $deadlineUnix % self::DEADLINE_TTL_STAGGER_MAX );
|
|
|
|
$parser->getOutput()->updateCacheExpiry( $ttl );
|
|
}
|
|
}
|