Go to the documentation of this file.
33 use InvalidArgumentException;
55 use Psr\Log\LoggerAwareInterface;
56 use Psr\Log\LoggerInterface;
57 use Psr\Log\NullLogger;
65 use Wikimedia\Assert\Assert;
169 'oldrevision' =>
null,
170 'oldcountable' =>
null,
171 'oldredirect' =>
null,
172 'triggeringUser' =>
null,
175 'causeAction' =>
null,
176 'causeAgent' =>
null,
251 'knows-current' =>
true,
252 'has-content' =>
true,
253 'has-revision' =>
true,
256 'knows-current' =>
true,
257 'has-content' =>
true,
258 'has-revision' =>
true,
261 'has-content' =>
true,
262 'has-revision' =>
true,
265 'has-revision' =>
true,
304 $this->logger =
new NullLogger();
326 $this->stage = $newStage;
341 if ( empty( self::$transitions[$this->stage][$newStage] ) ) {
342 throw new LogicException(
"Cannot transition from {$this->stage} to $newStage" );
367 throw new InvalidArgumentException(
'$parentId should match the parent of $revision' );
383 if ( $this->pageState
391 if ( $this->pageState
392 && $parentId !==
null
393 && $this->pageState[
'oldId'] !== $parentId
399 if ( $this->slotsUpdate
437 return $this->wikiPage->getTitle();
458 return $this->pageState[
'oldId'] > 0;
473 if ( $this->parentRevision ) {
477 if ( !$this->pageState[
'oldId'] ) {
484 $flags = $this->
useMaster() ? RevisionStore::READ_LATEST : 0;
485 $this->parentRevision = $oldId
486 ? $this->revisionStore->getRevisionById( $oldId, $flags )
513 if ( $this->pageState ) {
514 return $this->pageState[
'oldRevision'];
526 $current = $rev ? $rev->getRevisionRecord() :
null;
529 'oldRevision' => $current,
530 'oldId' => $rev ? $rev->getId() : 0,
537 return $this->pageState[
'oldRevision'];
546 return $this->revision !==
null;
557 return $this->revision !==
null && $this->revision->getId() !==
null;
565 return $this->wikiPage->getId();
574 if ( $this->revision ) {
592 return $this->
getSlots()->getSlot( $role );
604 return $this->
getRawSlot( $role )->getContent();
614 return $this->
getRawSlot( $role )->getModel();
628 return $this->wikiPage->wasLoadedFrom( self::READ_LATEST );
637 if ( !$this->
getTitle()->isContentPage() ) {
653 if ( $this->articleCountMethod ===
'link' ) {
662 foreach ( $this->
getSlots()->getSlotRoles() as $role ) {
663 $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
664 if ( $roleHandler->supportsArticleCount() ) {
667 if (
$content->isCountable( $hasLinks ) ) {
684 return $mainContent->isRedirect();
696 return $mainContent->isRedirect();
728 if ( $this->slotsUpdate ) {
729 if ( !$this->user ) {
730 throw new LogicException(
731 'Unexpected state: $this->slotsUpdate was initialized, '
732 .
'but $this->user was not.'
737 throw new LogicException(
'Can\'t call prepareContent() again for different user! '
738 .
'Expected ' . $this->user->getName() .
', got ' .
$user->
getName()
742 if ( !$this->slotsUpdate->hasSameUpdates(
$slotsUpdate ) ) {
743 throw new LogicException(
744 'Can\'t call prepareContent() again with different slot content!'
759 $stashedEdit =
false;
764 $stashedEdit = $editStash->checkCache(
786 $this->revision->setUser(
$user );
789 $oldCallback = $userPopts->getCurrentRevisionCallback();
790 $userPopts->setCurrentRevisionCallback(
791 function (
Title $parserTitle, $parser =
false ) use (
$title, $oldCallback ) {
793 $legacyRevision =
new Revision( $this->revision );
794 return $legacyRevision;
796 return call_user_func( $oldCallback, $parserTitle, $parser );
801 $pstContentSlots = $this->revision->getSlots();
806 if ( $slot->isInherited() ) {
815 $pstContent =
$content->preSaveTransform(
$title, $this->user, $userPopts );
819 $pstContentSlots->setSlot( $pstSlot );
823 $pstContentSlots->removeSlot( $role );
832 if ( !$this->options[
'changed'] ) {
854 if ( $stashedEdit ) {
856 $output = $stashedEdit->output;
858 $output->setCacheTime( $stashedEdit->timestamp );
860 $renderHints[
'known-revision-output'] =
$output;
862 $this->logger->debug( __METHOD__ .
': using stashed edit output...' );
867 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
905 if ( !$this->pageState ) {
906 throw new LogicException(
907 'Must call grabCurrentRevision() or prepareContent() '
908 .
'or prepareUpdate() before calling ' . $method
914 if ( !$this->revision ) {
915 throw new LogicException(
916 'Must call prepareContent() or prepareUpdate() before calling ' . $method
922 if ( !$this->revision->getId() ) {
923 throw new LogicException(
924 'Must call prepareUpdate() before calling ' . $method
936 return $this->options[
'created'];
950 return $this->options[
'changed'];
961 if ( $this->pageState[
'oldIsRedirect'] ===
null ) {
963 $rev = $this->pageState[
'oldRevision'];
967 $this->pageState[
'oldIsRedirect'] =
false;
971 return $this->pageState[
'oldIsRedirect'];
984 return $this->revision->getSlots();
995 if ( !$this->slotsUpdate ) {
998 $this->revision->getSlots(),
999 $old ? $old->getSlots() : null
1087 '$options["oldrevision"]',
1088 'must be a RevisionRecord (or Revision)'
1091 !isset(
$options[
'triggeringUser'] )
1093 '$options["triggeringUser"]',
1094 'must be a UserIdentity'
1098 throw new InvalidArgumentException(
1099 'Revision must have an ID set for it to be used with prepareUpdate()!'
1103 if ( $this->revision && $this->revision->getId() ) {
1107 throw new LogicException(
1108 'Trying to re-use DerivedPageDataUpdater with revision '
1110 .
', but it\'s already bound to revision '
1111 . $this->revision->getId()
1116 if ( $this->revision
1119 throw new LogicException(
1120 'The Revision provided has mismatching content!'
1127 if ( $this->revision ) {
1128 $oldId = $this->pageState[
'oldId'] ?? 0;
1130 } elseif ( isset( $this->options[
'oldrevision'] ) ) {
1132 $oldRev = $this->options[
'oldrevision'];
1133 $oldId = $oldRev->getId();
1139 if ( $oldId !==
null ) {
1148 $this->options[
'changed'] =
true;
1151 $this->options[
'changed'] =
false;
1154 throw new LogicException(
1155 'The Revision mismatches old revision ID: '
1156 .
'Old ID is ' . $oldId
1167 if ( $this->user !==
null && $this->options[
'changed'] && $this->slotsUpdate ) {
1169 if ( !$this->user->equals(
$user ) ) {
1170 throw new LogicException(
1171 'The Revision provided has a mismatching actor: expected '
1172 . $this->user->getName()
1181 if ( !$this->pageState ) {
1182 $this->pageState = [
1183 'oldIsRedirect' => isset( $this->options[
'oldredirect'] )
1184 && is_bool( $this->options[
'oldredirect'] )
1185 ? $this->options[
'oldredirect']
1187 'oldCountable' => isset( $this->options[
'oldcountable'] )
1188 && is_bool( $this->options[
'oldcountable'] )
1189 ? $this->options[
'oldcountable']
1193 if ( $this->options[
'changed'] ) {
1197 if ( isset( $this->options[
'oldrevision'] ) ) {
1198 $rev = $this->options[
'oldrevision'];
1199 $this->pageState[
'oldRevision'] = $rev instanceof
Revision
1206 $this->pageState[
'oldRevision'] =
$revision;
1211 $this->options[
'created'] = ( $this->options[
'created'] ||
1212 ( $this->pageState[
'oldId'] === 0 ) );
1220 if ( !$this->user ) {
1225 if ( $this->renderedRevision ) {
1226 $this->renderedRevision->updateRevision(
$revision );
1230 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
1237 'known-revision-output' =>
$options[
'known-revision-output'] ??
null
1260 $preparedEdit->parserOutputCallback = [ $this,
'getCanonicalParserOutput' ];
1261 $preparedEdit->pstContent = $this->revision->getContent(
SlotRecord::MAIN );
1262 $preparedEdit->newContent =
1266 $preparedEdit->oldContent =
null;
1267 $preparedEdit->revid = $this->revision ? $this->revision->getId() :
null;
1268 $preparedEdit->timestamp = $preparedEdit->output->getCacheTime();
1269 $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1271 return $preparedEdit;
1282 [
'generate-html' => $generateHtml ]
1321 $allUpdates = [ $linksUpdate ];
1327 foreach ( $this->
getSlots()->getSlotRoles() as $role ) {
1330 $handler =
$content->getContentHandler();
1332 $updates = $handler->getSecondaryDataUpdates(
1338 $allUpdates = array_merge( $allUpdates, $updates );
1343 $legacyUpdates =
$content->getSecondaryDataUpdates(
1351 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
1355 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1373 $content = $parentSlot->getContent();
1374 $handler =
$content->getContentHandler();
1376 $updates = $handler->getDeletionUpdates(
1380 $allUpdates = array_merge( $allUpdates, $updates );
1386 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
1390 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1395 'RevisionDataUpdates',
1420 $legacyRevision =
new Revision( $this->revision );
1426 if ( $userParserOptions->matchesForCacheKey( $this->getCanonicalParserOptions() ) ) {
1446 'recursive' => $this->options[
'changed']
1455 if ( $this->rcWatchCategoryMembership
1457 && ( $this->options[
'changed'] || $this->options[
'created'] )
1458 && !$this->options[
'restored']
1463 $this->jobQueueGroup->lazyPush(
1466 $this->revision->getTimestamp()
1476 [ &
$wikiPage, &$editInfo, $this->options[
'changed'] ] );
1481 if ( mt_rand( 0, 9 ) == 0 ) {
1488 $shortTitle =
$title->getDBkey();
1490 if ( !
$title->exists() ) {
1491 wfDebug( __METHOD__ .
": Page doesn't exist any more, bailing out\n" );
1499 $this->options[
'oldcountable'] ===
'no-change' ||
1500 ( !$this->options[
'changed'] && !$this->options[
'moved'] )
1503 } elseif ( $this->options[
'created'] ) {
1505 } elseif ( $this->options[
'oldcountable'] !==
null ) {
1507 - (int)$this->options[
'oldcountable'];
1508 } elseif ( isset( $this->pageState[
'oldCountable'] ) ) {
1510 - (int)$this->pageState[
'oldCountable'];
1514 $edits = $this->options[
'changed'] ? 1 : 0;
1515 $pages = $this->options[
'created'] ? 1 : 0;
1518 [
'edits' => $edits,
'articles' => $good,
'pages' => $pages ]
1531 if ( $this->options[
'changed']
1533 && $shortTitle != $legacyUser->getTitleKey()
1535 ->getPermissionManager()
1536 ->userHasRight( $legacyUser,
'nominornewtalk' ) )
1539 if ( !$recipient ) {
1540 wfDebug( __METHOD__ .
": invalid username\n" );
1548 $recipient->setNewtalk(
true, $legacyRevision );
1549 } elseif ( $recipient->isLoggedIn() ) {
1550 $recipient->setNewtalk(
true, $legacyRevision );
1552 wfDebug( __METHOD__ .
": don't need to notify a nonexistent user\n" );
1563 $this->messageCache->updateMessageOverride(
$title, $mainContent );
1567 if ( $this->options[
'created'] ) {
1569 } elseif ( $this->options[
'changed'] ) {
1574 $oldLegacyRevision = $oldRevision ?
new Revision( $oldRevision ) :
null;
1581 $this->loadbalancerFactory->getLocalDomainID()
1601 $options += [
'recursive' =>
false,
'defer' => false ];
1603 if ( !in_array(
$options[
'defer'], $deferValues,
true ) ) {
1604 throw new InvalidArgumentException(
'Invalid value for defer: ' .
$options[
'defer'] );
1608 $triggeringUser = $this->options[
'triggeringUser'] ??
$this->user;
1609 if ( !$triggeringUser instanceof
User ) {
1612 $causeAction = $this->options[
'causeAction'] ??
'unknown';
1613 $causeAgent = $this->options[
'causeAgent'] ??
'unknown';
1614 $legacyRevision =
new Revision( $this->revision );
1616 foreach ( $updates as $update ) {
1618 $update->setCause( $causeAction, $causeAgent );
1621 $update->setRevision( $legacyRevision );
1622 $update->setTriggeringUser( $triggeringUser );
1626 if (
$options[
'defer'] ===
false ) {
1628 $this->loadbalancerFactory->commitMasterChanges( __METHOD__ );
1629 foreach ( $updates as $update ) {
1633 foreach ( $updates as $update ) {
1654 $timestamp = $this->options[
'newrev'] ? $this->revision->getTimestamp()
1656 $this->parserCache->save(
1658 $timestamp, $this->revision->getId()
getCanonicalParserOutput()
Set options of the Parser.
A content handler knows how do deal with a specific type of content on a wiki page.
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
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.
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.
setArticleCountMethod( $articleCountMethod)
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.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
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')
Static factory method for creation from username.
getRevision()
Get the latest revision.
$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
Interface for database access objects.
equals(LinkTarget $title)
Compare with another title.
static onArticleEdit(Title $title, Revision $revision=null, $slotsChanged=null)
Purge caches on page update etc.
getCanonicalParserOptions()
doUpdates()
Do standard updates after page edit, purge, or import.
Abstract base class for update jobs that do something with some secondary data extracted from article...
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...
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 deferred updates.
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)
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
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.
setTransactionRoundRequirement( $mode)
static array[] $transitions
Transition table for managing the life cycle of DerivedPageDateUpdater instances.
isContentDeleted()
Whether the content is deleted and thus not visible to the public.
static invalidateModuleCache(Title $title, Revision $old=null, Revision $new=null, $domain)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
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.
Deferrable Update for closure/callback.
JobQueueGroup $jobQueueGroup
Represents a title within MediaWiki.
getRawContent( $role)
Returns the content of the given slot, with no audience checks.
__construct(WikiPage $wikiPage, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, SlotRoleRegistry $slotRoleRegistry, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, MessageCache $messageCache, Language $contLang, ILBFactory $loadbalancerFactory)
getSecondaryDataUpdates( $recursive=false)
boolean $rcWatchCategoryMembership
see $wgRCWatchCategoryMembership
RevisionRecord null $revision
getRawSlot( $role)
Returns the slot, modified or inherited, after PST, with no audience checks applied.
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (such as updating link tables).
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.
getTouchedSlotRoles()
Returns the role names of the slots touched by the new revision, including removed roles.
string $stage
A stage identifier for managing the life cycle of this instance.
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 a callable update.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
revisionIsRedirect(RevisionRecord $rev)
Internationalisation code.
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.
static newFromUser( $user)
Get a ParserOptions object from a given user.
Class to handle enqueueing of background jobs.