Merge "parser: inject the time for {{REVISIONTIMESTAMP}} on pre-save parse"
[lhc/web/wiklou.git] / includes / parser / Parser.php
index c28d842..4808caf 100644 (file)
  * @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;
@@ -168,8 +170,15 @@ class Parser {
         * @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 */
@@ -227,12 +236,6 @@ class Parser {
        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
@@ -274,8 +277,14 @@ class Parser {
        /** @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;
@@ -284,45 +293,84 @@ class Parser {
        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();
 
@@ -330,9 +378,7 @@ class Parser {
 
                $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();
        }
 
@@ -371,6 +417,17 @@ class Parser {
                Hooks::run( 'ParserCloned', [ $this ] );
        }
 
+       /**
+        * Which class should we use for the preprocessor if not otherwise specified?
+        *
+        * @since 1.34
+        * @deprecated since 1.34, removing configurability of preprocessor
+        * @return string
+        */
+       public static function getDefaultPreprocessorClass() {
+               return Preprocessor_Hash::class;
+       }
+
        /**
         * Do various kinds of initialisation on the first call of the parser
         */
@@ -602,7 +659,7 @@ class 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";
@@ -610,6 +667,7 @@ class Parser {
                $limitReport .= 'Dynamic content: ' .
                        ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
                        "\n";
+               $limitReport .= 'Complications: [' . implode( ', ', $this->mOutput->getAllFlags() ) . "]\n";
 
                foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
                        if ( Hooks::run( 'ParserLimitReportFormat',
@@ -653,7 +711,7 @@ class Parser {
                $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',
@@ -974,7 +1032,7 @@ class Parser {
         */
        public function getPreprocessor() {
                if ( !isset( $this->mPreprocessor ) ) {
-                       $class = $this->mPreprocessorClass;
+                       $class = $this->svcOptions->get( 'preprocessorClass' );
                        $this->mPreprocessor = new $class( $this );
                }
                return $this->mPreprocessor;
@@ -1980,7 +2038,7 @@ class Parser {
         * @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 ) {
@@ -2391,7 +2449,7 @@ class Parser {
                                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
@@ -2589,8 +2647,9 @@ class Parser {
                 * 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];
                }
@@ -2598,24 +2657,6 @@ class Parser {
                $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() )
-               ) {
-                       if ( $this->mRevisionId || $this->mOptions->getSpeculativeRevId() ) {
-                               return '-';
-                       } else {
-                               $this->mOutput->setFlag( 'vary-revision-exists' );
-
-                               return '';
-                       }
-               };
-
                $pageLang = $this->getFunctionLang();
 
                switch ( $index ) {
@@ -2734,28 +2775,41 @@ class Parser {
                                        # 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':
-                               # 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' );
+                                               wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting 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" );
+                                       $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;
@@ -2775,17 +2829,13 @@ class Parser {
                                $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':
@@ -2892,21 +2942,21 @@ class Parser {
                                $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;
@@ -2933,7 +2983,7 @@ class Parser {
        /**
         * @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
         */
@@ -2942,7 +2992,10 @@ class Parser {
                $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,
@@ -2950,10 +3003,10 @@ class Parser {
                        );
 
                        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" );
                        }
                }
 
@@ -3675,6 +3728,7 @@ class Parser {
                                        // 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" );
                                }
                        }
                }
@@ -3783,19 +3837,6 @@ class Parser {
                        'deps' => $deps ];
        }
 
-       /**
-        * Fetch a file and its title and register a reference to it.
-        * If 'broken' is a key in $options then the file will appear as a broken thumbnail.
-        * @param Title $title
-        * @param array $options Array of options to RepoGroup::findFile
-        * @return File|bool
-        * @deprecated since 1.32, use fetchFileAndTitle instead
-        */
-       public function fetchFile( $title, $options = [] ) {
-               wfDeprecated( __METHOD__, '1.32' );
-               return $this->fetchFileAndTitle( $title, $options )[0];
-       }
-
        /**
         * Fetch a file and its title and register a reference to it.
         * If 'broken' is a key in $options then the file will appear as a broken thumbnail.
@@ -3834,7 +3875,7 @@ class Parser {
                } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
                        $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
                } else { // get by (name,timestamp)
-                       $file = wfFindFile( $title, $options );
+                       $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $title, $options );
                }
                return $file;
        }
@@ -3848,7 +3889,7 @@ class Parser {
         * @return string
         */
        public function interwikiTransclude( $title, $action ) {
-               if ( !$this->siteConfig->get( 'EnableScaryTranscluding' ) ) {
+               if ( !$this->svcOptions->get( 'EnableScaryTranscluding' ) ) {
                        return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
                }
 
@@ -3868,7 +3909,7 @@ class Parser {
                                ( $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 );
 
@@ -4240,7 +4281,7 @@ class Parser {
 
                $headlines = $numMatches !== false ? $matches[3] : [];
 
-               $maxTocLevel = $this->siteConfig->get( 'MaxTocLevel' );
+               $maxTocLevel = $this->svcOptions->get( 'MaxTocLevel' );
                foreach ( $headlines as $headline ) {
                        $isTemplate = false;
                        $titleText = false;
@@ -4672,7 +4713,7 @@ class Parser {
         * 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
@@ -4694,7 +4735,7 @@ class Parser {
 
                $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 ) {
@@ -5850,6 +5891,11 @@ class Parser {
        /**
         * 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 is specified by getCurrentRevisionCallback()
+        *   - c) Null, meaning the parse is for preview mode and there is no revision
+        *
         * @return int|null
         */
        public function getRevisionId() {
@@ -5902,20 +5948,25 @@ class Parser {
        /**
         * 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;
        }
 
@@ -6010,7 +6061,7 @@ class Parser {
        }
 
        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 );
@@ -6359,9 +6410,9 @@ class Parser {
        /**
         * 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