* Added WikiPage::getParserOutput() and changed Article::getParserOutput() to use it
authorAlexandre Emsenhuber <ialex@users.mediawiki.org>
Thu, 17 Nov 2011 20:21:54 +0000 (20:21 +0000)
committerAlexandre Emsenhuber <ialex@users.mediawiki.org>
Thu, 17 Nov 2011 20:21:54 +0000 (20:21 +0000)
* WikiPage::getParserOutput() requires a ParserOptions object (and optionally the revision ID) instead of an User object, removes an hidden dependency on $wgLang. For this reason, WikiPage::isParserCacheUsed() now also uses a ParserOptions object instead of an User object (doesn't change anything in the code except the variable name and it's not called in extensions)
* Moved PoolWorkArticleView to WikiPage.php and added an entry in the AutoLoader and moved output-related stuff directly in Article::view() so that in can be shared with WikiPage::getParserOutput() (removes code duplication, etc.)
* Added the revision ID to the PoolCounter key so that it knows which revision is being parsed and doesn't wait for another parse operation with same options but different revisions
* Removed Article::doViewParse(), Article::tryDirtyCache() and Article::getOutputFromWikitext() since they are now integrated in PoolWorkArticleView and Article::view() and there are no callers in extensions. This also fixes a bug since Article::doViewParse() will get another ParserOptions instance with special options set in Article::view() not be repercuted.
* Updated DifferenceEngine to use the new system
* Updated docs/memcached.txt to correct method names

docs/memcached.txt
includes/Article.php
includes/AutoLoader.php
includes/WikiPage.php
includes/diff/DifferenceEngine.php

index f4d93c3..f5384f9 100644 (file)
@@ -159,7 +159,7 @@ Parser Cache:
                $hash: hash of user options applied to the page, see ParserOptions::optionsHash()
        ex: wikidb:pcache:idhash:1-0!1!0!!en!2
        stores: ParserOutput object
-       modified by: Article::editUpdates() or Article::getOutputFromWikitext()
+       modified by: WikiPage::doEditUpdates() or PoolWorkArticleView::doWork()
        expiry: $wgParserCacheExpireTime or less if it contains short lived functions
 
        key: $wgDBname:pcache:idoptions:$pageid
index 7c359fb..0b5f7d0 100644 (file)
@@ -453,7 +453,7 @@ class Article extends Page {
                }
 
                # Should the parser cache be used?
