Go to the documentation of this file.
31 use InvalidArgumentException;
57 use Psr\Log\LoggerAwareInterface;
58 use Psr\Log\LoggerInterface;
59 use Psr\Log\NullLogger;
69 use Wikimedia\Assert\Assert;
182 'oldrevision' =>
null,
183 'oldcountable' =>
null,
184 'oldredirect' =>
null,
185 'triggeringUser' =>
null,
188 'causeAction' =>
null,
189 'causeAgent' =>
null,
190 'editResult' =>
null,
263 private const TRANSITIONS = [
266 'knows-current' =>
true,
267 'has-content' =>
true,
268 'has-revision' =>
true,
271 'knows-current' =>
true,
272 'has-content' =>
true,
273 'has-revision' =>
true,
276 'has-content' =>
true,
277 'has-revision' =>
true,
280 'has-revision' =>
true,
332 $this->hookRunner =
new HookRunner( $hookContainer );
335 $this->logger =
new NullLogger();
357 $this->stage = $newStage;
372 if ( empty( self::TRANSITIONS[$this->stage][$newStage] ) ) {
373 throw new LogicException(
"Cannot transition from {$this->stage} to $newStage" );
398 throw new InvalidArgumentException(
'$parentId should match the parent of $revision' );
414 if ( $this->pageState
422 if ( $this->pageState
423 && $parentId !==
null
424 && $this->pageState[
'oldId'] !== $parentId
430 if ( $this->slotsUpdate
468 return $this->wikiPage->getTitle();
489 return $this->pageState[
'oldId'] > 0;
504 if ( $this->parentRevision ) {
508 if ( !$this->pageState[
'oldId'] ) {
515 $flags = $this->
useMaster() ? RevisionStore::READ_LATEST : 0;
516 $this->parentRevision = $oldId
517 ? $this->revisionStore->getRevisionById( $oldId, $flags )
544 if ( $this->pageState ) {
545 return $this->pageState[
'oldRevision'];
559 'oldRevision' => $current,
560 'oldId' => $current ? $current->getId() : 0,
567 return $this->pageState[
'oldRevision'];
576 return $this->revision !==
null;
587 return $this->revision !==
null && $this->revision->getId() !==
null;
595 return $this->wikiPage->getId();
604 if ( $this->revision ) {
622 return $this->
getSlots()->getSlot( $role );
634 return $this->
getRawSlot( $role )->getContent();
644 return $this->
getRawSlot( $role )->getModel();
653 return $this->contentHandlerFactory
659 return $this->wikiPage->wasLoadedFrom( self::READ_LATEST );
668 if ( !$this->
getTitle()->isContentPage() ) {
684 if ( $this->articleCountMethod ===
'link' ) {
693 foreach ( $this->
getSlots()->getSlotRoles() as $role ) {
694 $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
695 if ( $roleHandler->supportsArticleCount() ) {
698 if (
$content->isCountable( $hasLinks ) ) {
715 return $mainContent->isRedirect();
727 return $mainContent->isRedirect();
759 if ( $this->slotsUpdate ) {
760 if ( !$this->user ) {
761 throw new LogicException(
762 'Unexpected state: $this->slotsUpdate was initialized, '
763 .
'but $this->user was not.'
768 throw new LogicException(
'Can\'t call prepareContent() again for different user! '
769 .
'Expected ' . $this->user->getName() .
', got ' .
$user->
getName()
773 if ( !$this->slotsUpdate->hasSameUpdates(
$slotsUpdate ) ) {
774 throw new LogicException(
775 'Can\'t call prepareContent() again with different slot content!'
790 $stashedEdit =
false;
795 $stashedEdit = $editStash->checkCache(
803 $this->hookRunner->onArticlePrepareTextForEdit(
$wikiPage, $userPopts );
816 $this->revision->setTimestamp( MWTimestamp::now( TS_MW ) );
817 $this->revision->setUser(
$user );
820 $oldCallback = $userPopts->getCurrentRevisionRecordCallback();
821 $userPopts->setCurrentRevisionRecordCallback(
822 function (
Title $parserTitle, $parser =
null ) use (
$title, $oldCallback ) {
824 return $this->revision;
826 return call_user_func( $oldCallback, $parserTitle, $parser );
831 $pstContentSlots = $this->revision->getSlots();
836 if ( $slot->isInherited() ) {
845 $pstContent =
$content->preSaveTransform(
$title, $this->user, $userPopts );
849 $pstContentSlots->setSlot( $pstSlot );
853 $pstContentSlots->removeSlot( $role );
862 if ( !$this->options[
'changed'] ) {
884 if ( $stashedEdit ) {
886 $output = $stashedEdit->output;
888 $output->setCacheTime( $stashedEdit->timestamp );
890 $renderHints[
'known-revision-output'] = $output;
892 $this->logger->debug( __METHOD__ .
': using stashed edit output...' );
897 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
935 if ( !$this->pageState ) {
936 throw new LogicException(
937 'Must call grabCurrentRevision() or prepareContent() '
938 .
'or prepareUpdate() before calling ' . $method
944 if ( !$this->revision ) {
945 throw new LogicException(
946 'Must call prepareContent() or prepareUpdate() before calling ' . $method
952 if ( !$this->revision->getId() ) {
953 throw new LogicException(
954 'Must call prepareUpdate() before calling ' . $method
966 return $this->options[
'created'];
980 return $this->options[
'changed'];
991 if ( $this->pageState[
'oldIsRedirect'] ===
null ) {
993 $rev = $this->pageState[
'oldRevision'];
997 $this->pageState[
'oldIsRedirect'] =
false;
1001 return $this->pageState[
'oldIsRedirect'];
1014 return $this->revision->getSlots();
1025 if ( !$this->slotsUpdate ) {
1028 $this->revision->getSlots(),
1029 $old ? $old->getSlots() :
null
1121 __METHOD__ .
' with the `oldrevision` option being a ' .
1130 '$options["oldrevision"]',
1131 'must be a RevisionRecord (or Revision)'
1134 !isset(
$options[
'triggeringUser'] )
1136 '$options["triggeringUser"]',
1137 'must be a UserIdentity'
1142 '$options["editResult"]',
1143 'must be an EditResult'
1147 throw new InvalidArgumentException(
1148 'Revision must have an ID set for it to be used with prepareUpdate()!'
1152 if ( $this->revision && $this->revision->getId() ) {
1156 throw new LogicException(
1157 'Trying to re-use DerivedPageDataUpdater with revision '
1159 .
', but it\'s already bound to revision '
1160 . $this->revision->getId()
1165 if ( $this->revision
1168 throw new LogicException(
1169 'The Revision provided has mismatching content!'
1176 if ( $this->revision ) {
1177 $oldId = $this->pageState[
'oldId'] ?? 0;
1179 } elseif ( isset( $this->options[
'oldrevision'] ) ) {
1181 $oldRev = $this->options[
'oldrevision'];
1182 $oldId = $oldRev->getId();
1188 if ( $oldId !==
null ) {
1197 $this->options[
'changed'] =
true;
1200 $this->options[
'changed'] =
false;
1203 throw new LogicException(
1204 'The Revision mismatches old revision ID: '
1205 .
'Old ID is ' . $oldId
1216 if ( $this->user !==
null && $this->options[
'changed'] && $this->slotsUpdate ) {
1218 if ( !$this->user->equals(
$user ) ) {
1219 throw new LogicException(
1220 'The Revision provided has a mismatching actor: expected '
1221 . $this->user->getName()
1230 if ( !$this->pageState ) {
1231 $this->pageState = [
1232 'oldIsRedirect' => isset( $this->options[
'oldredirect'] )
1233 && is_bool( $this->options[
'oldredirect'] )
1234 ? $this->options[
'oldredirect']
1236 'oldCountable' => isset( $this->options[
'oldcountable'] )
1237 && is_bool( $this->options[
'oldcountable'] )
1238 ? $this->options[
'oldcountable']
1242 if ( $this->options[
'changed'] ) {
1246 if ( isset( $this->options[
'oldrevision'] ) ) {
1247 $rev = $this->options[
'oldrevision'];
1248 $this->pageState[
'oldRevision'] = $rev;
1253 $this->pageState[
'oldRevision'] =
$revision;
1258 $this->options[
'created'] = ( $this->options[
'created'] ||
1259 ( $this->pageState[
'oldId'] === 0 ) );
1267 if ( !$this->user ) {
1272 if ( $this->renderedRevision ) {
1273 $this->renderedRevision->updateRevision(
$revision );
1277 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
1284 'known-revision-output' =>
$options[
'known-revision-output'] ??
null
1307 $preparedEdit->parserOutputCallback = [ $this,
'getCanonicalParserOutput' ];
1308 $preparedEdit->pstContent = $this->revision->getContent(
SlotRecord::MAIN );
1309 $preparedEdit->newContent =
1313 $preparedEdit->oldContent =
null;
1314 $preparedEdit->revid = $this->revision ? $this->revision->getId() :
null;
1315 $preparedEdit->timestamp = $preparedEdit->output->getCacheTime();
1316 $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1318 return $preparedEdit;
1329 [
'generate-html' => $generateHtml ]
1368 $allUpdates = [ $linksUpdate ];
1374 foreach ( $this->
getSlots()->getSlotRoles() as $role ) {
1377 $handler =
$content->getContentHandler();
1379 $updates = $handler->getSecondaryDataUpdates(
1385 $allUpdates = array_merge( $allUpdates, $updates );
1390 $legacyUpdates =
$content->getSecondaryDataUpdates(
1398 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
1402 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1420 $content = $parentSlot->getContent();
1421 $handler =
$content->getContentHandler();
1423 $updates = $handler->getDeletionUpdates(
1427 $allUpdates = array_merge( $allUpdates, $updates );
1433 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
1437 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1441 $this->hookRunner->onRevisionDataUpdates(
1470 if ( $userParserOptions->matchesForCacheKey( $this->getCanonicalParserOptions() ) ) {
1486 'recursive' => $this->options[
'changed'],
1488 'defer' => DeferredUpdates::POSTSEND
1492 if ( $this->rcWatchCategoryMembership
1494 && ( $this->options[
'changed'] || $this->options[
'created'] )
1495 && !$this->options[
'restored']
1500 $this->jobQueueGroup->lazyPush(
1503 $this->revision->getTimestamp()
1512 $this->hookRunner->onArticleEditUpdates(
$wikiPage, $editInfo, $this->options[
'changed'] );
1515 if ( $this->hookRunner->onArticleEditUpdatesDeleteFromRecentchanges(
$wikiPage ) ) {
1517 if ( mt_rand( 0, 9 ) == 0 ) {
1524 $shortTitle =
$title->getDBkey();
1526 if ( !
$title->exists() ) {
1527 wfDebug( __METHOD__ .
": Page doesn't exist any more, bailing out" );
1535 $this->options[
'oldcountable'] ===
'no-change' ||
1536 ( !$this->options[
'changed'] && !$this->options[
'moved'] )
1539 } elseif ( $this->options[
'created'] ) {
1541 } elseif ( $this->options[
'oldcountable'] !==
null ) {
1543 - (int)$this->options[
'oldcountable'];
1544 } elseif ( isset( $this->pageState[
'oldCountable'] ) ) {
1546 - (int)$this->pageState[
'oldCountable'];
1550 $edits = $this->options[
'changed'] ? 1 : 0;
1551 $pages = $this->options[
'created'] ? 1 : 0;
1554 [
'edits' => $edits,
'articles' => $good,
'pages' => $pages ]
1567 if ( $this->options[
'changed']
1569 && $shortTitle != $legacyUser->getTitleKey()
1571 ->getPermissionManager()
1572 ->userHasRight( $legacyUser,
'nominornewtalk' ) )
1575 if ( !$recipient ) {
1576 wfDebug( __METHOD__ .
": invalid username" );
1581 if ( $this->hookRunner->onArticleEditUpdateNewTalk(
$wikiPage, $recipient ) ) {
1584 ->getTalkPageNotificationManager();
1587 $talkPageNotificationManager->setUserHasNewMessages( $recipient, $revRecord );
1588 } elseif ( $recipient->isRegistered() ) {
1589 $talkPageNotificationManager->setUserHasNewMessages( $recipient, $revRecord );
1591 wfDebug( __METHOD__ .
": don't need to notify a nonexistent user" );
1602 $this->messageCache->updateMessageOverride(
$title, $mainContent );
1606 if ( $this->options[
'created'] ) {
1608 } elseif ( $this->options[
'changed'] ) {
1610 } elseif ( $this->options[
'restored'] ) {
1612 "DerivedPageDataUpdater:restore:page:$id"
1623 $this->loadbalancerFactory->getLocalDomainID()
1638 if ( $this->options[
'editResult'] ===
null ) {
1642 $editResult = $this->options[
'editResult'];
1643 if ( !$editResult->isRevert() ) {
1647 if ( $this->options[
'approved'] ) {
1649 $this->jobQueueGroup->lazyPush(
1651 $this->revision->getId(),
1652 $this->options[
'editResult']
1657 $this->editResultCache->set(
1658 $this->revision->getId(),
1659 $this->options[
'editResult']
1678 $options += [
'recursive' =>
false,
'defer' => false ];
1679 $deferValues = [
false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1680 if ( !in_array(
$options[
'defer'], $deferValues,
true ) ) {
1681 throw new InvalidArgumentException(
'Invalid value for defer: ' .
$options[
'defer'] );
1684 $triggeringUser = $this->options[
'triggeringUser'] ??
$this->user;
1685 if ( !$triggeringUser instanceof
User ) {
1688 $causeAction = $this->options[
'causeAction'] ??
'unknown';
1689 $causeAgent = $this->options[
'causeAgent'] ??
'unknown';
1696 $this->loadbalancerFactory,
1701 [
'recursive' =>
$options[
'recursive'] ]
1703 $update->setCause( $causeAction, $causeAgent );
1705 if (
$options[
'defer'] ===
false ) {
1727 $timestamp = $this->options[
'newrev'] ? $this->revision->getTimestamp()
1728 : $output->getCacheTime();
1729 $this->parserCache->save(
1731 $timestamp, $this->revision->getId()
getCanonicalParserOutput()
Set options of the Parser.
Library for creating and parsing MW-style timestamps.
A content handler knows how do deal with a specific type of content on a wiki page.
static onArticleCreate(Title $title)
The onArticle*() functions are supposed to be a kind of hooks which should be called whenever any of ...
loadPageData( $from='fromdb')
Load the object from a given source by title.
getRevisionRecord()
Get the latest revision.
pageExisted()
Determines whether the page being edited already existed.
getParentRevision()
Returns the parent revision of the new revision wrapped by this update.
getRevision()
Returns the update's target revision - that is, the revision that will be the current revision after ...
string $articleCountMethod
see $wgArticleCountMethod
prepareContent(User $user, RevisionSlotsUpdate $slotsUpdate, $useStash=true)
Prepare updates based on an update which has not yet been saved.
getSlotParserOutput( $role, $generateHtml=true)
Job for pruning recent changes.
prepareUpdate(RevisionRecord $revision, array $options=[])
Prepare derived data updates targeting the given Revision.
IContentHandlerFactory $contentHandlerFactory
setArticleCountMethod( $articleCountMethod)
static invalidateModuleCache(Title $title, ?RevisionRecord $old, ?RevisionRecord $new, $domain)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
getSlots()
Returns the slots of the target revision, after PST.
static attemptUpdate(DeferrableUpdate $update, ILBFactory $lbFactory)
Attempt to run an update with the appropriate transaction round state it expects.
RevisionRecord null $parentRevision
setRcWatchCategoryMembership( $rcWatchCategoryMembership)
Abstraction for ResourceLoader modules which pull from wiki pages.
Database independant search index updater.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Class representing a MediaWiki article and history.
RevisionRenderer $revisionRenderer
Class the manages updates of *_link tables as well as similar extension-managed tables.
static newFromName( $name, $validate='valid')
$options
Stores (most of) the $options parameter of prepareUpdate().
RenderedRevision $renderedRevision
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
RevisionSlotsUpdate null $slotsUpdate
static newSpec(int $revertRevisionId, EditResult $editResult)
Returns a JobSpecification for this job.
Interface for database access objects.
equals(LinkTarget $title)
Compare with another title.
getCanonicalParserOptions()
doUpdates()
Do standard updates after page edit, purge, or import.
RevisionStore $revisionStore
assertHasRevision( $method)
SlotRoleRegistry $slotRoleRegistry
isReusableFor(UserIdentity $user=null, RevisionRecord $revision=null, RevisionSlotsUpdate $slotsUpdate=null, $parentId=null)
Checks whether this DerivedPageDataUpdater can be re-used for running updates targeting the given rev...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
setLogger(LoggerInterface $logger)
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
getRevisionSlotsUpdate()
Returns the RevisionSlotsUpdate for this updater.
Class for managing the deferral of updates within the scope of a PHP script invocation.
getContentHandler( $role)
static isIP( $name)
Does the string match an anonymous IP address?
getContentModel( $role)
Returns the content model of the given slot.
wasRedirect()
Whether the page was a redirect before the edit.
static factory(array $deltas)
Class for handling updates to the site_stats table.
isCreation()
Whether the edit creates the page.
assertTransition( $newStage)
Asserts that a transition to the given stage is possible, without performing it.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
isCountable( $editInfo=false)
Determine whether a page would be suitable for being counted as an article in the site_stats table ba...
Job to add recent change entries mentioning category membership changes.
isContentDeleted()
Whether the content is deleted and thus not visible to the public.
__construct(WikiPage $wikiPage, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, SlotRoleRegistry $slotRoleRegistry, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, MessageCache $messageCache, Language $contLang, ILBFactory $loadbalancerFactory, IContentHandlerFactory $contentHandlerFactory, HookContainer $hookContainer, EditResultCache $editResultCache)
isContentPrepared()
Whether prepareUpdate() or prepareContent() have been called on this instance.
Update object handling the cleanup of links tables after a page was deleted.
ILBFactory $loadbalancerFactory
getRemovedSlotRoles()
Returns the role names of the slots removed by the new revision.
array $pageState
The state of the relevant row in page table before the edit.
MessageCache $messageCache
doTransition( $newStage)
Transition function for managing the life cycle of this instances.
getModifiedSlotRoles()
Returns the role names of the slots modified by the new revision, not including removed roles.
Base interface for content objects.
JobQueueGroup $jobQueueGroup
Represents a title within MediaWiki.
getRawContent( $role)
Returns the content of the given slot, with no audience checks.
getSecondaryDataUpdates( $recursive=false)
EditResultCache $editResultCache
Cache for ParserOutput objects corresponding to the latest page revisions.
RevisionRecord null $revision
getRawSlot( $role)
Returns the slot, modified or inherited, after PST, with no audience checks applied.
Exception thrown when an unregistered content model is requested.
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (e.g.
static newSpec(Title $title, $revisionTimestamp)
isChange()
Whether the edit created, or should create, a new revision (that is, it's not a null-edit).
assertHasPageState( $method)
Interface that deferrable updates should implement.
Update object handling the cleanup of secondary data after a page was edited.
getTouchedSlotRoles()
Returns the role names of the slots touched by the new revision, including removed roles.
static onArticleEdit(Title $title, $revRecord=null, $slotsChanged=null)
Purge caches on page update etc.
string $stage
A stage identifier for managing the life cycle of this instance.
maybeEnqueueRevertedTagUpdateJob()
If the edit was a revert and it is considered "approved", enqueues the RevertedTagUpdateJob for it.
isRedirect()
Tests if the article content represents a redirect.
A handle for managing updates for derived page data on edit, import, purge, etc.
Cache of messages that are defined by MediaWiki namespace pages or by hooks.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
bool $rcWatchCategoryMembership
see $wgRCWatchCategoryMembership
revisionIsRedirect(RevisionRecord $rev)
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
grabCurrentRevision()
Returns the revision that was the page's current revision when grabCurrentRevision() was first called...
isUpdatePrepared()
Whether prepareUpdate() has been called on this instance.
Job for deferring the execution of RevertedTagUpdate.
static newFromUser( $user)
Get a ParserOptions object from a given user.
Class to handle enqueueing of background jobs.