X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fparser%2FParser.php;h=04c9c9a649af9ebbc01f86c71c82de31de9152dc;hb=e85fe191c91a282fa1d466997a346782644a8870;hp=c28d842af2498aa6b50c4e80f5fac753b52240b6;hpb=4d75fbf3571c765a3d84140cb77b45a86aab3184;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index c28d842af2..04c9c9a649 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -20,8 +20,10 @@ * @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,28 @@ class Parser { 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 */ @@ -602,7 +670,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"; @@ -653,7 +721,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 +1042,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 +2048,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 +2459,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 +2657,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 +2667,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 +2785,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 +2839,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 +2952,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 +2993,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 +3002,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 +3013,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 +3738,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" ); } } } @@ -3848,7 +3912,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 +3932,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 +4304,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 +4736,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 +4758,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 +5914,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 +5971,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 +6084,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 +6433,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