$from = self::convertSelectType( $from );
$db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
- $row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
+ $row = $db->selectRow(
+ 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
if ( !$row ) {
return null;
}
/**
* Fetch a page record with the given conditions
- * @param DatabaseBase $dbr
+ * @param IDatabase $dbr
* @param array $conditions
* @param array $options
* @return object|bool Database result resource, or false on failure
* Fetch a page record matching the Title object's namespace and title
* using a sanitized title string
*
- * @param DatabaseBase $dbr
+ * @param IDatabase $dbr
* @param Title $title
* @param array $options
* @return object|bool Database result resource, or false on failure
/**
* Fetch a page record matching the requested ID
*
- * @param DatabaseBase $dbr
+ * @param IDatabase $dbr
* @param int $id
* @param array $options
* @return object|bool Database result resource, or false on failure
* Load the object from a database row
*
* @since 1.20
- * @param object $data Database row containing at least fields returned by selectFields()
+ * @param object|bool $data DB row containing fields returned by selectFields() or false
* @param string|int $from One of the following:
* - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
* - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
// 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.
+ // 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
$conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
}
- $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
+ // Username hidden?
+ $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0";
$jconds = array(
'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
/**
* Get a ParserOutput for the given ParserOptions and revision ID.
- * The parser cache will be used if possible.
+ *
+ * The parser cache will be used if possible. Cache misses that result
+ * in parser runs are debounced with PoolCounter.
*
* @since 1.19
* @param ParserOptions $parserOptions ParserOptions to use for the parse operation
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
$useParserCache = $this->shouldCheckParserCache( $parserOptions, $oldid );
- wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
+ wfDebug( __METHOD__ .
+ ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $parserOptions->getStubThreshold() ) {
wfIncrStats( 'pcache.miss.stub' );
}
/**
* Do standard deferred updates after page view (existing or missing page)
* @param User $user The relevant user
- * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
+ * @param int $oldid Revision id being viewed; if not given or 0, latest revision is assumed
*/
public function doViewUpdates( User $user, $oldid = 0 ) {
if ( wfReadOnly() ) {
$title->invalidateCache();
if ( $wgUseSquid ) {
// Send purge now that page_touched update was committed above
- $update = SquidUpdate::newSimplePurge( $title );
+ $update = new SquidUpdate( $title->getSquidURLs() );
$update->doUpdate();
}
} );
return true;
}
-
/**
* Insert a new empty page record for this article.
* This *must* be followed up by creating a revision
* or else the record will be left in a funky state.
* Best if all done inside a transaction.
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @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,
- 'page_namespace' => $this->mTitle->getNamespace(),
- 'page_title' => $this->mTitle->getDBkey(),
- 'page_restrictions' => '',
- 'page_is_redirect' => 0, // Will set this shortly...
- 'page_is_new' => 1,
- 'page_random' => wfRandom(),
- 'page_touched' => $dbw->timestamp(),
- 'page_latest' => 0, // Fill this in shortly...
- 'page_len' => 0, // Fill this in shortly...
- ), __METHOD__, 'IGNORE' );
-
- $affected = $dbw->affectedRows();
-
- if ( $affected ) {
+ $dbw->insert(
+ 'page',
+ array(
+ 'page_id' => $dbw->nextSequenceValue( 'page_page_id_seq' ),
+ 'page_namespace' => $this->mTitle->getNamespace(),
+ 'page_title' => $this->mTitle->getDBkey(),
+ 'page_restrictions' => '',
+ 'page_is_redirect' => 0, // Will set this shortly...
+ 'page_is_new' => 1,
+ 'page_random' => wfRandom(),
+ 'page_touched' => $dbw->timestamp(),
+ 'page_latest' => 0, // Fill this in shortly...
+ 'page_len' => 0, // Fill this in shortly...
+ ),
+ __METHOD__,
+ 'IGNORE'
+ );
+
+ if ( $dbw->affectedRows() > 0 ) {
$newid = $dbw->insertId();
$this->mId = $newid;
$this->mTitle->resetArticleID( $newid );
return $newid;
} else {
- return false;
+ return false; // nothing changed
}
}
/**
* Update the page record to point to a newly saved revision.
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param Revision $revision For ID number, and text used to set
* length and redirect status fields
* @param int $lastRevision If given, will not overwrite the page field
* Giving 0 indicates the new page flag should be set on.
* @param bool $lastRevIsRedirect If given, will optimize adding and
* removing rows in redirect table.
- * @return bool True on success, false on failure
+ * @return bool Success; false if the page row was missing or page_latest changed
*/
public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
$lastRevIsRedirect = null
$this->mLatest = $revision->getId();
$this->mIsRedirect = (bool)$rt;
// Update the LinkCache.
- LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
- $this->mLatest, $revision->getContentModel() );
+ LinkCache::singleton()->addGoodLinkObj(
+ $this->getId(),
+ $this->mTitle,
+ $len,
+ $this->mIsRedirect,
+ $this->mLatest,
+ $revision->getContentModel()
+ );
}
return $result;
/**
* Add row to the redirect table if this is a redirect, remove otherwise.
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param Title $redirectTitle Title object pointing to the redirect target,
* or NULL if this is not a redirect
* @param null|bool $lastRevIsRedirect If given, will optimize adding and
*
* @deprecated since 1.24, use updateRevisionOn instead
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param Revision $revision
* @return bool
*/
* @param string $edittime Revision timestamp or null to use the current revision.
*
* @throws MWException
- * @return string New complete article text, or null if error.
+ * @return string|null New complete article text, or null if error.
*
* @deprecated since 1.21, use replaceSectionAtRev() instead
*/
* @param string $edittime Revision timestamp or null to use the current revision.
*
* @throws MWException
- * @return Content New complete article content, or null if error.
+ * @return Content|null New complete article content, or null if error.
*
* @since 1.21
* @deprecated since 1.24, use replaceSectionAtRev instead
*/
- public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
- $edittime = null ) {
+ public function replaceSectionContent(
+ $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
+ ) {
$baseRevId = null;
if ( $edittime && $sectionId !== 'new' ) {
* @param int|null $baseRevId
*
* @throws MWException
- * @return Content New complete article content, or null if error.
+ * @return Content|null New complete article content, or null if error.
*
* @since 1.24
*/
* Do not log the change in recentchanges
* EDIT_FORCE_BOT
* Mark the edit a "bot" edit regardless of user rights
- * EDIT_DEFER_UPDATES
- * Defer some of the updates until the end of index.php
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
*
* Do not log the change in recentchanges
* EDIT_FORCE_BOT
* Mark the edit a "bot" edit regardless of user rights
- * EDIT_DEFER_UPDATES
- * Defer some of the updates until the end of index.php
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
*
$changed = !$content->equals( $old_content );
if ( $changed ) {
- $dbw->begin( __METHOD__ );
-
$prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
$status->merge( $prepStatus );
if ( !$status->isOK() ) {
- $dbw->rollback( __METHOD__ );
+ return $status;
+ }
+
+ $dbw->begin( __METHOD__ );
+ // 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();
+ if ( $latestNow != $oldid ) {
+ $dbw->commit( __METHOD__ );
+ // Page updated or deleted in the mean time
+ $status->fatal( 'edit-conflict' );
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 );
+ // At this point we are now comitted to returning an OK
+ // status unless some DB query error or other exception comes up.
+ // This way callers don't have to call rollback() if $status is bad
+ // unless they actually try to catch exceptions (which is rare).
- if ( !$ok ) {
- // Belated edit conflict! Run away!!
- $status->fatal( 'edit-conflict' );
+ $revisionId = $revision->insertOn( $dbw );
+ // Update page_latest and friends to reflect the new revision
+ if ( !$this->updateRevisionOn( $dbw, $revision, null, $oldIsRedirect ) ) {
$dbw->rollback( __METHOD__ );
-
- return $status;
+ throw new MWException( "Failed to update page row to use new revision." );
}
- Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete',
+ array( $this, $revision, $baseRevId, $user ) );
// Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
// Create new article
$status->value['new'] = true;
- $dbw->begin( __METHOD__ );
-
$prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
$status->merge( $prepStatus );
-
if ( !$status->isOK() ) {
- $dbw->rollback( __METHOD__ );
-
return $status;
}
- $status->merge( $prepStatus );
+ $dbw->begin( __METHOD__ );
- // Add the page record; stake our claim on this title!
- // This will return false if the article already exists
+ // Add the page record unless one already exists for the title
$newid = $this->insertOn( $dbw );
-
if ( $newid === false ) {
- $dbw->rollback( __METHOD__ );
+ $dbw->commit( __METHOD__ ); // nothing inserted
$status->fatal( 'edit-already-exists' );
- return $status;
+ return $status; // nothing done
}
+ // At this point we are now comitted to returning an OK
+ // status unless some DB query error or other exception comes up.
+ // This way callers don't have to call rollback() if $status is bad
+ // unless they actually try to catch exceptions (which is rare).
+
// Save the revision text...
$revision = new Revision( array(
'page' => $newid,
}
// Update the page record with revision data
- $this->updateRevisionOn( $dbw, $revision, 0 );
+ if ( !$this->updateRevisionOn( $dbw, $revision, 0 ) ) {
+ $dbw->rollback( __METHOD__ );
+ throw new MWException( "Failed to update page row to use new revision." );
+ }
Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
Hooks::run( 'PageContentInsertComplete', $hook_args );
}
- // Do updates right now unless deferral was requested
- if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
- DeferredUpdates::doUpdates();
- }
-
// Return the new revision (or null) to the caller
$status->value['revision'] = $revision;
* @since 1.21
*/
public function prepareContentForEdit(
- Content $content, $revision = null, User $user = null, $serialFormat = null, $useCache = true
+ Content $content, $revision = null, User $user = null,
+ $serialFormat = null, $useCache = true
) {
global $wgContLang, $wgUser, $wgAjaxEditStash;
// itself (such as via self-transclusion). In this case, we need to make sure
// that any such self-references refer to the newly-saved revision, and not
// to the previous one, which could otherwise happen due to slave lag.
- $oldCallback = $edit->popts->setCurrentRevisionCallback(
- function ( $title, $parser = false ) use ( $revision, &$oldCallback ) {
+ $oldCallback = $edit->popts->getCurrentRevisionCallback();
+ $edit->popts->setCurrentRevisionCallback(
+ function ( Title $title, $parser = false ) use ( $revision, &$oldCallback ) {
if ( $title->equals( $revision->getTitle() ) ) {
return $revision;
} else {
- return call_user_func(
- $oldCallback,
- $title,
- $parser
- );
+ return call_user_func( $oldCallback, $title, $parser );
}
}
);
$edit->oldContent = $this->getContent( Revision::RAW );
// NOTE: B/C for hooks! don't use these fields!
- $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
- $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
+ $edit->newText = $edit->newContent
+ ? ContentHandler::getContentText( $edit->newContent )
+ : '';
+ $edit->oldText = $edit->oldContent
+ ? ContentHandler::getContentText( $edit->oldContent )
+ : '';
$edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) : '';
$this->mPreparedEdit = $edit;
$updates = $content->getSecondaryDataUpdates(
$this->getTitle(), null, $recursive, $editInfo->output );
foreach ( $updates as $update ) {
+ if ( $update instanceof LinksUpdate ) {
+ $update->setRevision( $revision );
+ $update->setTriggeringUser( $user );
+ }
DeferredUpdates::addUpdate( $update );
}
}
if ( !$recipient ) {
wfDebug( __METHOD__ . ": invalid username\n" );
} else {
- // Allow extensions to prevent user notification when a new message is added to their talk page
+ // Allow extensions to prevent user notification
+ // when a new message is added to their talk page
if ( Hooks::run( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
if ( User::isIP( $shortTitle ) ) {
// An anonymous user
}
}
- /**
- * 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
* @param bool $minor Whereas it's a minor modification
* @param string $serialFormat Format for storing the content in the database
*/
- public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
- $serialFormat = null
+ public function doQuickEditContent(
+ Content $content, User $user, $comment = '', $minor = false, $serialFormat = null
) {
$serialized = $content->serialize( $serialFormat );
__METHOD__
);
- Hooks::run( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete',
+ array( $this, $nullRevision, $latest, $user ) );
Hooks::run( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
} else { // Protection of non-existing page (also known as "title protection")
// Cascade protection is meaningless in this case
# with '' filtered out. All possible message keys are listed below:
# * protect-level-autoconfirmed
# * protect-level-sysop
- $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
+ $restrictionsText = wfMessage( 'protect-level-' . $restrictions )
+ ->inContentLanguage()->text();
$expiryText = $this->formatExpiry( $expiry[$action] );
foreach ( array_filter( $limit ) as $action => $restrictions ) {
$expiryText = $this->formatExpiry( $expiry[$action] );
- $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
+ $protectDescriptionLog .= $wgContLang->getDirMark() .
+ "[$action=$restrictions] ($expiryText)";
}
return trim( $protectDescriptionLog );
*/
protected static function flattenRestrictions( $limit ) {
if ( !is_array( $limit ) ) {
- throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
+ throw new MWException( __METHOD__ . ' given non-array restriction set' );
}
$bits = array();
* @param string $reason Delete reason for deletion log
* @param bool $suppress Suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
- * @param int $id Article ID
- * @param bool $commit Defaults to true, triggers transaction end
- * @param array &$error Array of errors to append to
+ * @param int $u1 Unused
+ * @param bool $u2 Unused
+ * @param array|string &$error Array of errors to append to
* @param User $user The deleting user
* @return bool True if successful
*/
public function doDeleteArticle(
- $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+ $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null
) {
- $status = $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user );
+ $status = $this->doDeleteArticleReal( $reason, $suppress, $u1, $u2, $error, $user );
return $status->isGood();
}
* @param string $reason Delete reason for deletion log
* @param bool $suppress Suppress all revisions and log the deletion in
* the suppression log instead of the deletion log
- * @param int $id Article ID
- * @param bool $commit Defaults to true, triggers transaction end
- * @param array &$error Array of errors to append to
+ * @param int $u1 Unused
+ * @param bool $u2 Unused
+ * @param array|string &$error Array of errors to append to
* @param User $user The deleting user
* @return Status Status object; if successful, $status->value is the log_id of the
* deletion log entry. If the page couldn't be deleted because it wasn't
* found, $status is a non-fatal 'cannotdelete' error
*/
public function doDeleteArticleReal(
- $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+ $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null
) {
global $wgUser, $wgContentHandlerUseDB;
$status = Status::newGood();
if ( $this->mTitle->getDBkey() === '' ) {
- $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+ $status->error( 'cannotdelete',
+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
return $status;
}
$user = is_null( $user ) ? $wgUser : $user;
- if ( !Hooks::run( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
+ if ( !Hooks::run( 'ArticleDelete',
+ array( &$this, &$user, &$reason, &$error, &$status, $suppress )
+ ) ) {
if ( $status->isOK() ) {
// Hook aborted but didn't set a fatal status
$status->fatal( 'delete-hook-aborted' );
}
$dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
-
- if ( $id == 0 ) {
- // 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 || $this->getLatest() != $latest ) {
- // Page not there or trx snapshot is stale
- $dbw->rollback( __METHOD__ );
- $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
- return $status;
- }
+ $dbw->startAtomic( __METHOD__ );
+
+ $this->loadPageData( self::READ_LATEST );
+ $id = $this->getID();
+ // 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.
+ $lockedLatest = $this->lock();
+ if ( $id == 0 || $this->getLatest() != $lockedLatest ) {
+ $dbw->endAtomic( __METHOD__ );
+ // Page not there or trx snapshot is stale
+ $status->error( 'cannotdelete',
+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+ return $status;
}
+ // At this point we are now comitted to returning an OK
+ // status unless some DB query error or other exception comes up.
+ // This way callers don't have to call rollback() if $status is bad
+ // unless they actually try to catch exceptions (which is rare).
+
// we need to remember the old content so we can use it to generate all deletion updates.
$content = $this->getContent( Revision::RAW );
$row['ar_content_format'] = 'rev_content_format';
}
- $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
+ // Copy all the page revisions into the archive table
+ $dbw->insertSelect(
+ 'archive',
+ array( 'page', 'revision' ),
$row,
array(
'page_id' => $id,
'page_id = rev_page'
- ), __METHOD__
+ ),
+ __METHOD__
);
// Now that it's safely backed up, delete it
$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
- $ok = ( $dbw->affectedRows() > 0 ); // $id could be laggy
-
- if ( !$ok ) {
- $dbw->rollback( __METHOD__ );
- $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
- return $status;
- }
if ( !$dbw->cascadingDeletes() ) {
$dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
$logEntry->publish( $logid );
} );
- if ( $commit ) {
- $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 );
+ $dbw->endAtomic( __METHOD__ );
$this->doDeleteUpdates( $id, $content );
- Hooks::run( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
+ Hooks::run( 'ArticleDeleteComplete',
+ array( &$this, &$user, $reason, $id, $content, $logEntry ) );
$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() ) );
+ $cache->set( $key, 1, $cache::TTL_DAY );
+
return $status;
}
/**
- * Lock the page row for this title and return page_latest (or 0)
+ * Lock the page row for this title+id and return page_latest (or 0)
*
- * @return integer
+ * @return integer Returns 0 if no row was found with this title+id
*/
protected function lock() {
return (int)wfGetDB( DB_MASTER )->selectField(
'page',
'page_latest',
array(
+ 'page_id' => $this->getId(),
+ // Typically page_id is enough, but some code might try to do
+ // updates assuming the title is the same, so verify that
'page_namespace' => $this->getTitle()->getNamespace(),
'page_title' => $this->getTitle()->getDBkey()
),
// Delete pagelinks, update secondary indexes, etc
$updates = $this->getDeletionUpdates( $content );
- // Make sure an enqueued jobs run after commit so they see the deletion
- wfGetDB( DB_MASTER )->onTransactionIdle( function() use ( $updates ) {
- DataUpdate::runUpdates( $updates, 'enqueue' );
- } );
+ foreach ( $updates as $update ) {
+ DeferredUpdates::addUpdate( $update );
+ }
// Reparse any pages transcluding this page
LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
}
// raise error, when the edit is an edit without a new version
- if ( empty( $status->value['revision'] ) ) {
+ $statusRev = isset( $status->value['revision'] )
+ ? $status->value['revision']
+ : null;
+ if ( !( $statusRev instanceof Revision ) ) {
$resultDetails = array( 'current' => $current );
return array( array( 'alreadyrolled',
htmlspecialchars( $this->mTitle->getPrefixedText() ),
) );
}
- $revId = $status->value['revision']->getId();
+ $revId = $statusRev->getId();
Hooks::run( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
// Images
if ( $title->getNamespace() == NS_FILE ) {
- $update = new HTMLCacheUpdate( $title, 'imagelinks' );
- $update->doUpdate();
+ DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'imagelinks' ) );
}
// User talk pages
}
if ( count( $added ) ) {
- $insertRows = array();
- foreach ( $added as $cat ) {
- $insertRows[] = array(
- 'cat_title' => $cat,
- 'cat_pages' => 1,
- 'cat_subcats' => ( $ns == NS_CATEGORY ) ? 1 : 0,
- 'cat_files' => ( $ns == NS_FILE ) ? 1 : 0,
- );
- }
- $dbw->upsert(
+ $existingAdded = $dbw->selectFieldValues(
'category',
- $insertRows,
- array( 'cat_title' ),
- $addFields,
- $method
+ 'cat_title',
+ array( 'cat_title' => $added ),
+ __METHOD__
);
+
+ // For category rows that already exist, do a plain
+ // UPDATE instead of INSERT...ON DUPLICATE KEY UPDATE
+ // to avoid creating gaps in the cat_id sequence.
+ if ( count( $existingAdded ) ) {
+ $dbw->update(
+ 'category',
+ $addFields,
+ array( 'cat_title' => $existingAdded ),
+ __METHOD__
+ );
+ }
+
+ $missingAdded = array_diff( $added, $existingAdded );
+ if ( count( $missingAdded ) ) {
+ $insertRows = array();
+ foreach ( $missingAdded as $cat ) {
+ $insertRows[] = array(
+ 'cat_title' => $cat,
+ 'cat_pages' => 1,
+ 'cat_subcats' => ( $ns == NS_CATEGORY ) ? 1 : 0,
+ 'cat_files' => ( $ns == NS_FILE ) ? 1 : 0,
+ );
+ }
+ $dbw->upsert(
+ 'category',
+ $insertRows,
+ array( 'cat_title' ),
+ $addFields,
+ $method
+ );
+ }
}
if ( count( $deleted ) ) {
return;
}
- if ( !Hooks::run( 'OpportunisticLinksUpdate', array( $this, $this->mTitle, $parserOutput ) ) ) {
+ if ( !Hooks::run( 'OpportunisticLinksUpdate',
+ array( $this, $this->mTitle, $parserOutput )
+ ) ) {
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( EnqueueJob::newFromLocalJobs(
- new JobSpecification( 'refreshLinks', $params,
- array( 'removeDuplicates' => true ), $this->mTitle )
- ) );
+ // 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 )
+ );
+ }
+ }
}
}
*/
public function getDeletionUpdates( Content $content = null ) {
if ( !$content ) {
- // load content object, which may be used to determine the necessary updates
- // XXX: the content may not be needed to determine the updates, then this would be overhead.
+ // load content object, which may be used to determine the necessary updates.
+ // XXX: the content may not be needed to determine the updates.
$content = $this->getContent( Revision::RAW );
}