Merge "Clean up handling of 'infinity'"
[lhc/web/wiklou.git] / includes / page / WikiPage.php
index f26b966..8754fb9 100644 (file)
@@ -31,8 +31,6 @@ interface Page {
  *
  * Some fields are public only for backwards-compatibility. Use accessors.
  * In the past, this class was part of Article.php and everything was public.
- *
- * @internal documentation reviewed 15 Mar 2010
  */
 class WikiPage implements Page, IDBAccessObject {
        // Constants for $mDataLoadedFrom and related
@@ -369,14 +367,12 @@ class WikiPage implements Page, IDBAccessObject {
                        $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
                } elseif ( $from === self::READ_NORMAL ) {
                        $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
-                       // Use a "last rev inserted" timestamp key to diminish the issue of slave lag.
-                       // Note that DB also stores the master position in the session and checks it.
-                       $touched = $this->getCachedLastEditTime();
-                       if ( $touched ) { // key set
-                               if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
-                                       $from = self::READ_LATEST;
-                                       $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
-                               }
+                       if ( !$data
+                               && wfGetLB()->getServerCount() > 1
+                               && wfGetLB()->hasOrMadeRecentMasterChanges()
+                       ) {
+                               $from = self::READ_LATEST;
+                               $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
                        }
                } else {
                        // No idea from where the caller got this data, assume slave database.
@@ -604,13 +600,23 @@ class WikiPage implements Page, IDBAccessObject {
                        return; // page doesn't exist or is missing page_latest info
                }
 
-               // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the
-               // latest changes committed. This is true even within REPEATABLE-READ transactions, where
-               // S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to
-               // also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row
-               // UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT.
-               // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
-               $flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0;
+               if ( $this->mDataLoadedFrom == self::READ_LOCKING ) {
+                       // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always
+                       // includes the latest changes committed. This is true even within REPEATABLE-READ
+                       // transactions, where S1 normally only sees changes committed before the first S1
+                       // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
+                       // may not find it since a page row UPDATE and revision row INSERT by S2 may have
+                       // happened after the first S1 SELECT.
+                       // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
+                       $flags = Revision::READ_LOCKING;
+               } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
+                       // Bug T93976: if page_latest was loaded from the master, fetch the
+                       // revision from there as well, as it may not exist yet on a slave DB.
+                       // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
+                       $flags = Revision::READ_LATEST;
+               } else {
+                       $flags = 0;
+               }
                $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
                if ( $revision ) { // sanity
                        $this->setLastEdit( $revision );
@@ -802,29 +808,6 @@ class WikiPage implements Page, IDBAccessObject {
                }
        }
 
-       /**
-        * Get the cached timestamp for the last time the page changed.
-        * This is only used to help handle slave lag by comparing to page_touched.
-        * @return string MW timestamp
-        */
-       protected function getCachedLastEditTime() {
-               global $wgMemc;
-               $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
-               return $wgMemc->get( $key );
-       }
-
-       /**
-        * Set the cached timestamp for the last time the page changed.
-        * This is only used to help handle slave lag by comparing to page_touched.
-        * @param string $timestamp
-        * @return void
-        */
-       public function setCachedLastEditTime( $timestamp ) {
-               global $wgMemc;
-               $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
-               $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60 * 15 );
-       }
-
        /**
         * Determine whether a page would be suitable for being counted as an
         * article in the site_stats table based on the title & its content
@@ -1167,28 +1150,24 @@ class WikiPage implements Page, IDBAccessObject {
         * @return bool
         */
        public function doPurge() {
-               global $wgUseSquid;
-
                if ( !Hooks::run( 'ArticlePurge', array( &$this ) ) ) {
                        return false;
                }
 
-               // Invalidate the cache
-               $this->mTitle->invalidateCache();
-
-               if ( $wgUseSquid ) {
-                       // Commit the transaction before the purge is sent
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->commit( __METHOD__ );
-
-                       // Send purge
-                       $update = SquidUpdate::newSimplePurge( $this->mTitle );
-                       $update->doUpdate();
-               }
+               $title = $this->mTitle;
+               wfGetDB( DB_MASTER )->onTransactionIdle( function() use ( $title ) {
+                       global $wgUseSquid;
+                       // Invalidate the cache in auto-commit mode
+                       $title->invalidateCache();
+                       if ( $wgUseSquid ) {
+                               // Send purge now that page_touched update was committed above
+                               $update = SquidUpdate::newSimplePurge( $title );
+                               $update->doUpdate();
+                       }
+               } );
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
                        // @todo move this logic to MessageCache
-
                        if ( $this->exists() ) {
                                // NOTE: use transclusion text for messages.
                                //       This is consistent with  MessageCache::getMsgFromNamespace()
@@ -1205,6 +1184,7 @@ class WikiPage implements Page, IDBAccessObject {
 
                        MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
                }
+
                return true;
        }
 
@@ -1263,6 +1243,13 @@ class WikiPage implements Page, IDBAccessObject {
        ) {
                global $wgContentHandlerUseDB;
 
+               // Assertion to try to catch T92046
+               if ( (int)$revision->getId() === 0 ) {
+                       throw new InvalidArgumentException(
+                               __METHOD__ . ': Revision has ID ' . var_export( $revision->getId(), 1 )
+                       );
+               }
+
                $content = $revision->getContent();
                $len = $content ? $content->getSize() : 0;
                $rt = $content ? $content->getUltimateRedirectTarget() : null;
@@ -1296,7 +1283,6 @@ class WikiPage implements Page, IDBAccessObject {
                if ( $result ) {
                        $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
                        $this->setLastEdit( $revision );
-                       $this->setCachedLastEditTime( $now );
                        $this->mLatest = $revision->getId();
                        $this->mIsRedirect = (bool)$rt;
                        // Update the LinkCache.
@@ -1498,8 +1484,18 @@ class WikiPage implements Page, IDBAccessObject {
 
                $baseRevId = null;
                if ( $edittime && $sectionId !== 'new' ) {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
+                       $dbr = wfGetDB( DB_SLAVE );
+                       $rev = Revision::loadFromTimestamp( $dbr, $this->mTitle, $edittime );
+                       // Try the master if this thread may have just added it.
+                       // This could be abstracted into a Revision method, but we don't want
+                       // to encourage loading of revisions by timestamp.
+                       if ( !$rev
+                               && wfGetLB()->getServerCount() > 1
+                               && wfGetLB()->hasOrMadeRecentMasterChanges()
+                       ) {
+                               $dbw = wfGetDB( DB_MASTER );
+                               $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
+                       }
                        if ( $rev ) {
                                $baseRevId = $rev->getId();
                        }
@@ -1538,10 +1534,7 @@ class WikiPage implements Page, IDBAccessObject {
                        if ( is_null( $baseRevId ) || $sectionId === 'new' ) {
                                $oldContent = $this->getContent();
                        } else {
-                               // TODO: try DB_SLAVE first
-                               $dbw = wfGetDB( DB_MASTER );
-                               $rev = Revision::loadFromId( $dbw, $baseRevId );
-
+                               $rev = Revision::newFromId( $baseRevId );
                                if ( !$rev ) {
                                        wfDebug( __METHOD__ . " asked for bogus section (page: " .
                                                $this->getId() . "; section: $sectionId)\n" );
@@ -1608,7 +1601,9 @@ class WikiPage implements Page, IDBAccessObject {
         * error will be returned. These two conditions are also possible with
         * auto-detection due to MediaWiki's performance-optimised locking strategy.
         *
-        * @param bool|int $baseRevId The revision ID this edit was based off, if any
+        * @param bool|int $baseRevId The revision ID this edit was based off, if any.
+        *   This is not the parent revision ID, rather the revision ID for older
+        *   content used as the source for a rollback, for example.
         * @param User $user The user doing the edit
         *
         * @throws MWException
@@ -1668,7 +1663,9 @@ class WikiPage implements Page, IDBAccessObject {
         * error will be returned. These two conditions are also possible with
         * auto-detection due to MediaWiki's performance-optimised locking strategy.
         *
-        * @param bool|int $baseRevId The revision ID this edit was based off, if any
+        * @param bool|int $baseRevId The revision ID this edit was based off, if any.
+        *   This is not the parent revision ID, rather the revision ID for older
+        *   content used as the source for a rollback, for example.
         * @param User $user The user doing the edit
         * @param string $serialFormat Format for storing the content in the
         *   database.
@@ -1802,7 +1799,7 @@ class WikiPage implements Page, IDBAccessObject {
                                $dbw->begin( __METHOD__ );
                                try {
 
-                                       $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+                                       $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
                                        $status->merge( $prepStatus );
 
                                        if ( !$status->isOK() ) {
@@ -1881,7 +1878,7 @@ class WikiPage implements Page, IDBAccessObject {
                        $dbw->begin( __METHOD__ );
                        try {
 
-                               $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+                               $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
                                $status->merge( $prepStatus );
 
                                if ( !$status->isOK() ) {
@@ -1980,6 +1977,7 @@ class WikiPage implements Page, IDBAccessObject {
                // Promote user to any groups they meet the criteria for
                $dbw->onTransactionIdle( function () use ( $user ) {
                        $user->addAutopromoteOnceGroups( 'onEdit' );
+                       $user->addAutopromoteOnceGroups( 'onView' ); // b/c
                } );
 
                return $status;
@@ -2077,7 +2075,7 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                // The edit may have already been prepared via api.php?action=stashedit
-               $cachedEdit = $useCache && $wgAjaxEditStash
+               $cachedEdit = $useCache && $wgAjaxEditStash && !$user->isAllowed( 'bot' )
                        ? ApiStashEdit::checkCache( $this->getTitle(), $content, $user )
                        : false;
 
@@ -3059,7 +3057,7 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                // Generate the edit summary if necessary
-               $target = Revision::newFromId( $s->rev_id );
+               $target = Revision::newFromId( $s->rev_id, Revision::READ_LATEST );
                if ( empty( $summary ) ) {
                        if ( $from == '' ) { // no public user name
                                $summary = wfMessage( 'revertpage-nouser' );
@@ -3379,12 +3377,15 @@ class WikiPage implements Page, IDBAccessObject {
         * Opportunistically enqueue link update jobs given fresh parser output if useful
         *
         * @param ParserOutput $parserOutput Current version page output
-        * @return bool Whether a job was pushed
         * @since 1.25
         */
        public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
                if ( wfReadOnly() ) {
-                       return false;
+                       return;
+               }
+
+               if ( !Hooks::run( 'OpportunisticLinksUpdate', array( $this, $this->mTitle, $parserOutput ) ) ) {
+                       return;
                }
 
                if ( $this->mTitle->areRestrictionsCascading() ) {
@@ -3395,7 +3396,7 @@ class WikiPage implements Page, IDBAccessObject {
                        $params = array();
                } else {
                        // If the inclusions are deterministic, the edit-triggered link jobs are enough
-                       return false;
+                       return;
                }
 
                // Check if the last link refresh was before page_touched
@@ -3403,10 +3404,10 @@ class WikiPage implements Page, IDBAccessObject {
                        JobQueueGroup::singleton()->push( EnqueueJob::newFromLocalJobs(
                                new JobSpecification( 'refreshLinks', $params, array(), $this->mTitle )
                        ) );
-                       return true;
+                       return;
                }
 
-               return false;
+               return;
        }
 
        /**