-               $useParserCache = $this->mPage->isParserCacheUsed( $wgUser, $oldid );
+               $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
                wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
                if ( $wgUser->getStubThreshold() ) {
                        wfIncrStats( 'pcache_miss_stub' );
@@ -550,16 +550,34 @@ class Article extends Page {
                                        # Run the parse, protected by a pool counter
                                        wfDebug( __METHOD__ . ": doing uncached parse\n" );
 
-                                       $key = $parserCache->getKey( $this, $parserOptions );
-                                       $poolArticleView = new PoolWorkArticleView( $this, $key, $useParserCache, $parserOptions );
+                                       $poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
+                                               $this->getRevIdFetched(), $useParserCache, $this->getContent() );
 
                                        if ( !$poolArticleView->execute() ) {
+                                               $error = $poolArticleView->getError();
+                                               if ( $error ) {
+                                                       $wgOut->clearHTML(); // for release() errors
+                                                       $wgOut->enableClientCache( false );
+                                                       $wgOut->setRobotPolicy( 'noindex,nofollow' );
+
+                                                       $errortext = $error->getWikiText( false, 'view-pool-error' );
+                                                       $wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
+                                               }
                                                # Connection or timeout error
                                                wfProfileOut( __METHOD__ );
                                                return;
-                                       } else {
-                                               $outputDone = true;
                                        }
+
+                                       $this->mParserOutput = $poolArticleView->getParserOutput();
+                                       $wgOut->addParserOutput( $this->mParserOutput );
+
+                                       # Don't cache a dirty ParserOutput object
+                                       if ( $poolArticleView->getIsDirty() ) {
+                                               $wgOut->setSquidMaxage( 0 );
+                                               $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
+                                       }
+
+                                       $outputDone = true;
                                        break;
                                # Should be unreachable, but just in case...
                                default:
@@ -1149,67 +1167,6 @@ class Article extends Page {
                        $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>" );
        }
 
-       /**
-        * Execute the uncached parse for action=view
-        * @return bool
-        */
-       public function doViewParse() {
-               global $wgOut;
-
-               $oldid = $this->getOldID();
-               $parserOptions = $this->getParserOptions();
-
-               # Render printable version, use printable version cache
-               $parserOptions->setIsPrintable( $wgOut->isPrintable() );
-
-               # Don't show section-edit links on old revisions... this way lies madness.
-               if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
-                       $parserOptions->setEditSection( false );
-               }
-
-               $useParserCache = $this->useParserCache( $oldid );
-               $this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions );
-
-               return true;
-       }
-
-       /**
-        * Try to fetch an expired entry from the parser cache. If it is present,
-        * output it and return true. If it is not present, output nothing and
-        * return false. This is used as a callback function for
-        * PoolCounter::executeProtected().
-        *
-        * @return boolean
-        */
-       public function tryDirtyCache() {
-               global $wgOut;
-               $parserCache = ParserCache::singleton();
-               $options = $this->getParserOptions();
-
-               if ( $wgOut->isPrintable() ) {
-                       $options->setIsPrintable( true );
-                       $options->setEditSection( false );
-               }
-
-               $output = $parserCache->getDirty( $this, $options );
-
-               if ( $output ) {
-                       wfDebug( __METHOD__ . ": sending dirty output\n" );
-                       wfDebugLog( 'dirty', "dirty output " . $parserCache->getKey( $this, $options ) . "\n" );
-                       $wgOut->setSquidMaxage( 0 );
-                       $this->mParserOutput = $output;
-                       $wgOut->addParserOutput( $output );
-                       $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
-
-                       return true;
-               } else {
-                       wfDebugLog( 'dirty', "dirty missing\n" );
-                       wfDebug( __METHOD__ . ": no dirty cache\n" );
-
-                       return false;
-               }
-       }
-
        /**
         * View redirect
         *
@@ -1641,25 +1598,6 @@ class Article extends Page {
 
        /**#@-*/
 
-       /**
-        * Add the primary page-view wikitext to the output buffer
-        * Saves the text into the parser cache if possible.
-        * Updates templatelinks if it is out of date.
-        *
-        * @param $text String
-        * @param $cache Boolean
-        * @param $parserOptions mixed ParserOptions object, or boolean false
-        */
-       public function outputWikiText( $text, $cache = true, $parserOptions = false ) {
-               global $wgOut;
-
-               $this->mParserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions );
-
-               $this->doCascadeProtectionUpdates( $this->mParserOutput );
-
-               $wgOut->addParserOutput( $this->mParserOutput );
-       }
-
        /**
         * Lightweight method to get the parser output for a page, checking the parser cache
         * and so on. Doesn't consider most of the stuff that WikiPage::view is forced to
@@ -1672,93 +1610,12 @@ class Article extends Page {
         * @return ParserOutput or false if the given revsion ID is not found
         */
        public function getParserOutput( $oldid = null, User $user = null ) {
-               global $wgEnableParserCache, $wgUser;
-               $user = is_null( $user ) ? $wgUser : $user;
-
-               wfProfileIn( __METHOD__ );
-               // Should the parser cache be used?
-               $useParserCache = $wgEnableParserCache &&
-                       $user->getStubThreshold() == 0 &&
-                       $this->mPage->exists() &&
-                       $oldid === null;
-
-               wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
-
-               if ( $user->getStubThreshold() ) {
-                       wfIncrStats( 'pcache_miss_stub' );
-               }
-
-               if ( $useParserCache ) {
-                       $options = $this->mPage->makeParserOptions( $user );
-                       $parserOutput = ParserCache::singleton()->get( $this, $options );
-                       if ( $parserOutput !== false ) {
-                               wfProfileOut( __METHOD__ );
-                               return $parserOutput;
-                       }
-               }
-
-               // Cache miss; parse and output it.
-               if ( $oldid === null ) {
-                       $text = $this->mPage->getRawText();
-               } else {
-                       $rev = Revision::newFromTitle( $this->getTitle(), $oldid );
-                       if ( $rev === null ) {
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-                       $text = $rev->getText();
-               }
-
-               $output = $this->getOutputFromWikitext( $text, $useParserCache );
-               wfProfileOut( __METHOD__ );
-               return $output;
-       }
-
-       /**
-        * This does all the heavy lifting for outputWikitext, except it returns the parser
-        * output instead of sending it straight to $wgOut. Makes things nice and simple for,
-        * say, embedding thread pages within a discussion system (LiquidThreads)
-        *
-        * @param $text string
-        * @param $cache boolean
-        * @param $parserOptions parsing options, defaults to false
-        * @return ParserOutput
-        */
-       public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) {
-               global $wgParser, $wgEnableParserCache, $wgUseFileCache;
-
-               if ( !$parserOptions ) {
-                       $parserOptions = $this->getParserOptions();
-               }
-
-               $time = - wfTime();
-               $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(),
-                       $parserOptions, true, true, $this->getRevIdFetched() );
-               $time += wfTime();
-
-               # Timing hack
-               if ( $time > 3 ) {
-                       wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
-                               $this->getTitle()->getPrefixedDBkey() ) );
-               }
-
-               if ( $wgEnableParserCache && $cache && $this->mParserOutput->isCacheable() ) {
-                       $parserCache = ParserCache::singleton();
-                       $parserCache->save( $this->mParserOutput, $this, $parserOptions );
-               }
-
-               // Make sure file cache is not used on uncacheable content.
-               // Output that has magic words in it can still use the parser cache
-               // (if enabled), though it will generally expire sooner.
-               if ( !$this->mParserOutput->isCacheable() || $this->mParserOutput->containsOldMagic() ) {
-                       $wgUseFileCache = false;
-               }
+               global $wgUser;
 
