Merge "Tests: Set dbname for DatabaseSqliteTest"
[lhc/web/wiklou.git] / includes / parser / Parser.php
index 5821d3f..b643c3f 100644 (file)
@@ -26,7 +26,9 @@ use MediaWiki\Linker\LinkRendererFactory;
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
 use MediaWiki\Special\SpecialPageFactory;
+use Psr\Log\NullLogger;
 use Wikimedia\ScopedCallback;
+use Psr\Log\LoggerInterface;
 
 /**
  * @defgroup Parser Parser
@@ -294,6 +296,9 @@ class Parser {
        /** @var NamespaceInfo */
        private $nsInfo;
 
+       /** @var LoggerInterface */
+       private $logger;
+
        /**
         * TODO Make this a const when HHVM support is dropped (T192166)
         *
@@ -333,13 +338,19 @@ class Parser {
         * @param SpecialPageFactory|null $spFactory
         * @param LinkRendererFactory|null $linkRendererFactory
         * @param NamespaceInfo|null $nsInfo
+        * @param LoggerInterface|null $logger
         */
        public function __construct(
-               $svcOptions = null, MagicWordFactory $magicWordFactory = null,
-               Language $contLang = null, ParserFactory $factory = null, $urlProtocols = null,
-               SpecialPageFactory $spFactory = null, $linkRendererFactory = null, $nsInfo = null
+               $svcOptions = null,
+               MagicWordFactory $magicWordFactory = null,
+               Language $contLang = null,
+               ParserFactory $factory = null,
+               $urlProtocols = null,
+               SpecialPageFactory $spFactory = null,
+               $linkRendererFactory = null,
+               $nsInfo = null,
+               $logger = null
        ) {
-               $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.
@@ -351,8 +362,8 @@ class Parser {
                                $this->mConf['preprocessorClass'] = self::getDefaultPreprocessorClass();
                        }
                        $this->svcOptions = new ServiceOptions( self::$constructorOptions,
-                               $this->mConf,
-                               func_num_args() > 6 ? func_get_arg( 6 ) : $services->getMainConfig()
+                               $this->mConf, func_num_args() > 6
+                                       ? func_get_arg( 6 ) : MediaWikiServices::getInstance()->getMainConfig()
                        );
                        $linkRendererFactory = func_num_args() > 7 ? func_get_arg( 7 ) : null;
                        $nsInfo = func_num_args() > 8 ? func_get_arg( 8 ) : null;
@@ -374,14 +385,17 @@ class Parser {
                        self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
 
                $this->magicWordFactory = $magicWordFactory ??
-                       $services->getMagicWordFactory();
+                       MediaWikiServices::getInstance()->getMagicWordFactory();
 
-               $this->contLang = $contLang ?? $services->getContentLanguage();
+               $this->contLang = $contLang ?? MediaWikiServices::getInstance()->getContentLanguage();
 
-               $this->factory = $factory ?? $services->getParserFactory();
-               $this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory();
-               $this->linkRendererFactory = $linkRendererFactory ?? $services->getLinkRendererFactory();
-               $this->nsInfo = $nsInfo ?? $services->getNamespaceInfo();
+               $this->factory = $factory ?? MediaWikiServices::getInstance()->getParserFactory();
+               $this->specialPageFactory = $spFactory ??
+                       MediaWikiServices::getInstance()->getSpecialPageFactory();
+               $this->linkRendererFactory = $linkRendererFactory ??
+                       MediaWikiServices::getInstance()->getLinkRendererFactory();
+               $this->nsInfo = $nsInfo ?? MediaWikiServices::getInstance()->getNamespaceInfo();
+               $this->logger = $logger ?: new NullLogger();
        }
 
        /**
@@ -455,8 +469,7 @@ class Parser {
         */
        public function clearState() {
                $this->firstCallInit();
-               $this->mOutput = new ParserOutput;
-               $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
+               $this->resetOutput();
                $this->mAutonumber = 0;
                $this->mIncludeCount = [];
                $this->mLinkHolders = new LinkHolderArray( $this );
@@ -499,6 +512,14 @@ class Parser {
                Hooks::run( 'ParserClearState', [ &$parser ] );
        }
 
+       /**
+        * Reset the ParserOutput
+        */
+       public function resetOutput() {
+               $this->mOutput = new ParserOutput;
+               $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
+       }
+
        /**
         * Convert wikitext to HTML
         * Do not call this function recursively.
@@ -1164,6 +1185,15 @@ class Parser {
                return $this->mStripList;
        }
 
+       /**
+        * Get the StripState
+        *
+        * @return StripState
+        */
+       public function getStripState() {
+               return $this->mStripState;
+       }
+
        /**
         * Add an item to the strip state
         * Returns the unique tag which must be inserted into the stripped text
@@ -2770,16 +2800,16 @@ class Parser {
                                $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
                                break;
                        case 'pageid': // requested in T25427
-                               $pageid = $this->getTitle()->getArticleID();
-                               if ( $pageid == 0 ) {
-                                       # 0 means the page doesn't exist in the database,
-                                       # which means the user is previewing a new page.
-                                       # 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" );
+                               # Inform the edit saving system that getting the canonical output
+                               # after page insertion requires a parse that used that exact page ID
+                               $this->setOutputFlag( 'vary-page-id', '{{PAGEID}} used' );
+                               $value = $this->mTitle->getArticleID();
+                               if ( !$value ) {
+                                       $value = $this->mOptions->getSpeculativePageId();
+                                       if ( $value ) {
+                                               $this->mOutput->setSpeculativePageIdUsed( $value );
+                                       }
                                }
-                               $value = $pageid ?: null;
                                break;
                        case 'revisionid':
                                if (
@@ -2793,15 +2823,13 @@ class Parser {
                                        if ( $this->getRevisionId() || $this->mOptions->getSpeculativeRevId() ) {
                                                $value = '-';
                                        } else {
-                                               $this->mOutput->setFlag( 'vary-revision-exists' );
-                                               wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-exists" );
+                                               $this->setOutputFlag( 'vary-revision-exists', '{{REVISIONID}} used' );
                                                $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" );
+                                       # revision insertion requires a parse that used that exact revision ID
+                                       $this->setOutputFlag( 'vary-revision-id', '{{REVISIONID}} used' );
                                        $value = $this->getRevisionId();
                                        if ( $value === 0 ) {
                                                $rev = $this->getRevisionObject();
@@ -2836,8 +2864,7 @@ class Parser {
                        case 'revisionuser':
                                # 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" );
+                               $this->setOutputFlag( 'vary-user', '{{REVISIONUSER}} used' );
                                $value = $this->getRevisionUser();
                                break;
                        case 'revisionsize':
@@ -3007,8 +3034,7 @@ class Parser {
                        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
-                               $this->mOutput->setFlag( 'vary-revision-timestamp' );
-                               wfDebug( __METHOD__ . ": $variable used, setting vary-revision-timestamp" );
+                               $this->setOutputFlag( 'vary-revision-timestamp', "$variable used" );
                        }
                }
 
@@ -3105,8 +3131,10 @@ class Parser {
                if ( $frame === false ) {
                        $frame = $this->getPreprocessor()->newFrame();
                } elseif ( !( $frame instanceof PPFrame ) ) {
-                       wfDebug( __METHOD__ . " called using plain parameters instead of "
-                               . "a PPFrame instance. Creating custom frame.\n" );
+                       $this->logger->debug(
+                               __METHOD__ . " called using plain parameters instead of " .
+                               "a PPFrame instance. Creating custom frame."
+                       );
                        $frame = $this->getPreprocessor()->newCustomFrame( $frame );
                }
 
@@ -3407,8 +3435,10 @@ class Parser {
                                        }
                                } elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
                                        $found = false; # access denied
-                                       wfDebug( __METHOD__ . ": template inclusion denied for " .
-                                               $title->getPrefixedDBkey() . "\n" );
+                                       $this->logger->debug(
+                                               __METHOD__ .
+                                               ": template inclusion denied for " . $title->getPrefixedDBkey()
+                                       );
                                } else {
                                        list( $text, $title ) = $this->getTemplateDom( $title );
                                        if ( $text !== false ) {
@@ -3446,7 +3476,7 @@ class Parser {
                                $this->addTrackingCategory( 'template-loop-category' );
                                $this->mOutput->addWarning( wfMessage( 'template-loop-warning',
                                        wfEscapeWikiText( $titleText ) )->text() );
-                               wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
+                               $this->logger->debug( __METHOD__ . ": template loop broken at '$titleText'" );
                        }
                }
 
@@ -3729,22 +3759,22 @@ class Parser {
                // Defaults to Parser::statelessFetchTemplate()
                $templateCb = $this->mOptions->getTemplateCallback();
                $stuff = call_user_func( $templateCb, $title, $this );
-               // We use U+007F DELETE to distinguish strip markers from regular text.
+               $rev = $stuff['revision'] ?? null;
                $text = $stuff['text'];
                if ( is_string( $stuff['text'] ) ) {
+                       // We use U+007F DELETE to distinguish strip markers from regular text
                        $text = strtr( $text, "\x7f", "?" );
                }
                $finalTitle = $stuff['finalTitle'] ?? $title;
-               if ( isset( $stuff['deps'] ) ) {
-                       foreach ( $stuff['deps'] as $dep ) {
-                               $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
-                               if ( $dep['title']->equals( $this->getTitle() ) ) {
-                                       // Self-transclusion; final result may change based on the new page version
-                                       $this->mOutput->setFlag( 'vary-revision' );
-                                       wfDebug( __METHOD__ . ": self transclusion, setting vary-revision" );
-                               }
+               foreach ( ( $stuff['deps'] ?? [] ) as $dep ) {
+                       $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
+                       if ( $dep['title']->equals( $this->getTitle() ) && $rev instanceof Revision ) {
+                               // Self-transclusion; final result may change based on the new page version
+                               $this->setOutputFlag( 'vary-revision-sha1', 'Self transclusion' );
+                               $this->getOutput()->setRevisionUsedSha1Base36( $rev->getSha1() );
                        }
                }
+
                return [ $text, $finalTitle ];
        }
 
@@ -3770,6 +3800,7 @@ class Parser {
                $text = $skip = false;
                $finalTitle = $title;
                $deps = [];
+               $rev = null;
 
                # Loop to fetch the article, with up to 1 redirect
                for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
@@ -3805,13 +3836,15 @@ class Parser {
                        $deps[] = [
                                'title' => $title,
                                'page_id' => $title->getArticleID(),
-                               'rev_id' => $rev_id ];
+                               'rev_id' => $rev_id
+                       ];
                        if ( $rev && !$title->equals( $rev->getTitle() ) ) {
                                # We fetched a rev from a different title; register it too...
                                $deps[] = [
                                        'title' => $rev->getTitle(),
                                        'page_id' => $rev->getPage(),
-                                       'rev_id' => $rev_id ];
+                                       'rev_id' => $rev_id
+                               ];
                        }
 
                        if ( $rev ) {
@@ -3845,9 +3878,11 @@ class Parser {
                        $title = $content->getRedirectTarget();
                }
                return [
+                       'revision' => $rev,
                        'text' => $text,
                        'finalTitle' => $finalTitle,
-                       'deps' => $deps ];
+                       'deps' => $deps
+               ];
        }
 
        /**
@@ -4685,7 +4720,7 @@ class Parser {
                                '~~~' => $sigText
                        ] );
                        # The main two signature forms used above are time-sensitive
-                       $this->mOutput->setFlag( 'user-signature' );
+                       $this->setOutputFlag( 'user-signature', 'User signature detected' );
                }
 
                # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
@@ -4750,7 +4785,7 @@ class Parser {
 
                if ( mb_strlen( $nickname ) > $this->svcOptions->get( 'MaxSigChars' ) ) {
                        $nickname = $username;
-                       wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
+                       $this->logger->debug( __METHOD__ . ": $username has overlong signature." );
                } elseif ( $fancySig !== false ) {
                        # Sig. might contain markup; validate this
                        if ( $this->validateSig( $nickname ) !== false ) {
@@ -4759,7 +4794,7 @@ class Parser {
                        } else {
                                # Failed to validate; fall back to the default
                                $nickname = $username;
-                               wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
+                               $this->logger->debug( __METHOD__ . ": $username has bad XML tags in signature." );
                        }
                }
 
@@ -4845,11 +4880,15 @@ class Parser {
         * @param ParserOptions $options
         * @param int $outputType
         * @param bool $clearState
+        * @param int|null $revId
         */
        public function startExternalParse( Title $title = null, ParserOptions $options,
-               $outputType, $clearState = true
+               $outputType, $clearState = true, $revId = null
        ) {
                $this->startParse( $title, $options, $outputType, $clearState );
+               if ( $revId !== null ) {
+                       $this->mRevisionId = $revId;
+               }
        }
 
        /**
@@ -5256,7 +5295,8 @@ class Parser {
                                                                        $handlerOptions[$paramName] = $match;
                                                                } else {
                                                                        // Guess not, consider it as caption.
-                                                                       wfDebug( "$parameterMatch failed parameter validation\n" );
+                                                                       $this->logger->debug(
+                                                                               "$parameterMatch failed parameter validation" );
                                                                        $label = $parameterMatch;
                                                                }
                                                }
@@ -5642,7 +5682,7 @@ class Parser {
         * @deprecated since 1.28; use getOutput()->updateCacheExpiry()
         */
        public function disableCache() {
-               wfDebug( "Parser output marked as uncacheable.\n" );
+               $this->logger->debug( "Parser output marked as uncacheable." );
                if ( !$this->mOutput ) {
                        throw new MWException( __METHOD__ .
                                " can only be called when actually parsing something" );
@@ -5922,19 +5962,21 @@ class Parser {
         * @since 1.23 (public since 1.23)
         */
        public function getRevisionObject() {
-               if ( !is_null( $this->mRevisionObject ) ) {
+               if ( $this->mRevisionObject ) {
                        return $this->mRevisionObject;
                }
 
                // NOTE: try to get the RevisionObject even if mRevisionId is null.
-               // This is useful when parsing revision that has not yet been saved.
+               // This is useful when parsing revision that has not yet been saved.
                // However, if we get back a saved revision even though we are in
                // preview mode, we'll have to ignore it, see below.
                // NOTE: This callback may be used to inject an OLD revision that was
                // already loaded, so "current" is a bit of a misnomer. We can't just
                // skip it if mRevisionId is set.
                $rev = call_user_func(
-                       $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
+                       $this->mOptions->getCurrentRevisionCallback(),
+                       $this->getTitle(),
+                       $this
                );
 
                if ( $this->mRevisionId === null && $rev && $rev->getId() ) {
@@ -6448,4 +6490,14 @@ class Parser {
                OutputPage::setupOOUI();
                $this->mOutput->setEnableOOUI( true );
        }
+
+       /**
+        * @param string $flag
+        * @param string $reason
+        */
+       protected function setOutputFlag( $flag, $reason ) {
+               $this->mOutput->setFlag( $flag );
+               $name = $this->mTitle->getPrefixedText();
+               $this->logger->debug( __METHOD__ . ": set $flag flag on '$name'; $reason" );
+       }
 }