wiki.techinc.nl/includes/parser/CoreMagicVariables.php
C. Scott Ananian 06ab90f163 Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput.  It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid.  It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name.  (Note that Parser::setOutputFlag() already called
these "output flags".)

In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API.  (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)

There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.

Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=

Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-15 14:25:54 -04:00

384 lines
14 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\Parser\ParserOutputFlags;
use Psr\Log\LoggerInterface;
/**
* Expansions of core magic variables, used by the parser.
* @internal
* @ingroup Parser
*/
class CoreMagicVariables {
/** @var int Assume that no output will later be saved this many seconds after parsing */
private const MAX_TTS = 900;
/**
* 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 int $ts Timestamp to use when expanding magic variable
* @param NamespaceInfo $nsInfo The NamespaceInfo to use when expanding
* @param ServiceOptions $svcOptions Service options for the parser
* @param LoggerInterface $logger
* @return string|null The expanded value, 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
int $ts,
NamespaceInfo $nsInfo,
ServiceOptions $svcOptions,
LoggerInterface $logger
): ?string {
$pageLang = $parser->getFunctionLang();
$title = $parser->getTitle();
switch ( $id ) {
case '!':
return '|';
case 'currentmonth':
return $pageLang->formatNumNoSeparators( MWTimestamp::getInstance( $ts )->format( 'm' ) );
case 'currentmonth1':
return $pageLang->formatNumNoSeparators( MWTimestamp::getInstance( $ts )->format( 'n' ) );
case 'currentmonthname':
return $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
case 'currentmonthnamegen':
return $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
case 'currentmonthabbrev':
return $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
case 'currentday':
return $pageLang->formatNumNoSeparators( MWTimestamp::getInstance( $ts )->format( 'j' ) );
case 'currentday2':
return $pageLang->formatNumNoSeparators( MWTimestamp::getInstance( $ts )->format( 'd' ) );
case 'localmonth':
return $pageLang->formatNumNoSeparators( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
case 'localmonth1':
return $pageLang->formatNumNoSeparators( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
case 'localmonthname':
return $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
case 'localmonthnamegen':
return $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
case 'localmonthabbrev':
return $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
case 'localday':
return $pageLang->formatNumNoSeparators( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
case 'localday2':
return $pageLang->formatNumNoSeparators( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
case 'pagename':
return wfEscapeWikiText( $title->getText() );
case 'pagenamee':
return wfEscapeWikiText( $title->getPartialURL() );
case 'fullpagename':
return wfEscapeWikiText( $title->getPrefixedText() );
case 'fullpagenamee':
return wfEscapeWikiText( $title->getPrefixedURL() );
case 'subpagename':
return wfEscapeWikiText( $title->getSubpageText() );
case 'subpagenamee':
return wfEscapeWikiText( $title->getSubpageUrlForm() );
case 'rootpagename':
return wfEscapeWikiText( $title->getRootText() );
case 'rootpagenamee':
return wfEscapeWikiText( wfUrlencode( str_replace(
' ',
'_',
$title->getRootText()
) ) );
case 'basepagename':
return wfEscapeWikiText( $title->getBaseText() );
case 'basepagenamee':
return wfEscapeWikiText( wfUrlencode( str_replace(
' ',
'_',
$title->getBaseText()
) ) );
case 'talkpagename':
if ( $title->canHaveTalkPage() ) {
$talkPage = $title->getTalkPage();
return wfEscapeWikiText( $talkPage->getPrefixedText() );
}
return '';
case 'talkpagenamee':
if ( $title->canHaveTalkPage() ) {
$talkPage = $title->getTalkPage();
return wfEscapeWikiText( $talkPage->getPrefixedURL() );
}
return '';
case 'subjectpagename':
$subjPage = $title->getSubjectPage();
return wfEscapeWikiText( $subjPage->getPrefixedText() );
case 'subjectpagenamee':
$subjPage = $title->getSubjectPage();
return wfEscapeWikiText( $subjPage->getPrefixedURL() );
case 'pageid': // requested in T25427
// Inform the edit saving system that getting the canonical output
// after page insertion requires a parse that used that exact page ID
self::setOutputFlag( $parser, $logger, ParserOutputFlags::VARY_PAGE_ID, '{{PAGEID}} used' );
$value = $title->getArticleID();
if ( !$value ) {
$value = $parser->getOptions()->getSpeculativePageId();
if ( $value ) {
$parser->getOutput()->setSpeculativePageIdUsed( $value );
}
}
return (string)$value;
case 'revisionid':
$namespace = $title->getNamespace();
if (
$svcOptions->get( 'MiserMode' ) &&
!$parser->getOptions()->getInterfaceMessage() &&
// @TODO: disallow this variable on all namespaces
$nsInfo->isSubject( $namespace )
) {
// Use a stub result instead of the actual revision ID in order to avoid
// double parses on page save but still allow preview detection (T137900)
if ( $parser->getRevisionId() || $parser->getOptions()->getSpeculativeRevId() ) {
return '-';
} else {
self::setOutputFlag(
$parser,
$logger,
ParserOutputFlags::VARY_REVISION_EXISTS,
'{{REVISIONID}} used'
);
return '';
}
} else {
// Inform the edit saving system that getting the canonical output after
// revision insertion requires a parse that used that exact revision ID
self::setOutputFlag( $parser, $logger, ParserOutputFlags::VARY_REVISION_ID, '{{REVISIONID}} used' );
$value = $parser->getRevisionId();
if ( $value === 0 ) {
$rev = $parser->getRevisionRecordObject();
$value = $rev ? $rev->getId() : $value;
}
if ( !$value ) {
$value = $parser->getOptions()->getSpeculativeRevId();
if ( $value ) {
$parser->getOutput()->setSpeculativeRevIdUsed( $value );
}
}
return (string)$value;
}
case 'revisionday':
return strval( (int)self::getRevisionTimestampSubstring(
$parser, $logger, 6, 2, self::MAX_TTS, $id
) );
case 'revisionday2':
return self::getRevisionTimestampSubstring(
$parser, $logger, 6, 2, self::MAX_TTS, $id
);
case 'revisionmonth':
return self::getRevisionTimestampSubstring(
$parser, $logger, 4, 2, self::MAX_TTS, $id
);
case 'revisionmonth1':
return strval( (int)self::getRevisionTimestampSubstring(
$parser, $logger, 4, 2, self::MAX_TTS, $id
) );
case 'revisionyear':
return self::getRevisionTimestampSubstring(
$parser, $logger, 0, 4, self::MAX_TTS, $id
);
case 'revisiontimestamp':
return self::getRevisionTimestampSubstring(
$parser, $logger, 0, 14, self::MAX_TTS, $id
);
case 'revisionuser':
// Inform the edit saving system that getting the canonical output after
// revision insertion requires a parse that used the actual user ID
self::setOutputFlag( $parser, $logger, ParserOutputFlags::VARY_USER, '{{REVISIONUSER}} used' );
// Note that getRevisionUser() can return null; we need to
// be sure to cast this to (an empty) string, since 'null'
// means "magic variable not handled here".
return (string)$parser->getRevisionUser();
case 'revisionsize':
return (string)$parser->getRevisionSize();
case 'namespace':
return str_replace( '_', ' ',
$parser->getContentLanguage()->getNsText( $title->getNamespace() ) );
case 'namespacee':
return wfUrlencode( $parser->getContentLanguage()->getNsText( $title->getNamespace() ) );
case 'namespacenumber':
return $title->getNamespace();
case 'talkspace':
return $title->canHaveTalkPage()
? str_replace( '_', ' ', $title->getTalkNsText() )
: '';
case 'talkspacee':
return $title->canHaveTalkPage()
? wfUrlencode( $title->getTalkNsText() )
: '';
case 'subjectspace':
return str_replace( '_', ' ', $title->getSubjectNsText() );
case 'subjectspacee':
return ( wfUrlencode( $title->getSubjectNsText() ) );
case 'currentdayname':
return $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
case 'currentyear':
return $pageLang->formatNumNoSeparators( MWTimestamp::getInstance( $ts )->format( 'Y' ) );
case 'currenttime':
return $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
case 'currenthour':
return $pageLang->formatNumNoSeparators( MWTimestamp::getInstance( $ts )->format( 'H' ) );
case 'currentweek':
// @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
// int to remove the padding
return $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
case 'currentdow':
return $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
case 'localdayname':
return $pageLang->getWeekdayName(
(int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
);
case 'localyear':
return $pageLang->formatNumNoSeparators( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ) );
case 'localtime':
return $pageLang->time(
MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
false,
false
);
case 'localhour':
return $pageLang->formatNumNoSeparators( MWTimestamp::getLocalInstance( $ts )->format( 'H' ) );
case 'localweek':
// @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
// int to remove the padding
return $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
case 'localdow':
return $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
case 'numberofarticles':
return $pageLang->formatNum( SiteStats::articles() );
case 'numberoffiles':
return $pageLang->formatNum( SiteStats::images() );
case 'numberofusers':
return $pageLang->formatNum( SiteStats::users() );
case 'numberofactiveusers':
return $pageLang->formatNum( SiteStats::activeUsers() );
case 'numberofpages':
return $pageLang->formatNum( SiteStats::pages() );
case 'numberofadmins':
return $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
case 'numberofedits':
return $pageLang->formatNum( SiteStats::edits() );
case 'currenttimestamp':
return wfTimestamp( TS_MW, $ts );
case 'localtimestamp':
return MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
case 'currentversion':
return SpecialVersion::getVersion();
case 'articlepath':
return (string)$svcOptions->get( 'ArticlePath' );
case 'sitename':
return (string)$svcOptions->get( 'Sitename' );
case 'server':
return (string)$svcOptions->get( 'Server' );
case 'servername':
return (string)$svcOptions->get( 'ServerName' );
case 'scriptpath':
return (string)$svcOptions->get( 'ScriptPath' );
case 'stylepath':
return (string)$svcOptions->get( 'StylePath' );
case 'directionmark':
return $pageLang->getDirMark();
case 'contentlanguage':
return (string)$svcOptions->get( 'LanguageCode' );
case 'pagelanguage':
return $pageLang->getCode();
case 'cascadingsources':
return CoreParserFunctions::cascadingsources( $parser );
default:
// This is not one of the core magic variables
return null;
}
}
/**
* @param Parser $parser
* @param LoggerInterface $logger
* @param int $start
* @param int $len
* @param int $mtts Max time-till-save; sets vary-revision-timestamp if result changes by then
* @param string $variable Parser variable name
* @return string
*/
private static function getRevisionTimestampSubstring(
Parser $parser,
LoggerInterface $logger,
int $start,
int $len,
int $mtts,
string $variable
): string {
// Get the timezone-adjusted timestamp to be used for this revision
$resNow = substr( $parser->getRevisionTimestamp(), $start, $len );
// Possibly set vary-revision if there is not yet an associated revision
if ( !$parser->getRevisionRecordObject() ) {
// Get the timezone-adjusted timestamp $mtts seconds in the future.
// This future is relative to the current time and not that of the
// parser options. The rendered timestamp can be compared to that
// of the timestamp specified by the parser options.
$resThen = substr(
$parser->getContentLanguage()->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
$start,
$len
);
if ( $resNow !== $resThen ) {
// Inform the edit saving system that getting the canonical output after
// revision insertion requires a parse that used an actual revision timestamp
self::setOutputFlag( $parser, $logger, ParserOutputFlags::VARY_REVISION_TIMESTAMP, "$variable used" );
}
}
return $resNow;
}
/**
* Helper method borrowed from Parser.php: sets the flag on the output
* but also does some debug logging.
* @param Parser $parser
* @param LoggerInterface $logger
* @param string $flag
* @param string $reason
*/
private static function setOutputFlag(
Parser $parser,
LoggerInterface $logger,
string $flag,
string $reason
): void {
$parser->getOutput()->setOutputFlag( $flag );
$name = $parser->getTitle()->getPrefixedText();
// This code was moved from Parser::setOutputFlag and used __METHOD__
// originally; we've hard-coded that output here so that our refactor
// doesn't change the messages in the logs.
$logger->debug( "Parser::setOutputFlag: set $flag flag on '$name'; $reason" );
}
}