-               if ( $this->isCurrent() ) {
-                       $this->mPage->doCascadeProtectionUpdates( $this->mParserOutput );
-               }
+               $user = is_null( $user ) ? $wgUser : $user;
+               $parserOptions = $this->mPage->makeParserOptions( $user );
 
-               return $this->mParserOutput;
+               return $this->mPage->getParserOutput( $parserOptions, $oldid );
        }
 
        /**
@@ -2063,68 +1920,3 @@ class Article extends Page {
        }
        // ******
 }
-
-class PoolWorkArticleView extends PoolCounterWork {
-
-       /**
-        * @var Article
-        */
-       private $mArticle;
-
-       function __construct( $article, $key, $useParserCache, $parserOptions ) {
-               parent::__construct( 'ArticleView', $key );
-               $this->mArticle = $article;
-               $this->cacheable = $useParserCache;
-               $this->parserOptions = $parserOptions;
-       }
-
-       /**
-        * @return bool
-        */
-       function doWork() {
-               return $this->mArticle->doViewParse();
-       }
-
-       /**
-        * @return bool
-        */
-       function getCachedWork() {
-               global $wgOut;
-
-               $parserCache = ParserCache::singleton();
-               $this->mArticle->mParserOutput = $parserCache->get( $this->mArticle, $this->parserOptions );
-
-               if ( $this->mArticle->mParserOutput !== false ) {
-                       wfDebug( __METHOD__ . ": showing contents parsed by someone else\n" );
-                       $wgOut->addParserOutput( $this->mArticle->mParserOutput );
-                       # Ensure that UI elements requiring revision ID have
-                       # the correct version information.
-                       $wgOut->setRevisionId( $this->mArticle->getLatest() );
-                       return true;
-               }
-               return false;
-       }
-
-       /**
-        * @return bool
-        */
-       function fallback() {
-               return $this->mArticle->tryDirtyCache();
-       }
-
-       /**
-        * @param $status Status
-        */
-       function error( $status ) {
-               global $wgOut;
-
-               $wgOut->clearHTML(); // for release() errors
-               $wgOut->enableClientCache( false );
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-
-               $errortext = $status->getWikiText( false, 'view-pool-error' );
-               $wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
-
-               return false;
-       }
-}
index 6594831..fc4467d 100644 (file)
@@ -168,6 +168,7 @@ $wgAutoloadLocalClasses = array(
        'PoolCounter' => 'includes/PoolCounter.php',
        'PoolCounter_Stub' => 'includes/PoolCounter.php',
        'PoolCounterWork' => 'includes/PoolCounter.php',
+       'PoolWorkArticleView' => 'includes/WikiPage.php',
        'Preferences' => 'includes/Preferences.php',
        'PreferencesForm' => 'includes/Preferences.php',
        'PrefixSearch' => 'includes/PrefixSearch.php',
index 3f0183e..1de6c17 100644 (file)
@@ -708,20 +708,61 @@ class WikiPage extends Page {
        /**
         * Should the parser cache be used?
         *
-        * @param $user User The relevant user
+        * @param $parserOptions ParserOptions to check
         * @param $oldid int
         * @return boolean
         */
-       public function isParserCacheUsed( User $user, $oldid ) {
+       public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
                global $wgEnableParserCache;
 
                return $wgEnableParserCache
-                       && $user->getStubThreshold() == 0
+                       && $parserOptions->getStubThreshold() == 0
                        && $this->exists()
                        && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
                        && $this->mTitle->isWikitextPage();
        }
 
+       /**
+        * Get a ParserOutput for the given ParserOptions and revision ID.
+        * The the parser cache will be used if possible.
+        *
+        * @since 1.19
+        * @param $parserOptions ParserOptions to use for the parse operation
+        * @param $oldid Revision ID to get the text from, passing null or 0 will
+        *               get the current revision (default value)
+        * @return ParserOutput or false if the revision was not found
+        */
+       public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
+               global $wgParser;
+
+               wfProfileIn( __METHOD__ );
+
+               $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
+               wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
+               if ( $parserOptions->getStubThreshold() ) {
+                       wfIncrStats( 'pcache_miss_stub' );
+               }
+
+               if ( $useParserCache ) {
+                       $parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
+                       if ( $parserOutput !== false ) {
+                               wfProfileOut( __METHOD__ );
+                               return $parserOutput;
+                       }
+               }
+
+               if ( $oldid === null || $oldid === 0 ) {
+                       $oldid = $this->getLatest();
+               }
+
+               $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
+               $pool->execute();
+
+               wfProfileOut( __METHOD__ );
+
+               return $pool->getParserOutput();
+       }
+
        /**
         * Perform the actions of a page purging
         */
@@ -2671,6 +2712,183 @@ class WikiPage extends Page {
         */
        public function useParserCache( $oldid ) {
                global $wgUser;
-               return $this->isParserCacheUsed( $wgUser, $oldid );
+               return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
+       }
+}
+
+class PoolWorkArticleView extends PoolCounterWork {
+
+       /**
+        * @var Page
+        */
+       private $page;
+
+       /**
+        * @var string
+        */
+       private $cacheKey;
+
+       /**
+        * @var integer
+        */
+       private $revid;
+
+       /**
+        * @var ParserOptions
+        */
+       private $parserOptions;
+
+       /**
+        * @var string|null
+        */
+       private $text;
+
+       /**
+        * @var ParserOutput|false
+        */
+       private $parserOutput = false;
+
+       /**
+        * @var bool
+        */
+       private $isDirty = false;
+
+       /**
+        * @var Status|false
+        */
+       private $error = false;
+
+       /**
+        * Constructor
+        *
+        * @param $page Page
+        * @param $revid Integer: ID of the revision being parsed
+        * @param $useParserCache Boolean: whether to use the parser cache
+        * @param $parserOptions parserOptions to use for the parse operation
+        * @param $text String: text to parse or null to load it
+        */
+       function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
+               $this->page = $page;
+               $this->revid = $revid;
+               $this->cacheable = $useParserCache;
+               $this->parserOptions = $parserOptions;
+               $this->text = $text;
+               $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
+               parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
+       }
+
+       /**
+        * Get the ParserOutput from this object, or false in case of failure
+        *
+        * @return ParserOutput
+        */
+       public function getParserOutput() {
+               return $this->parserOutput;
+       }
+
+       /**
+        * Get whether the ParserOutput is a dirty one (i.e. expired)
+        *
+        * @return bool
+        */
+       public function getIsDirty() {
+               return $this->isDirty();
+       }
+
+       /**
+        * Get a Status object in case of error or false otherwise
+        *
+        * @return Status|false
+        */
+       public function getError() {
+               return $this->error;
+       }
+
+       /**
+        * @return bool
+        */
+       function doWork() {
+               global $wgParser, $wgUseFileCache;
+
+               $isCurrent = $this->revid === $this->page->getLatest();
+
+               if ( $this->text !== null ) {
+                       $text = $this->text;
+               } elseif ( $isCurrent ) {
+                       $text = $this->page->getRawText();
+               } else {
+                       $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
+                       if ( $rev === null ) {
+                               return false;
+                       }
+                       $text = $rev->getText();
+               }
+
+               $time = - wfTime();
+               $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
+                       $this->parserOptions, true, true, $this->revid );
+               $time += wfTime();
+
+               # Timing hack
+               if ( $time > 3 ) {
+                       wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
+                               $this->page->getTitle()->getPrefixedDBkey() ) );
+               }
+
+               if ( $this->cacheable && $this->parserOutput->isCacheable() ) {
+                       ParserCache::singleton()->save( $this->parserOutput, $this->page, $this->parserOptions );
+               }
+
+               // Make sure file cache is not used on uncacheable content.
+               // Output that has magic words in it can still use the parser cache
+               // (if enabled), though it will generally expire sooner.
+               if ( !$this->parserOutput->isCacheable() || $this->parserOutput->containsOldMagic() ) {
+                       $wgUseFileCache = false;
+               }
+
+               if ( $isCurrent ) {
+                       $this->page->doCascadeProtectionUpdates( $this->parserOutput );
+               }
+       }
+
+       /**
+        * @return bool
+        */
+       function getCachedWork() {
+               $this->parserOutput = ParserCache::singleton()->get( $this->page, $this->parserOptions );
+
+               if ( $this->parserOutput === false ) {
+                       wfDebug( __METHOD__ . ": parser cache miss\n" );
+                       return false;
+               } else {
+                       wfDebug( __METHOD__ . ": parser cache hit\n" );
+                       return true;
+               }
+       }
+
+       /**
+        * @return bool
+        */
+       function fallback() {
+               $this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions );
+
+               if ( $this->parserOutput === false ) {
+                       wfDebugLog( 'dirty', "dirty missing\n" );
+                       wfDebug( __METHOD__ . ": no dirty cache\n" );
+                       return false;
+               } else {
+                       wfDebug( __METHOD__ . ": sending dirty output\n" );
+                       wfDebugLog( 'dirty', "dirty output {$this->cacheKey}\n" );
+                       $this->isDirty = true;
+                       return true;
+               }
+       }
+
+       /**
+        * @param $status Status
+        */
+       function error( $status ) {
+               $this->error = $status;
+               return false;
        }
 }
index 08bc392..a57d4b0 100644 (file)
@@ -516,9 +516,8 @@ class DifferenceEngine extends ContextSource {
                        } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
                                // Handled by extension
                        } else {
-                               # Use the current version parser cache if applicable
+                               // Normal page
                                $wikiPage = WikiPage::factory( $this->mNewPage );
-                               $useParserCache = $wikiPage->isParserCacheUsed( $this->getUser(), $this->mNewid );
 
                                $parserOptions = ParserOptions::newFromContext( $this->getContext() );
                                $parserOptions->enableLimitReport();
@@ -528,15 +527,11 @@ class DifferenceEngine extends ContextSource {
                                        $parserOptions->setEditSection( false );
                                }
 
-                               $parserOutput = false;
-                               if ( $useParserCache ) {
-                                       $parserOutput = ParserCache::singleton()->get( $wikiPage, $parserOptions );
-                               }
+                               $parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
 
+                               # WikiPage::getParserOutput() should not return false, but just in case
                                if( $parserOutput ) {
                                        $out->addParserOutput( $parserOutput );
-                               } else {
-                                       $out->addWikiTextTidy( $this->mNewtext );
                                }
                        }
                }