if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
$this->mRedirectTarget = Title::makeTitle(
$row->rd_namespace, $row->rd_title,
- $row->rd_fragment, $row->rd_interwiki );
+ $row->rd_fragment, $row->rd_interwiki
+ );
return $this->mRedirectTarget;
}
}
/**
- * Insert an entry for this page into the redirect table.
+ * Insert an entry for this page into the redirect table if the content is a redirect
+ *
+ * The database update will be deferred via DeferredUpdates
*
* Don't call this function directly unless you know what you're doing.
* @return Title|null Title object or null if not a redirect
*/
public function insertRedirect() {
- // recurse through to only get the final target
$content = $this->getContent();
$retval = $content ? $content->getUltimateRedirectTarget() : null;
if ( !$retval ) {
return null;
}
- $this->insertRedirectEntry( $retval );
+
+ // Update the DB post-send if the page has not cached since now
+ $that = $this;
+ $latest = $this->getLatest();
+ DeferredUpdates::addCallableUpdate( function() use ( $that, $retval, $latest ) {
+ $that->insertRedirectEntry( $retval, $latest );
+ } );
+
return $retval;
}
/**
- * Insert or update the redirect table entry for this page to indicate
- * it redirects to $rt .
+ * Insert or update the redirect table entry for this page to indicate it redirects to $rt
* @param Title $rt Redirect target
+ * @param int|null $oldLatest Prior page_latest for check and set
*/
- public function insertRedirectEntry( $rt ) {
+ public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
$dbw = wfGetDB( DB_MASTER );
- $dbw->replace( 'redirect', array( 'rd_from' ),
- array(
- 'rd_from' => $this->getId(),
- 'rd_namespace' => $rt->getNamespace(),
- 'rd_title' => $rt->getDBkey(),
- 'rd_fragment' => $rt->getFragment(),
- 'rd_interwiki' => $rt->getInterwiki(),
- ),
- __METHOD__
- );
+ $dbw->startAtomic( __METHOD__ );
+
+ if ( !$oldLatest || $oldLatest == $this->lockAndGetLatest() ) {
+ $dbw->replace( 'redirect',
+ array( 'rd_from' ),
+ array(
+ 'rd_from' => $this->getId(),
+ 'rd_namespace' => $rt->getNamespace(),
+ 'rd_title' => $rt->getDBkey(),
+ 'rd_fragment' => $rt->getFragment(),
+ 'rd_interwiki' => $rt->getInterwiki(),
+ ),
+ __METHOD__
+ );
+ }
+
+ $dbw->endAtomic( __METHOD__ );
}
/**
return new UserArrayFromResult( $res );
}
- /**
- * Get the last N authors
- * @param int $num Number of revisions to get
- * @param int|string $revLatest The latest rev_id, selected from the master (optional)
- * @return array Array of authors, duplicates not removed
- */
- public function getLastNAuthors( $num, $revLatest = 0 ) {
- // First try the slave
- // If that doesn't have the latest revision, try the master
- $continue = 2;
- $db = wfGetDB( DB_SLAVE );
-
- do {
- $res = $db->select( array( 'page', 'revision' ),
- array( 'rev_id', 'rev_user_text' ),
- array(
- 'page_namespace' => $this->mTitle->getNamespace(),
- 'page_title' => $this->mTitle->getDBkey(),
- 'rev_page = page_id'
- ), __METHOD__,
- array(
- 'ORDER BY' => 'rev_timestamp DESC',
- 'LIMIT' => $num
- )
- );
-
- if ( !$res ) {
- return array();
- }
-
- $row = $db->fetchObject( $res );
-
- if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
- $db = wfGetDB( DB_MASTER );
- $continue--;
- } else {
- $continue = 0;
- }
- } while ( $continue );
-
- $authors = array( $row->rev_user_text );
-
- foreach ( $res as $row ) {
- $authors[] = $row->rev_user_text;
- }
-
- return $authors;
- }
-
/**
* Should the parser cache be used?
*
$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 = new SquidUpdate( $title->getSquidURLs() );
- $update->doUpdate();
- }
} );
+ // Send purge after above page_touched update was committed
+ DeferredUpdates::addUpdate(
+ new SquidUpdate( $title->getSquidURLs() ),
+ DeferredUpdates::PRESEND
+ );
+
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
// @todo move this logic to MessageCache
if ( $this->exists() ) {
* must exist and must not be deleted
* @param Revision $undo
* @param Revision $undoafter Must be an earlier revision than $undo
- * @return mixed String on success, false on failure
+ * @return Content|bool Content on success, false on failure
* @since 1.21
* Before we had the Content object, this was done in getUndoText
*/
$isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
$bot = $flags & EDIT_FORCE_BOT;
+ $old_revision = $this->getRevision(); // current revision
$old_content = $this->getContent( Revision::RAW ); // current revision's content
$oldsize = $old_content ? $old_content->getSize() : 0;
// Get the latest page_latest value while locking it.
// Do a CAS style check to see if it's the same as when this method
// started. If it changed then bail out before touching the DB.
- $latestNow = $this->lock();
+ $latestNow = $this->lockAndGetLatest();
if ( $latestNow != $oldid ) {
$dbw->commit( __METHOD__ );
// Page updated or deleted in the mean time
$revisionId = $revision->insertOn( $dbw );
// Update page_latest and friends to reflect the new revision
- $this->updateRevisionOn( $dbw, $revision, null, $oldIsRedirect );
+ if ( !$this->updateRevisionOn( $dbw, $revision, null, $oldIsRedirect ) ) {
+ $dbw->rollback( __METHOD__ );
+ throw new MWException( "Failed to update page row to use new revision." );
+ }
Hooks::run( 'NewRevisionFromEditComplete',
array( $this, $revision, $baseRevId, $user ) );
$user,
array(
'changed' => $changed,
- 'oldcountable' => $oldcountable
+ 'oldcountable' => $oldcountable,
+ 'oldrevision' => $old_revision
)
);
$this->mTimestamp = $now;
// Update links, etc.
- $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
+ $this->doEditUpdates(
+ $revision,
+ $user,
+ array( 'created' => true, 'oldrevision' => $old_revision )
+ );
$hook_args = array( &$this, &$user, $content, $summary,
$flags & EDIT_MINOR, null, null, &$flags, $revision );
* Prepare text which is about to be saved.
* Returns a stdClass with source, pst and output members
*
+ * @param string $text
+ * @param int|null $revid
+ * @param User|null $user
* @deprecated since 1.21: use prepareContentForEdit instead.
* @return object
*/
}
// The edit may have already been prepared via api.php?action=stashedit
- $cachedEdit = $useCache && $wgAjaxEditStash && !$user->isAllowed( 'bot' )
+ $cachedEdit = $useCache && $wgAjaxEditStash
? ApiStashEdit::checkCache( $this->getTitle(), $content, $user )
: false;
* - changed: boolean, whether the revision changed the content (default true)
* - created: boolean, whether the revision created the page (default false)
* - moved: boolean, whether the page was moved (default false)
+ * - restored: boolean, whether the page was undeleted (default false)
+ * - oldrevision: Revision object for the pre-update revision (default null)
* - oldcountable: boolean, null, or string 'no-change' (default null):
* - boolean: whether the page was counted as an article before that
* revision, only used in changed is true and created is false
* - 'no-change': don't update the article count, ever
*/
public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
+ global $wgRCWatchCategoryMembership;
+
$options += array(
'changed' => true,
'created' => false,
'moved' => false,
+ 'restored' => false,
+ 'oldrevision' => null,
'oldcountable' => null
);
$content = $revision->getContent();
if ( $content ) {
$recursive = $options['changed']; // bug 50785
$updates = $content->getSecondaryDataUpdates(
- $this->getTitle(), null, $recursive, $editInfo->output );
+ $this->getTitle(), null, $recursive, $editInfo->output
+ );
foreach ( $updates as $update ) {
if ( $update instanceof LinksUpdate ) {
$update->setRevision( $revision );
+ $update->setTriggeringUser( $user );
}
DeferredUpdates::addUpdate( $update );
}
+ if ( $wgRCWatchCategoryMembership
+ && ( $options['changed'] || $options['created'] )
+ && !$options['restored']
+ ) {
+ // Note: jobs are pushed after deferred updates, so the job should be able to see
+ // the recent change entry (also done via deferred updates) and carry over any
+ // bot/deletion/IP flags, ect.
+ JobQueueGroup::singleton()->lazyPush( new CategoryMembershipChangeJob(
+ $this->getTitle(),
+ array(
+ 'pageId' => $this->getId(),
+ 'revTimestamp' => $revision->getTimestamp()
+ )
+ ) );
+ }
}
Hooks::run( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
}
}
- /**
- * Edit an article without doing all that other stuff
- * The article must already exist; link tables etc
- * are not updated, caches are not flushed.
- *
- * @param string $text Text submitted
- * @param User $user The relevant user
- * @param string $comment Comment submitted
- * @param bool $minor Whereas it's a minor modification
- *
- * @deprecated since 1.21, use doEditContent() instead.
- */
- public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
- ContentHandler::deprecated( __METHOD__, "1.21" );
-
- $content = ContentHandler::makeContent( $text, $this->getTitle() );
- $this->doQuickEditContent( $content, $user, $comment, $minor );
- }
-
/**
* Edit an article without doing all that other stuff
* The article must already exist; link tables etc
// 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.
- $lockedLatest = $this->lock();
+ $lockedLatest = $this->lockAndGetLatest();
if ( $id == 0 || $this->getLatest() != $lockedLatest ) {
$dbw->endAtomic( __METHOD__ );
// Page not there or trx snapshot is stale
$status->value = $logid;
// Show log excerpt on 404 pages rather than just a link
+ $cache = ObjectCache::getMainStashInstance();
$key = wfMemcKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
- ObjectCache::getMainStashInstance()->set( $key, 1, 86400 );
+ $cache->set( $key, 1, $cache::TTL_DAY );
return $status;
}
* Lock the page row for this title+id and return page_latest (or 0)
*
* @return integer Returns 0 if no row was found with this title+id
+ * @since 1.27
*/
- protected function lock() {
+ public function lockAndGetLatest() {
return (int)wfGetDB( DB_MASTER )->selectField(
'page',
'page_latest',
return;
}
+ $params = array(
+ 'isOpportunistic' => true,
+ 'rootJobTimestamp' => $parserOutput->getCacheTime()
+ );
+
if ( $this->mTitle->areRestrictionsCascading() ) {
// If the page is cascade protecting, the links should really be up-to-date
- $params = array( 'prioritize' => true );
+ JobQueueGroup::singleton()->lazyPush(
+ RefreshLinksJob::newPrioritized( $this->mTitle, $params )
+ );
} elseif ( $parserOutput->hasDynamicContent() ) {
- // Assume the output contains time/random based magic words
- $params = array();
- } else {
- // If the inclusions are deterministic, the edit-triggered link jobs are enough
- return;
- }
-
- // Check if the last link refresh was before page_touched
- if ( $this->getLinksTimestamp() < $this->getTouched() ) {
- $params['isOpportunistic'] = true;
- $params['rootJobTimestamp'] = $parserOutput->getCacheTime();
- JobQueueGroup::singleton()->lazyPush( new RefreshLinksJob( $this->mTitle, $params ) );
+ // Assume the output contains "dynamic" time/random based magic words.
+ // Only update pages that expired due to dynamic content and NOT due to edits
+ // to referenced templates/files. When the cache expires due to dynamic content,
+ // page_touched is unchanged. We want to avoid triggering redundant jobs due to
+ // views of pages that were just purged via HTMLCacheUpdateJob. In that case, the
+ // template/file edit already triggered recursive RefreshLinksJob jobs.
+ if ( $this->getLinksTimestamp() > $this->getTouched() ) {
+ // If a page is uncacheable, do not keep spamming a job for it.
+ // Although it would be de-duplicated, it would still waste I/O.
+ $cache = ObjectCache::getLocalClusterInstance();
+ $key = $cache->makeKey( 'dynamic-linksupdate', 'last', $this->getId() );
+ if ( $cache->add( $key, time(), 60 ) ) {
+ JobQueueGroup::singleton()->lazyPush(
+ RefreshLinksJob::newDynamic( $this->mTitle, $params )
+ );
+ }
+ }
}
}