* @file
* @ingroup Parser
*/
+use MediaWiki\Config\ServiceOptions;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\Linker\LinkRendererFactory;
+use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
use MediaWiki\Special\SpecialPageFactory;
use Wikimedia\ScopedCallback;
* @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 */
public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
public $mInputSize = false; # For {{PAGESIZE}} on current page.
- /**
- * @var string Deprecated accessor for the strip marker prefix.
- * @deprecated since 1.26; use Parser::MARKER_PREFIX instead.
- */
- public $mUniqPrefix = self::MARKER_PREFIX;
-
/**
* @var array Array with the language name of each language link (i.e. the
* interwiki prefix) in the key, value arbitrary. Used to avoid sending
/** @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;
* @since 1.21
* @param string|bool $url Optional URL, to extract the domain from for rel =>
* nofollow if appropriate
- * @param Title|null $title Optional Title, for wgNoFollowNsExceptions lookups
+ * @param LinkTarget|null $title Optional LinkTarget, for wgNoFollowNsExceptions lookups
* @return string|null Rel attribute for $url
*/
public static function getExternalLinkRel( $url = false, $title = null ) {
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
* Some of these require message or data lookups and can be
* expensive to check many times.
*/
- if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] )
- && isset( $this->mVarCache[$index] )
+ if (
+ Hooks::run( 'ParserGetVariableValueVarCache', [ &$parser, &$this->mVarCache ] ) &&
+ isset( $this->mVarCache[$index] )
) {
return $this->mVarCache[$index];
}
$ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
- // In miser mode, disable words that always cause double-parses on page save (T137900)
- static $slowRevWords = [ 'revisionid' => true ]; // @TODO: 'revisiontimestamp'
- if (
- isset( $slowRevWords[$index] ) &&
- $this->siteConfig->get( 'MiserMode' ) &&
- !$this->mOptions->getInterfaceMessage() &&
- // @TODO: disallow this word on all namespaces
- $this->nsInfo->isContent( $this->mTitle->getNamespace() )
- ) {
- return $this->mRevisionId ? '-' : '';
- };
-
$pageLang = $this->getFunctionLang();
switch ( $index ) {
$value = $pageid ?: null;
break;
case 'revisionid':
- # Let the edit saving system know we should parse the page
- # *after* a revision ID has been assigned.
- $this->mOutput->setFlag( 'vary-revision-id' );
- wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
- $value = $this->mRevisionId;
-
- if ( !$value ) {
- $rev = $this->getRevisionObject();
- if ( $rev ) {
- $value = $rev->getId();
+ if (
+ $this->svcOptions->get( 'MiserMode' ) &&
+ !$this->mOptions->getInterfaceMessage() &&
+ // @TODO: disallow this word on all namespaces
+ $this->nsInfo->isContent( $this->mTitle->getNamespace() )
+ ) {
+ // 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 ( $this->getRevisionId() || $this->mOptions->getSpeculativeRevId() ) {
+ $value = '-';
+ } else {
+ $this->mOutput->setFlag( 'vary-revision-exists' );
+ $value = '';
}
- }
-
- if ( !$value ) {
- $value = $this->mOptions->getSpeculativeRevId();
- if ( $value ) {
- $this->mOutput->setSpeculativeRevIdUsed( $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" );
+ $value = $this->getRevisionId();
+ if ( $value === 0 ) {
+ $rev = $this->getRevisionObject();
+ $value = $rev ? $rev->getId() : $value;
+ }
+ if ( !$value ) {
+ $value = $this->mOptions->getSpeculativeRevId();
+ if ( $value ) {
+ $this->mOutput->setSpeculativeRevIdUsed( $value );
+ }
}
}
break;
$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;
* @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;
* If you have pre-fetched the nickname or the fancySig option, you can
* specify them here to save a database query.
* Do not reuse this parser instance after calling getUserSig(),
- * as it may have changed if it's the $wgParser.
+ * as it may have changed.
*
* @param User &$user
* @param string|bool $nickname Nickname to use or false to use user's default nickname
$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 ) {
/**
* Get the ID of the revision we are parsing
*
+ * 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()
+ * - c) Null, meaning the parse is for preview mode and there is no revision
+ *
* @return int|null
*/
public function getRevisionId() {
}
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 );
/**
* Return this parser if it is not doing anything, otherwise
* get a fresh parser. You can use this method by doing
- * $myParser = $wgParser->getFreshParser(), or more simply
- * $wgParser->getFreshParser()->parse( ... );
- * if you're unsure if $wgParser is safe to use.
+ * $newParser = $oldParser->getFreshParser(), or more simply
+ * $oldParser->getFreshParser()->parse( ... );
+ * if you're unsure if $oldParser is safe to use.
*
* @since 1.24
* @return Parser A parser object that is not parsing anything