* @file
* @ingroup Parser
*/
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\Linker\LinkRendererFactory;
use MediaWiki\Linker\LinkTarget;
* @var MagicWordArray
*/
public $mSubstWords;
+
+ /**
+ * @deprecated since 1.34, there should be no need to use this
+ * @var array
+ */
+ public $mConf;
+
# Initialised in constructor
- public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols;
+ public $mExtLinkBracketedRegex, $mUrlProtocols;
# Initialized in getPreprocessor()
/** @var Preprocessor */
/** @var SpecialPageFactory */
private $specialPageFactory;
- /** @var Config */
- private $siteConfig;
+ /**
+ * This is called $svcOptions instead of $options like elsewhere to avoid confusion with
+ * $mOptions, which is public and widely used, and also with the local variable $options used
+ * for ParserOptions throughout this file.
+ *
+ * @var ServiceOptions
+ */
+ private $svcOptions;
/** @var LinkRendererFactory */
private $linkRendererFactory;
private $nsInfo;
/**
- * @param array $parserConf See $wgParserConf documentation
+ * TODO Make this a const when HHVM support is dropped (T192166)
+ *
+ * @var array
+ * @since 1.33
+ */
+ public static $constructorOptions = [
+ // See $wgParserConf documentation
+ 'class',
+ 'preprocessorClass',
+ // See documentation for the corresponding config options
+ 'ArticlePath',
+ 'EnableScaryTranscluding',
+ 'ExtraInterlanguageLinkPrefixes',
+ 'FragmentMode',
+ 'LanguageCode',
+ 'MaxSigChars',
+ 'MaxTocLevel',
+ 'MiserMode',
+ 'ScriptPath',
+ 'Server',
+ 'ServerName',
+ 'ShowHostnames',
+ 'Sitename',
+ 'StylePath',
+ 'TranscludeCacheExpiry',
+ ];
+
+ /**
+ * Constructing parsers directly is deprecated! Use a ParserFactory.
+ *
+ * @param ServiceOptions|null $svcOptions
* @param MagicWordFactory|null $magicWordFactory
* @param Language|null $contLang Content language
* @param ParserFactory|null $factory
* @param string|null $urlProtocols As returned from wfUrlProtocols()
* @param SpecialPageFactory|null $spFactory
- * @param Config|null $siteConfig
* @param LinkRendererFactory|null $linkRendererFactory
* @param NamespaceInfo|null $nsInfo
*/
public function __construct(
- array $parserConf = [], MagicWordFactory $magicWordFactory = null,
+ $svcOptions = null, MagicWordFactory $magicWordFactory = null,
Language $contLang = null, ParserFactory $factory = null, $urlProtocols = null,
- SpecialPageFactory $spFactory = null, Config $siteConfig = null,
- LinkRendererFactory $linkRendererFactory = null,
- NamespaceInfo $nsInfo = null
+ SpecialPageFactory $spFactory = null, $linkRendererFactory = null, $nsInfo = null
) {
- $this->mConf = $parserConf;
+ $services = MediaWikiServices::getInstance();
+ if ( !$svcOptions || is_array( $svcOptions ) ) {
+ // Pre-1.34 calling convention is the first parameter is just ParserConf, the seventh is
+ // Config, and the eighth is LinkRendererFactory.
+ $this->mConf = (array)$svcOptions;
+ if ( empty( $this->mConf['class'] ) ) {
+ $this->mConf['class'] = self::class;
+ }
+ if ( empty( $this->mConf['preprocessorClass'] ) ) {
+ $this->mConf['preprocessorClass'] = self::getDefaultPreprocessorClass();
+ }
+ $this->svcOptions = new ServiceOptions( self::$constructorOptions,
+ $this->mConf,
+ func_num_args() > 6 ? func_get_arg( 6 ) : $services->getMainConfig()
+ );
+ $linkRendererFactory = func_num_args() > 7 ? func_get_arg( 7 ) : null;
+ $nsInfo = func_num_args() > 8 ? func_get_arg( 8 ) : null;
+ } else {
+ // New calling convention
+ $svcOptions->assertRequiredOptions( self::$constructorOptions );
+ // $this->mConf is public, so we'll keep those two options there as well for
+ // compatibility until it's removed
+ $this->mConf = [
+ 'class' => $svcOptions->get( 'class' ),
+ 'preprocessorClass' => $svcOptions->get( 'preprocessorClass' ),
+ ];
+ $this->svcOptions = $svcOptions;
+ }
+
$this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols();
$this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
self::EXT_LINK_ADDR .
self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
- if ( isset( $parserConf['preprocessorClass'] ) ) {
- $this->mPreprocessorClass = $parserConf['preprocessorClass'];
- } elseif ( wfIsHHVM() ) {
- # Under HHVM Preprocessor_Hash is much faster than Preprocessor_DOM
- $this->mPreprocessorClass = Preprocessor_Hash::class;
- } elseif ( extension_loaded( 'domxml' ) ) {
- # PECL extension that conflicts with the core DOM extension (T15770)
- wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
- $this->mPreprocessorClass = Preprocessor_Hash::class;
- } elseif ( extension_loaded( 'dom' ) ) {
- $this->mPreprocessorClass = Preprocessor_DOM::class;
- } else {
- $this->mPreprocessorClass = Preprocessor_Hash::class;
- }
- wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
- $services = MediaWikiServices::getInstance();
$this->magicWordFactory = $magicWordFactory ??
$services->getMagicWordFactory();
$this->factory = $factory ?? $services->getParserFactory();
$this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory();
- $this->siteConfig = $siteConfig ?? $services->getMainConfig();
- $this->linkRendererFactory =
- $linkRendererFactory ?? $services->getLinkRendererFactory();
+ $this->linkRendererFactory = $linkRendererFactory ?? $services->getLinkRendererFactory();
$this->nsInfo = $nsInfo ?? $services->getNamespaceInfo();
}
Hooks::run( 'ParserCloned', [ $this ] );
}
+ /**
+ * Which class should we use for the preprocessor if not otherwise specified?
+ *
+ * @since 1.34
+ * @return string
+ */
+ public static function getDefaultPreprocessorClass() {
+ if ( wfIsHHVM() ) {
+ # Under HHVM Preprocessor_Hash is much faster than Preprocessor_DOM
+ return Preprocessor_Hash::class;
+ }
+ if ( extension_loaded( 'domxml' ) ) {
+ # PECL extension that conflicts with the core DOM extension (T15770)
+ wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
+ return Preprocessor_Hash::class;
+ }
+ if ( extension_loaded( 'dom' ) ) {
+ return Preprocessor_DOM::class;
+ }
+ return Preprocessor_Hash::class;
+ }
+
/**
* Do various kinds of initialisation on the first call of the parser
*/
Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
$limitReport = "NewPP limit report\n";
- if ( $this->siteConfig->get( 'ShowHostnames' ) ) {
+ if ( $this->svcOptions->get( 'ShowHostnames' ) ) {
$limitReport .= 'Parsed by ' . wfHostname() . "\n";
}
$limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
$this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
// Add other cache related metadata
- if ( $this->siteConfig->get( 'ShowHostnames' ) ) {
+ if ( $this->svcOptions->get( 'ShowHostnames' ) ) {
$this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
}
$this->mOutput->setLimitReportData( 'cachereport-timestamp',
*/
public function getPreprocessor() {
if ( !isset( $this->mPreprocessor ) ) {
- $class = $this->mPreprocessorClass;
+ $class = $this->svcOptions->get( 'preprocessorClass' );
$this->mPreprocessor = new $class( $this );
}
return $this->mPreprocessor;
if (
$iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
Language::fetchLanguageName( $iw, null, 'mw' ) ||
- in_array( $iw, $this->siteConfig->get( 'ExtraInterlanguageLinkPrefixes' ) )
+ in_array( $iw, $this->svcOptions->get( 'ExtraInterlanguageLinkPrefixes' ) )
)
) {
# T26502: filter duplicates
# The vary-revision flag must be set, because the magic word
# will have a different value once the page is saved.
$this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
+ wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision" );
}
$value = $pageid ?: null;
break;
case 'revisionid':
if (
- $this->siteConfig->get( 'MiserMode' ) &&
+ $this->svcOptions->get( 'MiserMode' ) &&
!$this->mOptions->getInterfaceMessage() &&
// @TODO: disallow this word on all namespaces
$this->nsInfo->isContent( $this->mTitle->getNamespace() )
$value = '-';
} else {
$this->mOutput->setFlag( 'vary-revision-exists' );
+ wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-exists" );
$value = '';
}
} else {
# Inform the edit saving system that getting the canonical output after
# revision insertion requires another parse using the actual revision ID
$this->mOutput->setFlag( 'vary-revision-id' );
- wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
+ wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id" );
$value = $this->getRevisionId();
if ( $value === 0 ) {
$rev = $this->getRevisionObject();
$value = $this->getRevisionTimestampSubstring( 0, 4, self::MAX_TTS, $index );
break;
case 'revisiontimestamp':
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned. This is for null edits.
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
- $value = $this->getRevisionTimestamp();
+ $value = $this->getRevisionTimestampSubstring( 0, 14, self::MAX_TTS, $index );
break;
case 'revisionuser':
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned for null edits.
+ # Inform the edit saving system that getting the canonical output after
+ # revision insertion requires a parse that used the actual user ID
$this->mOutput->setFlag( 'vary-user' );
- wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
+ wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user" );
$value = $this->getRevisionUser();
break;
case 'revisionsize':
$value = SpecialVersion::getVersion();
break;
case 'articlepath':
- return $this->siteConfig->get( 'ArticlePath' );
+ return $this->svcOptions->get( 'ArticlePath' );
case 'sitename':
- return $this->siteConfig->get( 'Sitename' );
+ return $this->svcOptions->get( 'Sitename' );
case 'server':
- return $this->siteConfig->get( 'Server' );
+ return $this->svcOptions->get( 'Server' );
case 'servername':
- return $this->siteConfig->get( 'ServerName' );
+ return $this->svcOptions->get( 'ServerName' );
case 'scriptpath':
- return $this->siteConfig->get( 'ScriptPath' );
+ return $this->svcOptions->get( 'ScriptPath' );
case 'stylepath':
- return $this->siteConfig->get( 'StylePath' );
+ return $this->svcOptions->get( 'StylePath' );
case 'directionmark':
return $pageLang->getDirMark();
case 'contentlanguage':
- return $this->siteConfig->get( 'LanguageCode' );
+ return $this->svcOptions->get( 'LanguageCode' );
case 'pagelanguage':
$value = $pageLang->getCode();
break;
/**
* @param int $start
* @param int $len
- * @param int $mtts Max time-till-save; sets vary-revision if result might change by then
+ * @param int $mtts Max time-till-save; sets vary-revision-timestamp if result changes by then
* @param string $variable Parser variable name
* @return string
*/
$resNow = substr( $this->getRevisionTimestamp(), $start, $len );
# Possibly set vary-revision if there is not yet an associated revision
if ( !$this->getRevisionObject() ) {
- # Get the timezone-adjusted timestamp $mtts seconds in the future
+ # 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(
$this->contLang->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
$start,
);
if ( $resNow !== $resThen ) {
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned. This is for null edits.
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": $variable used, setting vary-revision...\n" );
+ # Inform the edit saving system that getting the canonical output after
+ # revision insertion requires a parse that used an actual revision timestamp
+ $this->mOutput->setFlag( 'vary-revision-timestamp' );
+ wfDebug( __METHOD__ . ": $variable used, setting vary-revision-timestamp" );
}
}
// If we transclude ourselves, the final result
// will change based on the new version of the page
$this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": self transclusion, setting vary-revision" );
}
}
}
* @return string
*/
public function interwikiTransclude( $title, $action ) {
- if ( !$this->siteConfig->get( 'EnableScaryTranscluding' ) ) {
+ if ( !$this->svcOptions->get( 'EnableScaryTranscluding' ) ) {
return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
}
( $wikiId !== false ) ? $wikiId : 'external',
sha1( $url )
),
- $this->siteConfig->get( 'TranscludeCacheExpiry' ),
+ $this->svcOptions->get( 'TranscludeCacheExpiry' ),
function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
$req = MWHttpRequest::factory( $url, [], $fname );
$headlines = $numMatches !== false ? $matches[3] : [];
- $maxTocLevel = $this->siteConfig->get( 'MaxTocLevel' );
+ $maxTocLevel = $this->svcOptions->get( 'MaxTocLevel' );
foreach ( $headlines as $headline ) {
$isTemplate = false;
$titleText = false;
$nickname = $nickname == null ? $username : $nickname;
- if ( mb_strlen( $nickname ) > $this->siteConfig->get( 'MaxSigChars' ) ) {
+ if ( mb_strlen( $nickname ) > $this->svcOptions->get( 'MaxSigChars' ) ) {
$nickname = $username;
wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
} elseif ( $fancySig !== false ) {
*
* The return value will be either:
* - a) Positive, indicating a specific revision ID (current or old)
- * - b) Zero, meaning the revision ID specified by getCurrentRevisionCallback()
+ * - b) Zero, meaning the revision ID is specified by getCurrentRevisionCallback()
* - c) Null, meaning the parse is for preview mode and there is no revision
*
* @return int|null
/**
* Get the timestamp associated with the current revision, adjusted for
* the default server-local timestamp
- * @return string
+ * @return string TS_MW timestamp
*/
public function getRevisionTimestamp() {
- if ( is_null( $this->mRevisionTimestamp ) ) {
- $revObject = $this->getRevisionObject();
- $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
-
- # The cryptic '' timezone parameter tells to use the site-default
- # timezone offset instead of the user settings.
- # Since this value will be saved into the parser cache, served
- # to other users, and potentially even used inside links and such,
- # it needs to be consistent for all visitors.
- $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
+ if ( $this->mRevisionTimestamp !== null ) {
+ return $this->mRevisionTimestamp;
}
+
+ # Use specified revision timestamp, falling back to the current timestamp
+ $revObject = $this->getRevisionObject();
+ $timestamp = $revObject ? $revObject->getTimestamp() : $this->mOptions->getTimestamp();
+ $this->mOutput->setRevisionTimestampUsed( $timestamp ); // unadjusted time zone
+
+ # The cryptic '' timezone parameter tells to use the site-default
+ # timezone offset instead of the user settings.
+ # Since this value will be saved into the parser cache, served
+ # to other users, and potentially even used inside links and such,
+ # it needs to be consistent for all visitors.
+ $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
+
return $this->mRevisionTimestamp;
}
}
private function makeLegacyAnchor( $sectionName ) {
- $fragmentMode = $this->siteConfig->get( 'FragmentMode' );
+ $fragmentMode = $this->svcOptions->get( 'FragmentMode' );
if ( isset( $fragmentMode[1] ) && $fragmentMode[1] === 'legacy' ) {
// ForAttribute() and ForLink() are the same for legacy encoding
$id = Sanitizer::escapeIdForAttribute( $sectionName, Sanitizer::ID_FALLBACK );