diff options
Diffstat (limited to 'includes/page/WikiPage.php')
-rw-r--r-- | includes/page/WikiPage.php | 417 |
1 files changed, 215 insertions, 202 deletions
diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php index 7c789249..d1cec60b 100644 --- a/includes/page/WikiPage.php +++ b/includes/page/WikiPage.php @@ -361,18 +361,18 @@ class WikiPage implements Page, IDBAccessObject { return; } - if ( $from === self::READ_LOCKING ) { - $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle, array( 'FOR UPDATE' ) ); - } elseif ( $from === self::READ_LATEST ) { - $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle ); - } elseif ( $from === self::READ_NORMAL ) { - $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle ); + if ( is_int( $from ) ) { + list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from ); + $data = $this->pageDataFromTitle( wfGetDB( $index ), $this->mTitle, $opts ); + if ( !$data + && $index == DB_SLAVE && wfGetLB()->getServerCount() > 1 && wfGetLB()->hasOrMadeRecentMasterChanges() ) { $from = self::READ_LATEST; - $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle ); + list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from ); + $data = $this->pageDataFromTitle( wfGetDB( $index ), $this->mTitle, $opts ); } } else { // No idea from where the caller got this data, assume slave database. @@ -1082,16 +1082,13 @@ class WikiPage implements Page, IDBAccessObject { * Should the parser cache be used? * * @param ParserOptions $parserOptions ParserOptions to check - * @param int $oldid + * @param int $oldId * @return bool */ - public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) { - global $wgEnableParserCache; - - return $wgEnableParserCache - && $parserOptions->getStubThreshold() == 0 + public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) { + return $parserOptions->getStubThreshold() == 0 && $this->exists() - && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() ) + && ( $oldId === null || $oldId === 0 || $oldId === $this->getLatest() ) && $this->getContentHandler()->isParserCacheSupported(); } @@ -1108,10 +1105,10 @@ class WikiPage implements Page, IDBAccessObject { */ public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) { - $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid ); + $useParserCache = $this->shouldCheckParserCache( $parserOptions, $oldid ); wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" ); if ( $parserOptions->getStubThreshold() ) { - wfIncrStats( 'pcache_miss_stub' ); + wfIncrStats( 'pcache.miss.stub' ); } if ( $useParserCache ) { @@ -1143,7 +1140,12 @@ class WikiPage implements Page, IDBAccessObject { Hooks::run( 'PageViewUpdates', array( $this, $user ) ); // Update newtalk / watchlist notification status - $user->clearNotification( $this->mTitle, $oldid ); + try { + $user->clearNotification( $this->mTitle, $oldid ); + } catch ( DBError $e ) { + // Avoid outage if the master is not reachable + MWExceptionHandler::logException( $e ); + } } /** @@ -1151,28 +1153,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() @@ -1189,6 +1187,7 @@ class WikiPage implements Page, IDBAccessObject { MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text ); } + return true; } @@ -1200,10 +1199,9 @@ class WikiPage implements Page, IDBAccessObject { * Best if all done inside a transaction. * * @param DatabaseBase $dbw - * @return int The newly created page_id key, or false if the title already existed + * @return int|bool The newly created page_id key; false if the title already existed */ public function insertOn( $dbw ) { - $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' ); $dbw->insert( 'page', array( 'page_id' => $page_id, @@ -1224,9 +1222,11 @@ class WikiPage implements Page, IDBAccessObject { $newid = $dbw->insertId(); $this->mId = $newid; $this->mTitle->resetArticleID( $newid ); - } - return $affected ? $newid : false; + return $newid; + } else { + return false; + } } /** @@ -1265,10 +1265,9 @@ class WikiPage implements Page, IDBAccessObject { $conditions['page_latest'] = $lastRevision; } - $now = wfTimestampNow(); $row = array( /* SET */ 'page_latest' => $revision->getId(), - 'page_touched' => $dbw->timestamp( $now ), + 'page_touched' => $dbw->timestamp( $revision->getTimestamp() ), 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0, 'page_is_redirect' => $rt !== null ? 1 : 0, 'page_len' => $len, @@ -1690,6 +1689,7 @@ class WikiPage implements Page, IDBAccessObject { * revision: The revision object for the inserted revision, or null. * * @since 1.21 + * @throws MWException */ public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false, User $user = null, $serialFormat = null @@ -1765,7 +1765,6 @@ class WikiPage implements Page, IDBAccessObject { $dbw = wfGetDB( DB_MASTER ); $now = wfTimestampNow(); - $this->mTimestamp = $now; if ( $flags & EDIT_UPDATE ) { // Update article, but only if changed. @@ -1801,57 +1800,50 @@ class WikiPage implements Page, IDBAccessObject { if ( $changed ) { $dbw->begin( __METHOD__ ); - try { - $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user ); - $status->merge( $prepStatus ); + $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user ); + $status->merge( $prepStatus ); - if ( !$status->isOK() ) { - $dbw->rollback( __METHOD__ ); + if ( !$status->isOK() ) { + $dbw->rollback( __METHOD__ ); - return $status; - } - $revisionId = $revision->insertOn( $dbw ); + return $status; + } + $revisionId = $revision->insertOn( $dbw ); - // Update page - // - // We check for conflicts by comparing $oldid with the current latest revision ID. - $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect ); + // Update page + // + // We check for conflicts by comparing $oldid with the current latest revision ID. + $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect ); - if ( !$ok ) { - // Belated edit conflict! Run away!! - $status->fatal( 'edit-conflict' ); + if ( !$ok ) { + // Belated edit conflict! Run away!! + $status->fatal( 'edit-conflict' ); - $dbw->rollback( __METHOD__ ); + $dbw->rollback( __METHOD__ ); - return $status; - } + return $status; + } - Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) ); - // Update recentchanges - if ( !( $flags & EDIT_SUPPRESS_RC ) ) { - // Mark as patrolled if the user can do so - $patrolled = $wgUseRCPatrol && !count( - $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); - // Add RC row to the DB - $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary, - $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize, - $revisionId, $patrolled - ); + Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) ); - // Log auto-patrolled edits - if ( $patrolled ) { - PatrolLog::record( $rc, true, $user ); - } - } - $user->incEditCount(); - } catch ( Exception $e ) { - $dbw->rollback( __METHOD__ ); - // Question: Would it perhaps be better if this method turned all - // exceptions into $status's? - throw $e; + // Update recentchanges + if ( !( $flags & EDIT_SUPPRESS_RC ) ) { + // Mark as patrolled if the user can do so + $patrolled = $wgUseRCPatrol && !count( + $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); + // Add RC row to the DB + RecentChange::notifyEdit( + $now, $this->mTitle, $isminor, $user, $summary, + $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize, + $revisionId, $patrolled + ); } + + $user->incEditCount(); + $dbw->commit( __METHOD__ ); + $this->mTimestamp = $now; } else { // Bug 32948: revision ID must be set to page {{REVISIONID}} and // related variables correctly @@ -1873,86 +1865,80 @@ class WikiPage implements Page, IDBAccessObject { $revision = null; // Update page_touched, this is usually implicit in the page update // Other cache updates are done in onArticleEdit() - $this->mTitle->invalidateCache(); + $this->mTitle->invalidateCache( $now ); } } else { // Create new article $status->value['new'] = true; $dbw->begin( __METHOD__ ); - try { - $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user ); - $status->merge( $prepStatus ); + $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user ); + $status->merge( $prepStatus ); - if ( !$status->isOK() ) { - $dbw->rollback( __METHOD__ ); + if ( !$status->isOK() ) { + $dbw->rollback( __METHOD__ ); - return $status; - } + return $status; + } - $status->merge( $prepStatus ); + $status->merge( $prepStatus ); - // Add the page record; stake our claim on this title! - // This will return false if the article already exists - $newid = $this->insertOn( $dbw ); + // Add the page record; stake our claim on this title! + // This will return false if the article already exists + $newid = $this->insertOn( $dbw ); - if ( $newid === false ) { - $dbw->rollback( __METHOD__ ); - $status->fatal( 'edit-already-exists' ); + if ( $newid === false ) { + $dbw->rollback( __METHOD__ ); + $status->fatal( 'edit-already-exists' ); - return $status; - } + return $status; + } - // Save the revision text... - $revision = new Revision( array( - 'page' => $newid, - 'title' => $this->getTitle(), // for determining the default content model - 'comment' => $summary, - 'minor_edit' => $isminor, - 'text' => $serialized, - 'len' => $newsize, - 'user' => $user->getId(), - 'user_text' => $user->getName(), - 'timestamp' => $now, - 'content_model' => $content->getModel(), - 'content_format' => $serialFormat, - ) ); - $revisionId = $revision->insertOn( $dbw ); + // Save the revision text... + $revision = new Revision( array( + 'page' => $newid, + 'title' => $this->getTitle(), // for determining the default content model + 'comment' => $summary, + 'minor_edit' => $isminor, + 'text' => $serialized, + 'len' => $newsize, + 'user' => $user->getId(), + 'user_text' => $user->getName(), + 'timestamp' => $now, + 'content_model' => $content->getModel(), + 'content_format' => $serialFormat, + ) ); + $revisionId = $revision->insertOn( $dbw ); - // Bug 37225: use accessor to get the text as Revision may trim it - $content = $revision->getContent(); // sanity; get normalized version + // Bug 37225: use accessor to get the text as Revision may trim it + $content = $revision->getContent(); // sanity; get normalized version - if ( $content ) { - $newsize = $content->getSize(); - } + if ( $content ) { + $newsize = $content->getSize(); + } - // Update the page record with revision data - $this->updateRevisionOn( $dbw, $revision, 0 ); + // Update the page record with revision data + $this->updateRevisionOn( $dbw, $revision, 0 ); - Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) ); + Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) ); - // Update recentchanges - if ( !( $flags & EDIT_SUPPRESS_RC ) ) { - // Mark as patrolled if the user can do so - $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count( - $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); - // Add RC row to the DB - $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot, - '', $newsize, $revisionId, $patrolled ); + // Update recentchanges + if ( !( $flags & EDIT_SUPPRESS_RC ) ) { + // Mark as patrolled if the user can do so + $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count( + $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) ); + // Add RC row to the DB + RecentChange::notifyNew( + $now, $this->mTitle, $isminor, $user, $summary, $bot, + '', $newsize, $revisionId, $patrolled + ); + } - // Log auto-patrolled edits - if ( $patrolled ) { - PatrolLog::record( $rc, true, $user ); - } - } - $user->incEditCount(); + $user->incEditCount(); - } catch ( Exception $e ) { - $dbw->rollback( __METHOD__ ); - throw $e; - } $dbw->commit( __METHOD__ ); + $this->mTimestamp = $now; // Update links, etc. $this->doEditUpdates( $revision, $user, array( 'created' => true ) ); @@ -1973,13 +1959,13 @@ class WikiPage implements Page, IDBAccessObject { $status->value['revision'] = $revision; $hook_args = array( &$this, &$user, $content, $summary, - $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ); + $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ); ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args ); Hooks::run( 'PageContentSaveComplete', $hook_args ); // Promote user to any groups they meet the criteria for - $dbw->onTransactionIdle( function () use ( $user ) { + DeferredUpdates::addCallableUpdate( function () use ( $user ) { $user->addAutopromoteOnceGroups( 'onEdit' ); $user->addAutopromoteOnceGroups( 'onView' ); // b/c } ); @@ -2079,7 +2065,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; @@ -2164,8 +2150,6 @@ class WikiPage implements Page, IDBAccessObject { * - 'no-change': don't update the article count, ever */ public function doEditUpdates( Revision $revision, User $user, array $options = array() ) { - global $wgEnableParserCache; - $options += array( 'changed' => true, 'created' => false, @@ -2185,31 +2169,30 @@ class WikiPage implements Page, IDBAccessObject { $editInfo = $this->mPreparedEdit; } - // Save it to the parser cache - if ( $wgEnableParserCache ) { - $parserCache = ParserCache::singleton(); - $parserCache->save( - $editInfo->output, $this, $editInfo->popts, $editInfo->timestamp, $editInfo->revid - ); - } + // Save it to the parser cache. + // Make sure the cache time matches page_touched to avoid double parsing. + ParserCache::singleton()->save( + $editInfo->output, $this, $editInfo->popts, + $revision->getTimestamp(), $editInfo->revid + ); // Update the links tables and other secondary data if ( $content ) { $recursive = $options['changed']; // bug 50785 $updates = $content->getSecondaryDataUpdates( $this->getTitle(), null, $recursive, $editInfo->output ); - DataUpdate::runUpdates( $updates ); + foreach ( $updates as $update ) { + DeferredUpdates::addUpdate( $update ); + } } Hooks::run( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) ); if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) { - JobQueueGroup::singleton()->push( array( - // Flush old entries from the `recentchanges` table - RecentChangesUpdateJob::newPurgeJob(), - // Update the cached list of active users - RecentChangesUpdateJob::newCacheUpdateJob() - ) ); + // Flush old entries from the `recentchanges` table + if ( mt_rand( 0, 9 ) == 0 ) { + JobQueueGroup::singleton()->lazyPush( RecentChangesUpdateJob::newPurgeJob() ); + } } if ( !$this->exists() ) { @@ -2276,9 +2259,8 @@ class WikiPage implements Page, IDBAccessObject { if ( $options['created'] ) { self::onArticleCreate( $this->mTitle ); } elseif ( $options['changed'] ) { // bug 50785 - self::onArticleEdit( $this->mTitle ); + self::onArticleEdit( $this->mTitle, $revision ); } - } /** @@ -2375,8 +2357,8 @@ class WikiPage implements Page, IDBAccessObject { $dbw = wfGetDB( DB_MASTER ); foreach ( $restrictionTypes as $action ) { - if ( !isset( $expiry[$action] ) ) { - $expiry[$action] = $dbw->getInfinity(); + if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) { + $expiry[$action] = 'infinity'; } if ( !isset( $limit[$action] ) ) { $limit[$action] = ''; @@ -2616,10 +2598,8 @@ class WikiPage implements Page, IDBAccessObject { */ protected function formatExpiry( $expiry ) { global $wgContLang; - $dbr = wfGetDB( DB_SLAVE ); - $encodedExpiry = $dbr->encodeExpiry( $expiry ); - if ( $encodedExpiry != 'infinity' ) { + if ( $expiry != 'infinity' ) { return wfMessage( 'protect-expiring', $wgContLang->timeanddate( $expiry, false, false ), @@ -2784,9 +2764,16 @@ class WikiPage implements Page, IDBAccessObject { $dbw->begin( __METHOD__ ); if ( $id == 0 ) { - $this->loadPageData( 'forupdate' ); + // T98706: lock the page from various other updates but avoid using + // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to + // the revisions queries (which also JOIN on user). Only lock the page + // row and CAS check on page_latest to see if the trx snapshot matches. + $latest = $this->lock(); + + $this->loadPageData( WikiPage::READ_LATEST ); $id = $this->getID(); - if ( $id == 0 ) { + if ( $id == 0 || $this->getLatest() != $latest ) { + // Page not there or trx snapshot is stale $dbw->rollback( __METHOD__ ); $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) ); return $status; @@ -2868,7 +2855,7 @@ class WikiPage implements Page, IDBAccessObject { // Clone the title, so we have the information we need when we log $logTitle = clone $this->mTitle; - // Log the deletion, if the page was suppressed, log it at Oversight instead + // Log the deletion, if the page was suppressed, put it in the suppression log instead $logtype = $suppress ? 'suppress' : 'delete'; $logEntry = new ManualLogEntry( $logtype, 'delete' ); @@ -2886,6 +2873,10 @@ class WikiPage implements Page, IDBAccessObject { $dbw->commit( __METHOD__ ); } + // Show log excerpt on 404 pages rather than just a link + $key = wfMemcKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) ); + ObjectCache::getMainStashInstance()->set( $key, 1, 86400 ); + $this->doDeleteUpdates( $id, $content ); Hooks::run( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) ); @@ -2894,6 +2885,24 @@ class WikiPage implements Page, IDBAccessObject { } /** + * Lock the page row for this title and return page_latest (or 0) + * + * @return integer + */ + protected function lock() { + return (int)wfGetDB( DB_MASTER )->selectField( + 'page', + 'page_latest', + array( + 'page_namespace' => $this->getTitle()->getNamespace(), + 'page_title' => $this->getTitle()->getDBkey() + ), + __METHOD__, + array( 'FOR UPDATE' ) + ); + } + + /** * Do some database updates after deletion * * @param int $id The page_id value of the page being deleted @@ -3021,7 +3030,7 @@ class WikiPage implements Page, IDBAccessObject { ) ); } - // Get the last edit not by this guy... + // Get the last edit not by this person... // Note: these may not be public values $user = intval( $current->getUser( Revision::RAW ) ); $user_text = $dbw->addQuotes( $current->getUserText( Revision::RAW ) ); @@ -3043,29 +3052,6 @@ class WikiPage implements Page, IDBAccessObject { return array( array( 'notvisiblerev' ) ); } - // Set patrolling and bot flag on the edits, which gets rollbacked. - // This is done before the rollback edit to have patrolling also on failure (bug 62157). - $set = array(); - if ( $bot && $guser->isAllowed( 'markbotedits' ) ) { - // Mark all reverted edits as bot - $set['rc_bot'] = 1; - } - - if ( $wgUseRCPatrol ) { - // Mark all reverted edits as patrolled - $set['rc_patrolled'] = 1; - } - - if ( count( $set ) ) { - $dbw->update( 'recentchanges', $set, - array( /* WHERE */ - 'rc_cur_id' => $current->getPage(), - 'rc_user_text' => $current->getUserText(), - 'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ), - ), __METHOD__ - ); - } - // Generate the edit summary if necessary $target = Revision::newFromId( $s->rev_id, Revision::READ_LATEST ); if ( empty( $summary ) ) { @@ -3114,6 +3100,30 @@ class WikiPage implements Page, IDBAccessObject { $guser ); + // Set patrolling and bot flag on the edits, which gets rollbacked. + // This is done even on edit failure to have patrolling in that case (bug 62157). + $set = array(); + if ( $bot && $guser->isAllowed( 'markbotedits' ) ) { + // Mark all reverted edits as bot + $set['rc_bot'] = 1; + } + + if ( $wgUseRCPatrol ) { + // Mark all reverted edits as patrolled + $set['rc_patrolled'] = 1; + } + + if ( count( $set ) ) { + $dbw->update( 'recentchanges', $set, + array( /* WHERE */ + 'rc_cur_id' => $current->getPage(), + 'rc_user_text' => $current->getUserText(), + 'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ), + ), + __METHOD__ + ); + } + if ( !$status->isOK() ) { return $status->getErrorsArray(); } @@ -3157,7 +3167,6 @@ class WikiPage implements Page, IDBAccessObject { // Update existence markers on article/talk tabs... $other = $title->getOtherPage(); - $other->invalidateCache(); $other->purgeSquid(); $title->touchLinks(); @@ -3174,7 +3183,6 @@ class WikiPage implements Page, IDBAccessObject { // Update existence markers on article/talk tabs... $other = $title->getOtherPage(); - $other->invalidateCache(); $other->purgeSquid(); $title->touchLinks(); @@ -3211,20 +3219,24 @@ class WikiPage implements Page, IDBAccessObject { * Purge caches on page update etc * * @param Title $title + * @param Revision|null $revision Revision that was just saved, may be null */ - public static function onArticleEdit( Title $title ) { + public static function onArticleEdit( Title $title, Revision $revision = null ) { // Invalidate caches of articles which include this page - DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' ); + DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'templatelinks' ) ); // Invalidate the caches of all pages which redirect here - DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' ); + DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'redirect' ) ); // Purge squid for this page only $title->purgeSquid(); - // Clear file cache for this page only HTMLFileCache::clearFileCache( $title ); - InfoAction::invalidateCache( $title ); + + $revid = $revision ? $revision->getId() : null; + DeferredUpdates::addCallableUpdate( function() use ( $title, $revid ) { + InfoAction::invalidateCache( $title, $revid ); + } ); } /**#@-*/ @@ -3411,13 +3423,14 @@ class WikiPage implements Page, IDBAccessObject { // Check if the last link refresh was before page_touched if ( $this->getLinksTimestamp() < $this->getTouched() ) { - JobQueueGroup::singleton()->push( EnqueueJob::newFromLocalJobs( - new JobSpecification( 'refreshLinks', $params, array(), $this->mTitle ) + $params['isOpportunistic'] = true; + $params['rootJobTimestamp'] = $parserOutput->getCacheTime(); + + JobQueueGroup::singleton()->lazyPush( EnqueueJob::newFromLocalJobs( + new JobSpecification( 'refreshLinks', $params, + array( 'removeDuplicates' => true ), $this->mTitle ) ) ); - return; } - - return; } /** |