use MediaWiki\Edit\PreparedEdit;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Revision\RevisionRenderer;
use MediaWiki\Storage\DerivedPageDataUpdater;
use MediaWiki\Storage\PageUpdater;
use MediaWiki\Storage\RevisionRecord;
use MediaWiki\Storage\RevisionSlotsUpdate;
use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SlotRecord;
use Wikimedia\Assert\Assert;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IDatabase;
return MediaWikiServices::getInstance()->getRevisionStore();
}
+ /**
+ * @return RevisionRenderer
+ */
+ private function getRevisionRenderer() {
+ return MediaWikiServices::getInstance()->getRevisionRenderer();
+ }
+
/**
* @return ParserCache
*/
return null;
}
+ /**
+ * Get the latest revision
+ * @return RevisionRecord|null
+ */
+ public function getRevisionRecord() {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->getRevisionRecord();
+ }
+ return null;
+ }
+
/**
* Get the content of the current revision. No side-effects...
*
// links.
$hasLinks = (bool)count( $editInfo->output->getLinks() );
} else {
+ // NOTE: keep in sync with revisionRenderer::getLinkCount
$hasLinks = (bool)wfGetDB( DB_REPLICA )->selectField( 'pagelinks', 1,
[ 'pl_from' => $this->getId() ], __METHOD__ );
}
* The parser cache will be used if possible. Cache misses that result
* in parser runs are debounced with PoolCounter.
*
+ * XXX merge this with updateParserCache()?
+ *
* @since 1.19
* @param ParserOptions $parserOptions ParserOptions to use for the parse operation
* @param null|int $oldid Revision ID to get the text from, passing null or 0 will
$derivedDataUpdater = new DerivedPageDataUpdater(
$this, // NOTE: eventually, PageUpdater should not know about WikiPage
$this->getRevisionStore(),
+ $this->getRevisionRenderer(),
$this->getParserCache(),
JobQueueGroup::singleton(),
MessageCache::singleton(),
MediaWikiServices::getInstance()->getContentLanguage(),
- LoggerFactory::getInstance( 'SaveParse' )
+ MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
);
$derivedDataUpdater->setRcWatchCategoryMembership( $wgRCWatchCategoryMembership );
$updater->prepareContent( $user, $slots, $useCache );
if ( $revision ) {
- $updater->prepareUpdate( $revision );
+ $updater->prepareUpdate(
+ $revision,
+ [
+ 'causeAction' => 'prepare-edit',
+ 'causeAgent' => $user->getName(),
+ ]
+ );
}
}
* - null: if created is false, don't update the article count; if created
* is true, do update the article count
* - 'no-change': don't update the article count, ever
+ * - causeAction: an arbitrary string identifying the reason for the update.
+ * See DataUpdate::getCauseAction(). (default 'edit-page')
+ * - causeAgent: name of the user who caused the update. See DataUpdate::getCauseAgent().
+ * (string, defaults to the passed user)
*/
public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
+ $options += [
+ 'causeAction' => 'edit-page',
+ 'causeAgent' => $user->getName(),
+ ];
+
$revision = $revision->getRevisionRecord();
$updater = $this->getDerivedDataUpdater( $user, $revision );
$updater->doUpdates();
}
+ /**
+ * Update the parser cache.
+ *
+ * @note This is a temporary workaround until there is a proper data updater class.
+ * It will become deprecated soon.
+ *
+ * @param array $options
+ * - causeAction: an arbitrary string identifying the reason for the update.
+ * See DataUpdate::getCauseAction(). (default 'edit-page')
+ * - causeAgent: name of the user who caused the update (string, defaults to the
+ * user who created the revision)
+ * @since 1.32
+ */
+ public function updateParserCache( array $options = [] ) {
+ $revision = $this->getRevisionRecord();
+ if ( !$revision || !$revision->getId() ) {
+ LoggerFactory::getInstance( 'wikipage' )->info(
+ __METHOD__ . 'called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
+ );
+ return;
+ }
+ $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) );
+
+ $updater = $this->getDerivedDataUpdater( $user, $revision );
+ $updater->prepareUpdate( $revision, $options );
+ $updater->doParserCacheUpdate();
+ }
+
+ /**
+ * Do secondary data updates (such as updating link tables).
+ * Secondary data updates are only a small part of the updates needed after saving
+ * a new revision; normally PageUpdater::doUpdates should be used instead (which includes
+ * secondary data updates). This method is provided for partial purges.
+ *
+ * @note This is a temporary workaround until there is a proper data updater class.
+ * It will become deprecated soon.
+ *
+ * @param array $options
+ * - recursive (bool, default true): whether to do a recursive update (update pages that
+ * depend on this page, e.g. transclude it). This will set the $recursive parameter of
+ * Content::getSecondaryDataUpdates. Typically this should be true unless the update
+ * was something that did not really change the page, such as a null edit.
+ * - triggeringUser: The user triggering the update (UserIdentity, defaults to the
+ * user who created the revision)
+ * - causeAction: an arbitrary string identifying the reason for the update.
+ * See DataUpdate::getCauseAction(). (default 'unknown')
+ * - causeAgent: name of the user who caused the update (string, default 'unknown')
+ * - defer: one of the DeferredUpdates constants, or false to run immediately (default: false).
+ * Note that even when this is set to false, some updates might still get deferred (as
+ * some update might directly add child updates to DeferredUpdates).
+ * - transactionTicket: a transaction ticket from LBFactory::getEmptyTransactionTicket(),
+ * only when defer is false (default: null)
+ * @since 1.32
+ */
+ public function doSecondaryDataUpdates( array $options = [] ) {
+ $options['recursive'] = $options['recursive'] ?? true;
+ $revision = $this->getRevisionRecord();
+ if ( !$revision || !$revision->getId() ) {
+ LoggerFactory::getInstance( 'wikipage' )->info(
+ __METHOD__ . 'called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
+ );
+ return;
+ }
+ $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) );
+
+ $updater = $this->getDerivedDataUpdater( $user, $revision );
+ $updater->prepareUpdate( $revision, $options );
+ $updater->doSecondaryDataUpdates( $options );
+ }
+
/**
* Update the article's restriction field, and leave a log entry.
* This works for protection both existing and non-existing pages.
* Do some database updates after deletion
*
* @param int $id The page_id value of the page being deleted
- * @param Content|null $content Optional page content to be used when determining
+ * @param Content|null $content Page content to be used when determining
* the required updates. This may be needed because $this->getContent()
* may already return null when the page proper was deleted.
- * @param Revision|null $revision The latest page revision
+ * @param RevisionRecord|Revision|null $revision The current page revision at the time of
+ * deletion, used when determining the required updates. This may be needed because
+ * $this->getRevision() may already return null when the page proper was deleted.
* @param User|null $user The user that caused the deletion
*/
public function doDeleteUpdates(
$id, Content $content = null, Revision $revision = null, User $user = null
) {
+ if ( $id !== $this->getId() ) {
+ throw new InvalidArgumentException( 'Mismatching page ID' );
+ }
+
try {
$countable = $this->isCountable();
} catch ( Exception $ex ) {
) );
// Delete pagelinks, update secondary indexes, etc
- $updates = $this->getDeletionUpdates( $content );
+ $updates = $this->getDeletionUpdates(
+ $revision ? $revision->getRevisionRecord() : $content
+ );
foreach ( $updates as $update ) {
DeferredUpdates::addUpdate( $update );
}
* updates should remove any information about this page from secondary data
* stores such as links tables.
*
- * @param Content|null $content Optional Content object for determining the
- * necessary updates.
+ * @param RevisionRecord|Content|null $rev The revision being deleted. Also accepts a Content
+ * object for backwards compatibility.
* @return DeferrableUpdate[]
*/
- 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.
+ public function getDeletionUpdates( $rev = null ) {
+ if ( !$rev ) {
+ wfDeprecated( __METHOD__ . ' without a RevisionRecord', '1.32' );
+
try {
- $content = $this->getContent( Revision::RAW );
+ $rev = $this->getRevisionRecord();
} catch ( Exception $ex ) {
// If we can't load the content, something is wrong. Perhaps that's why
// the user is trying to delete the page, so let's not fail in that case.
// Note that doDeleteArticleReal() will already have logged an issue with
// loading the content.
+ wfDebug( __METHOD__ . ' failed to load current revision of page ' . $this->getId() );
}
}
- if ( !$content ) {
- $updates = [];
+ if ( !$rev ) {
+ $slotContent = [];
+ } elseif ( $rev instanceof Content ) {
+ wfDeprecated( __METHOD__ . ' with a Content object instead of a RevisionRecord', '1.32' );
+
+ $slotContent = [ 'main' => $rev ];
} else {
- $updates = $content->getDeletionUpdates( $this );
+ $slotContent = array_map( function ( SlotRecord $slot ) {
+ return $slot->getContent( Revision::RAW );
+ }, $rev->getSlots()->getSlots() );
}
- Hooks::run( 'WikiPageDeletionUpdates', [ $this, $content, &$updates ] );
- return $updates;
+ $allUpdates = [ new LinksDeletionUpdate( $this ) ];
+
+ // NOTE: once Content::getDeletionUpdates() is removed, we only need to content
+ // model here, not the content object!
+ // TODO: consolidate with similar logic in DerivedPageDataUpdater::getSecondaryDataUpdates()
+ /** @var Content $content */
+ foreach ( $slotContent as $role => $content ) {
+ $handler = $content->getContentHandler();
+
+ $updates = $handler->getDeletionUpdates(
+ $this->getTitle(),
+ $role
+ );
+ $allUpdates = array_merge( $allUpdates, $updates );
+
+ // TODO: remove B/C hack in 1.32!
+ $legacyUpdates = $content->getDeletionUpdates( $this );
+
+ // HACK: filter out redundant and incomplete LinksDeletionUpdate
+ $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
+ return !( $update instanceof LinksDeletionUpdate );
+ } );
+
+ $allUpdates = array_merge( $allUpdates, $legacyUpdates );
+ }
+
+ Hooks::run( 'PageDeletionDataUpdates', [ $this->getTitle(), $rev, &$allUpdates ] );
+
+ // TODO: hard deprecate old hook in 1.33
+ Hooks::run( 'WikiPageDeletionUpdates', [ $this, $content, &$allUpdates ] );
+ return $allUpdates;
}
/**