Moved view count from WikiPage to Title; avoids an extra DB query when showing the...
[lhc/web/wiklou.git] / includes / WikiPage.php
index 3acabf5..e5dbb0a 100644 (file)
@@ -21,7 +21,6 @@ class WikiPage extends Page {
        /**@{{
         * @protected
         */
-       public $mCounter = -1;               // !< Integer (-1 means "not loaded")
        public $mDataLoaded = false;         // !< Boolean
        public $mIsRedirect = false;         // !< Boolean
        public $mLatest = false;             // !< Integer (false means "not loaded")
@@ -240,7 +239,6 @@ class WikiPage extends Page {
        public function clear() {
                $this->mDataLoaded = false;
 
-               $this->mCounter = -1; # Not loaded
                $this->mRedirectTarget = null; # Title object if set
                $this->mLastRevision = null; # Latest revision
                $this->mTimestamp = '';
@@ -380,7 +378,6 @@ class WikiPage extends Page {
                        # Old-fashioned restrictions
                        $this->mTitle->loadRestrictions( $data->page_restrictions );
 
-                       $this->mCounter     = intval( $data->page_counter );
                        $this->mTouched     = wfTimestamp( TS_MW, $data->page_touched );
                        $this->mIsRedirect  = intval( $data->page_is_redirect );
                        $this->mLatest      = intval( $data->page_latest );
@@ -404,7 +401,7 @@ class WikiPage extends Page {
         * @return bool Whether or not the page exists in the database
         */
        public function exists() {
-               return $this->getId() > 0;
+               return $this->mTitle->exists();
        }
 
        /**
@@ -416,29 +413,16 @@ class WikiPage extends Page {
         * @return bool
         */
        public function hasViewableContent() {
-               return $this->exists() || $this->mTitle->isAlwaysKnown();
+               return $this->mTitle->exists() || $this->mTitle->isAlwaysKnown();
        }
 
        /**
+        * Get the number of views of this page
+        *
         * @return int The view count for the page
         */
        public function getCount() {
-               if ( -1 == $this->mCounter ) {
-                       $id = $this->getId();
-
-                       if ( $id == 0 ) {
-                               $this->mCounter = 0;
-                       } else {
-                               $dbr = wfGetDB( DB_SLAVE );
-                               $this->mCounter = $dbr->selectField( 'page',
-                                       'page_counter',
-                                       array( 'page_id' => $id ),
-                                       __METHOD__
-                               );
-                       }
-               }
-
-               return $this->mCounter;
+               return $this->mTitle->getCount();
        }
 
        /**
@@ -708,20 +692,59 @@ 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
-                       && $this->exists()
+                       && $parserOptions->getStubThreshold() == 0
+                       && $this->mTitle->exists()
                        && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
                        && $this->mTitle->isWikitextPage();
        }
 
+       /**
+        * Get a ParserOutput for the given ParserOptions and revision ID.
+        * 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 ) {
+               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
         */
@@ -747,14 +770,15 @@ class WikiPage extends Page {
                }
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-                       if ( $this->getId() == 0 ) {
-                               $text = false;
-                       } else {
+                       if ( $this->mTitle->exists() ) {
                                $text = $this->getRawText();
+                       } else {
+                               $text = false;
                        }
 
                        MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
                }
+               return true;
        }
 
        /**
@@ -1609,7 +1633,7 @@ class WikiPage extends Page {
        public function doDeleteArticle(
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
-               global $wgUseTrackbacks, $wgUser;
+               global $wgUser;
                $user = is_null( $user ) ? $wgUser : $user;
 
                wfDebug( __METHOD__ . "\n" );
@@ -1702,10 +1726,6 @@ class WikiPage extends Page {
                if ( !$dbw->cascadingDeletes() ) {
                        $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
 
-                       if ( $wgUseTrackbacks ) {
-                               $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
-                       }
-
                        # Delete outgoing links
                        $dbw->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
                        $dbw->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
@@ -1949,7 +1969,7 @@ class WikiPage extends Page {
                }
 
                # Don't update page view counters on views from bot users (bug 14044)
-               if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->getId() ) {
+               if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->mTitle->exists() ) {
                        DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
                        DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
                }
@@ -1982,7 +2002,18 @@ class WikiPage extends Page {
                $edit->newText = $text;
                $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
                $edit->popts = $this->makeParserOptions( 'canonical' );
-               $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
+
+               // Bug 32858: For a CSS/JS page, put a blank parser output into the 
+               // prepared edit, so that links etc. won't be registered in the 
+               // database. We could have set $edit->output to false or something if 
+               // we thought of this bug earlier, but now that would break the 
+               // interface with AbuseFilter etc.
+               if ( $this->mTitle->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
+                       $input = '';
+               } else {
+                       $input = $edit->pst;
+               }
+               $edit->output = $wgParser->parse( $input, $this->mTitle, $edit->popts, true, true, $revid );
                $edit->oldText = $this->getRawText();
 
                $this->mPreparedEdit = $edit;
@@ -2053,15 +2084,15 @@ class WikiPage extends Page {
                        }
                }
 
-               $id = $this->getId();
-               $title = $this->mTitle->getPrefixedDBkey();
-               $shortTitle = $this->mTitle->getDBkey();
-
-               if ( 0 == $id ) {
+               if ( !$this->mTitle->exists() ) {
                        wfProfileOut( __METHOD__ );
                        return;
                }
 
+               $id = $this->getId();
+               $title = $this->mTitle->getPrefixedDBkey();
+               $shortTitle = $this->mTitle->getDBkey();
+
                if ( !$options['changed'] ) {
                        $good = 0;
                        $total = 0;
@@ -2280,7 +2311,9 @@ class WikiPage extends Page {
                # User talk pages
                if ( $title->getNamespace() == NS_USER_TALK ) {
                        $user = User::newFromName( $title->getText(), false );
-                       $user->setNewtalk( false );
+                       if ( $user ) {
+                               $user->setNewtalk( false );
+                       }
                }
 
                # Image redirects
@@ -2670,6 +2703,185 @@ 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 true;
+       }
+
+       /**
+        * @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;
        }
 }