use MediaWiki\Revision\SlotRecord;
use MediaWiki\User\UserIdentity;
use MessageCache;
+use MWCallableUpdate;
use ParserCache;
use ParserOptions;
use ParserOutput;
use Title;
use User;
use Wikimedia\Assert\Assert;
-use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\Rdbms\ILBFactory;
use WikiPage;
/**
*
* DerivedPageDataUpdater instances are designed to be cached inside a WikiPage instance,
* and re-used by callback code over the course of an update operation. It's a stepping stone
- * one the way to a more complete refactoring of WikiPage.
+ * on the way to a more complete refactoring of WikiPage.
*
* When using a DerivedPageDataUpdater, the following life cycle must be observed:
* grabCurrentRevision (optional), prepareContent (optional), prepareUpdate (required
private $messageCache;
/**
- * @var LBFactory
+ * @var ILBFactory
*/
private $loadbalancerFactory;
* @param JobQueueGroup $jobQueueGroup
* @param MessageCache $messageCache
* @param Language $contLang
- * @param LBFactory $loadbalancerFactory
+ * @param ILBFactory $loadbalancerFactory
*/
public function __construct(
WikiPage $wikiPage,
JobQueueGroup $jobQueueGroup,
MessageCache $messageCache,
Language $contLang,
- LBFactory $loadbalancerFactory
+ ILBFactory $loadbalancerFactory
) {
$this->wikiPage = $wikiPage;
}
}
- /**
- * @return bool|string
- */
- private function getWikiId() {
- // TODO: get from RevisionStore
- return false;
- }
-
/**
* Checks whether this DerivedPageDataUpdater can be re-used for running updates targeting
* the given revision.
*/
public function isContentDeleted() {
if ( $this->revision ) {
- // XXX: if that revision is the current revision, this should be skipped
return $this->revision->isDeleted( RevisionRecord::DELETED_TEXT );
} else {
// If the content has not been saved yet, it cannot have been deleted yet.
$hasLinks = (bool)count( $this->getCanonicalParserOutput()->getLinks() );
}
- foreach ( $this->getModifiedSlotRoles() as $role ) {
+ foreach ( $this->getSlots()->getSlotRoles() as $role ) {
$roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
if ( $roleHandler->supportsArticleCount() ) {
$content = $this->getRawContent( $role );
* See DataUpdate::getCauseAction(). (default 'unknown')
* - causeAgent: name of the user who caused the update. See DataUpdate::getCauseAgent().
* (string, default 'unknown')
+ * - known-revision-output: a combined canonical ParserOutput for the revision, perhaps
+ * from some cache. The caller is responsible for ensuring that the ParserOutput indeed
+ * matched the $rev and $options. This mechanism is intended as a temporary stop-gap,
+ * for the time until caches have been changed to store RenderedRevision states instead
+ * of ParserOutput objects. (default: null) (since 1.33)
*/
public function prepareUpdate( RevisionRecord $revision, array $options = [] ) {
Assert::parameter(
}
// "created" is forced here
- $this->options['created'] = ( $this->pageState['oldId'] === 0 );
+ $this->options['created'] = ( $this->options['created'] ||
+ ( $this->pageState['oldId'] === 0 ) );
$this->revision = $revision;
if ( $this->renderedRevision ) {
$this->renderedRevision->updateRevision( $revision );
} else {
-
// NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
// NOTE: the revision is either new or current, so we can bypass audience checks.
$this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
$this->revision,
null,
null,
- [ 'use-master' => $this->useMaster(), 'audience' => RevisionRecord::RAW ]
+ [
+ 'use-master' => $this->useMaster(),
+ 'audience' => RevisionRecord::RAW,
+ 'known-revision-output' => $options['known-revision-output'] ?? null
+ ]
);
// XXX: Since we presumably are dealing with the current revision,
}
// Defer the getCannonicalParserOutput() call triggered by getSecondaryDataUpdates()
- DeferredUpdates::addCallableUpdate( function () {
- $this->doSecondaryDataUpdates( [
- // T52785 do not update any other pages on a null edit
- 'recursive' => $this->options['changed']
- ] );
- } );
+ // by wrapping the code that schedules the secondary updates in a callback itself
+ $wrapperUpdate = new MWCallableUpdate(
+ function () {
+ $this->doSecondaryDataUpdates( [
+ // T52785 do not update any other pages on a null edit
+ 'recursive' => $this->options['changed']
+ ] );
+ },
+ __METHOD__
+ );
+ $wrapperUpdate->setTransactionRoundRequirement( $wrapperUpdate::TRX_ROUND_ABSENT );
+ DeferredUpdates::addUpdate( $wrapperUpdate );
// TODO: MCR: check if *any* changed slot supports categories!
if ( $this->rcWatchCategoryMembership
$id = $this->getPageId();
$title = $this->getTitle();
- $dbKey = $title->getPrefixedDBkey();
$shortTitle = $title->getDBkey();
if ( !$title->exists() ) {
// TODO: make search infrastructure aware of slots!
$mainSlot = $this->revision->getSlot( SlotRecord::MAIN );
if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) {
- DeferredUpdates::addUpdate( new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) );
+ DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $mainSlot->getContent() ) );
}
// If this is another user's talk page, update newtalk.
if ( $this->options['changed']
&& $title->getNamespace() == NS_USER_TALK
&& $shortTitle != $legacyUser->getTitleKey()
- && !( $this->revision->isMinor() && $legacyUser->isAllowed( 'nominornewtalk' ) )
+ && !( $this->revision->isMinor() && MediaWikiServices::getInstance()
+ ->getPermissionManager()
+ ->userHasRight( $legacyUser, 'nominornewtalk' ) )
) {
$recipient = User::newFromName( $shortTitle, false );
if ( !$recipient ) {
// TODO: In the wiring, register a listener for this on the new PageEventEmitter
ResourceLoaderWikiModule::invalidateModuleCache(
- $title, $oldLegacyRevision, $legacyRevision, $this->getWikiId() ?: wfWikiID()
+ $title,
+ $oldLegacyRevision,
+ $legacyRevision,
+ $this->loadbalancerFactory->getLocalDomainID()
);
$this->doTransition( 'done' );
* current page or otherwise depend on it (default: false)
* - defer: one of the DeferredUpdates constants, or false to run immediately after waiting
* for replication of the changes from the SecondaryDataUpdates hooks (default: false)
- * - transactionTicket: a transaction ticket from LBFactory::getEmptyTransactionTicket(),
- * only when defer is false (default: null)
* @since 1.32
*/
public function doSecondaryDataUpdates( array $options = [] ) {
$this->assertHasRevision( __METHOD__ );
- $options += [
- 'recursive' => false,
- 'defer' => false,
- 'transactionTicket' => null,
- ];
+ $options += [ 'recursive' => false, 'defer' => false ];
$deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
if ( !in_array( $options['defer'], $deferValues, true ) ) {
throw new InvalidArgumentException( 'Invalid value for defer: ' . $options['defer'] );
}
- Assert::parameterType(
- 'integer|null', $options['transactionTicket'], '$options[\'transactionTicket\']' );
-
$updates = $this->getSecondaryDataUpdates( $options['recursive'] );
$triggeringUser = $this->options['triggeringUser'] ?? $this->user;
$causeAction = $this->options['causeAction'] ?? 'unknown';
$causeAgent = $this->options['causeAgent'] ?? 'unknown';
$legacyRevision = new Revision( $this->revision );
- $ticket = $options['transactionTicket'];
-
- if ( $options['defer'] === false && $ticket !== null ) {
- // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure
- // any pending writes they made get flushed before the doUpdate() calls below.
- // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock().
- $this->loadbalancerFactory->commitAndWaitForReplication( __METHOD__, $ticket );
- }
foreach ( $updates as $update ) {
if ( $update instanceof DataUpdate ) {
$update->setRevision( $legacyRevision );
$update->setTriggeringUser( $triggeringUser );
}
+ }
- if ( $options['defer'] === false ) {
- if ( $update instanceof DataUpdate && $ticket !== null ) {
- $update->setTransactionTicket( $ticket );
- }
- $update->doUpdate();
- } else {
+ if ( $options['defer'] === false ) {
+ // T221577: flush any transaction; each update needs outer transaction scope
+ $this->loadbalancerFactory->commitMasterChanges( __METHOD__ );
+ foreach ( $updates as $update ) {
+ DeferredUpdates::attemptUpdate( $update, $this->loadbalancerFactory );
+ }
+ } else {
+ foreach ( $updates as $update ) {
DeferredUpdates::addUpdate( $update, $options['defer'] );
}
}