Merge "Make EditPage use PageUpdater"

This commit is contained in:
jenkins-bot 2021-09-16 20:55:33 +00:00 committed by Gerrit Code Review
commit 5ebdc642fc
4 changed files with 280 additions and 217 deletions

View file

@ -55,6 +55,7 @@ use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\RevisionStoreRecord;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Storage\EditResult;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserNameUtils;
use MediaWiki\Watchlist\WatchlistManager;
@ -1674,15 +1675,15 @@ class EditPage implements IEditObject {
* @return Status
*/
public function attemptSave( &$resultDetails = false ) {
// TODO: MCR:
// * treat $this->minoredit like $this->markAsBot and check isAllowed( 'minoredit' )!
// * add $this->autopatrol like $this->markAsBot and check isAllowed( 'autopatrol' )!
// This is needed since PageUpdater no longer checks these rights!
// Allow bots to exempt some edits from bot flagging
$markAsBot = $this->markAsBot
&& $this->permManager->userHasRight( $this->context->getUser(), 'bot' );
$status = $this->internalAttemptSave( $resultDetails, $markAsBot );
&& $this->context->getAuthority()->isAllowed( 'bot' );
// Allow trusted users to mark some edits as minor
$markAsMinor = $this->minoredit && !$this->isNew
&& $this->context->getAuthority()->isAllowed( 'minoredit' );
$status = $this->internalAttemptSave( $resultDetails, $markAsBot, $markAsMinor );
$this->getHookRunner()->onEditPage__attemptSave_after( $this, $status, $resultDetails );
@ -1893,6 +1894,7 @@ class EditPage implements IEditObject {
* revision is a redirect.
* @param bool $markAsBot True if edit is being made under the bot right
* and the bot wishes the edit to be marked as such.
* @param bool $markAsMinor True if edit should be marked as minor.
*
* @return Status Status object, possibly with a message, but always with
* one of the AS_* constants in $status->value,
@ -1904,7 +1906,9 @@ class EditPage implements IEditObject {
* AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some
* time.
*/
public function internalAttemptSave( &$result, $markAsBot = false ) {
public function internalAttemptSave( &$result, $markAsBot = false, $markAsMinor = false ) {
global $wgUseNPPatrol, $wgUseRCPatrol;
if ( !$this->getHookRunner()->onEditPage__attemptSave( $this ) ) {
wfDebug( "Hook 'EditPage::attemptSave' aborted article saving" );
$status = Status::newFatal( 'hookaborted' );
@ -2051,7 +2055,33 @@ class EditPage implements IEditObject {
$this->page->loadPageData( 'fromdbmaster' );
$new = !$this->page->exists();
$flags = EDIT_AUTOSUMMARY |
( $new ? EDIT_NEW : EDIT_UPDATE ) |
( $markAsMinor ? EDIT_MINOR : 0 ) |
( $markAsBot ? EDIT_FORCE_BOT : 0 );
if ( $new ) {
$content = $textbox_content;
$result['sectionanchor'] = '';
if ( $this->section == 'new' ) {
if ( $this->sectiontitle !== '' ) {
// Insert the section title above the content.
$content = $content->addSectionHeader( $this->sectiontitle );
} elseif ( $this->summary !== '' ) {
// Insert the section title above the content.
$content = $content->addSectionHeader( $this->summary );
}
list( $newSectionSummary, $anchor ) = $this->newSectionSummary();
$this->summary = $newSectionSummary;
$result['sectionanchor'] = $anchor;
}
$pageUpdater = $this->page->newPageUpdater( $user );
$pageUpdater->setContent( SlotRecord::MAIN, $content );
$pageUpdater->prepareUpdate( $flags );
// BEGINNING OF MIGRATION TO EDITCONSTRAINT SYSTEM (see T157658)
// Create a new runner to avoid rechecking the prior constraints, use the same factory
$constraintRunner = new EditConstraintRunner();
@ -2073,10 +2103,10 @@ class EditPage implements IEditObject {
$constraintRunner->addConstraint(
$constraintFactory->newEditFilterMergedContentHookConstraint(
$textbox_content,
$content,
$this->context,
$this->summary,
$this->minoredit
$markAsMinor
)
);
@ -2087,23 +2117,6 @@ class EditPage implements IEditObject {
return Status::wrap( $failed->getLegacyStatus() );
}
// END OF MIGRATION TO EDITCONSTRAINT SYSTEM (continued below)
$content = $textbox_content;
$result['sectionanchor'] = '';
if ( $this->section == 'new' ) {
if ( $this->sectiontitle !== '' ) {
// Insert the section title above the content.
$content = $content->addSectionHeader( $this->sectiontitle );
} elseif ( $this->summary !== '' ) {
// Insert the section title above the content.
$content = $content->addSectionHeader( $this->summary );
}
list( $newSectionSummary, $anchor ) = $this->newSectionSummary();
$this->summary = $newSectionSummary;
$result['sectionanchor'] = $anchor;
}
} else { # not $new
# Article exists. Check for edit conflict.
@ -2227,6 +2240,10 @@ class EditPage implements IEditObject {
return Status::newGood( self::AS_CONFLICT_DETECTED )->setOK( false );
}
$pageUpdater = $this->page->newPageUpdater( $user );
$pageUpdater->setContent( SlotRecord::MAIN, $content );
$pageUpdater->prepareUpdate( $flags );
// BEGINNING OF MIGRATION TO EDITCONSTRAINT SYSTEM (see T157658)
// Create a new runner to avoid rechecking the prior constraints, use the same factory
$constraintRunner = new EditConstraintRunner();
@ -2235,7 +2252,7 @@ class EditPage implements IEditObject {
$content,
$this->context,
$this->summary,
$this->minoredit
$markAsMinor
)
);
@ -2326,28 +2343,33 @@ class EditPage implements IEditObject {
}
// END OF MIGRATION TO EDITCONSTRAINT SYSTEM
$flags = EDIT_AUTOSUMMARY |
( $new ? EDIT_NEW : EDIT_UPDATE ) |
( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
( $markAsBot ? EDIT_FORCE_BOT : 0 );
$isUndo = false;
if ( $this->undidRev ) {
if ( $this->undidRev && $this->isUndoClean( $content ) ) {
// As the user can change the edit's content before saving, we only mark
// "clean" undos as reverts. This is to avoid abuse by marking irrelevant
// edits as undos.
$isUndo = $this->isUndoClean( $content );
$pageUpdater
->setOriginalRevisionId( $this->undoAfter ?: false )
->markAsRevert(
EditResult::REVERT_UNDO,
$this->undidRev,
$this->undoAfter ?: null
);
}
$doEditStatus = $this->page->doUserEditContent(
$content,
$user,
$this->summary,
$flags,
$isUndo && $this->undoAfter ? $this->undoAfter : false,
$this->changeTags,
$isUndo ? $this->undidRev : 0
);
$needsPatrol = $wgUseRCPatrol || ( $wgUseNPPatrol && !$this->page->exists() );
if ( $needsPatrol && $this->context->getAuthority()
->authorizeWrite( 'autopatrol', $this->getTitle() )
) {
$pageUpdater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
}
$pageUpdater
->addTags( $this->changeTags )
->saveRevision(
CommentStoreComment::newUnsavedComment( trim( $this->summary ) ),
$flags
);
$doEditStatus = $pageUpdater->getStatus();
if ( !$doEditStatus->isOK() ) {
// Failure from doEdit()

View file

@ -183,16 +183,16 @@ class PageUpdater {
*/
private $editResult = null;
/**
* @var string[] currently enabled software change tags
*/
private $softwareTags;
/**
* @var ServiceOptions
*/
private $serviceOptions;
/**
* @var int
*/
private $flags = 0;
/**
* @param UserIdentity $author
* @param WikiPage $wikiPage
@ -240,7 +240,6 @@ class PageUpdater {
$this->userEditTracker = $userEditTracker;
$this->userGroupManager = $userGroupManager;
$this->titleFormatter = $titleFormatter;
$this->softwareTags = $softwareTags;
$this->slotsUpdate = new RevisionSlotsUpdate();
$this->editResultBuilder = new EditResultBuilder(
@ -256,6 +255,62 @@ class PageUpdater {
);
}
/**
* Sets any flags to use when performing the update.
* Flags passed in subsequent calls to this method as well as calls to prepareUpdate()
* or saveRevision() are aggregated using bitwise OR.
*
* Known flags:
*
* EDIT_NEW
* Create a new page, or fail with "edit-already-exists" if the page exists.
* EDIT_UPDATE
* Create a new revision, or fail with "edit-gone-missing" if the page does not exist.
* EDIT_MINOR
* Mark this revision as minor
* EDIT_SUPPRESS_RC
* Do not log the change in recentchanges
* EDIT_FORCE_BOT
* Mark the revision as automated ("bot edit")
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
* EDIT_INTERNAL
* Signal that the page retrieve/save cycle happened entirely in this request.
*
* @param int $flags Bitfield
* @return $this
*/
public function setFlags( int $flags ) {
$this->flags |= $flags;
return $this;
}
/**
* Prepare the update.
* This sets up the RevisionRecord to be saved.
*
* @since 1.37
*
* @param int $flags Bitfield, will be combined with flags set via setFlags().
* EDIT_FORCE_BOT and EDIT_INTERNAL will bypass the edit stash.
*/
public function prepareUpdate( int $flags = 0 ) {
$this->setFlags( $flags );
// Load the data from the primary database if needed. Needed to check flags.
$this->grabParentRevision();
if ( !$this->derivedDataUpdater->isUpdatePrepared() ) {
// Avoid statsd noise and wasted cycles check the edit stash (T136678)
$useStashed = !( ( $this->flags & EDIT_INTERNAL ) || ( $this->flags & EDIT_FORCE_BOT ) );
// Prepare the update. This performs PST and generates the canonical ParserOutput.
$this->derivedDataUpdater->prepareContent(
$this->author,
$this->slotsUpdate,
$useStashed
);
}
}
/**
* @param UserIdentity $user
*
@ -414,20 +469,6 @@ class PageUpdater {
return $this->derivedDataUpdater->grabCurrentRevision();
}
/**
* Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
*
* @param int $flags
* @return int Updated $flags
*/
private function checkFlags( $flags ) {
if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
$flags |= ( $this->derivedDataUpdater->pageExisted() ) ? EDIT_UPDATE : EDIT_NEW;
}
return $flags;
}
/**
* Set the new content for the given slot role
*
@ -581,10 +622,9 @@ class PageUpdater {
}
/**
* @param int $flags Bit mask: a bit mask of EDIT_XXX flags.
* @return string[]
*/
private function computeEffectiveTags( $flags ) {
private function computeEffectiveTags() {
$tags = $this->tags;
$editResult = $this->getEditResult();
@ -595,7 +635,7 @@ class PageUpdater {
$content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
// TODO: MCR: Do this for all slots. Also add tags for removing roles!
$tag = $handler->getChangeTag( $old_content, $content, $flags );
$tag = $handler->getChangeTag( $old_content, $content, $this->flags );
// If there is no applicable tag, null is returned, so we need to check
if ( $tag ) {
$tags[] = $tag;
@ -648,12 +688,10 @@ class PageUpdater {
}
/**
* @param int $flags Bit mask: a bit mask of EDIT_XXX flags.
*
* @return CommentStoreComment
*/
private function makeAutoSummary( $flags ) {
if ( !$this->useAutomaticEditSummaries || ( $flags & EDIT_AUTOSUMMARY ) === 0 ) {
private function makeAutoSummary() {
if ( !$this->useAutomaticEditSummaries || ( $this->flags & EDIT_AUTOSUMMARY ) === 0 ) {
return CommentStoreComment::newUnsavedComment( '' );
}
@ -670,7 +708,7 @@ class PageUpdater {
$handler = $this->getContentHandler( $role );
$content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
$old_content = $this->getParentContent( $role );
$summary = $handler->getAutosummary( $old_content, $content, $flags );
$summary = $handler->getAutosummary( $old_content, $content, $this->flags );
return CommentStoreComment::newUnsavedComment( $summary );
}
@ -691,23 +729,10 @@ class PageUpdater {
* saveRevision() now need to check the "minoredit" themselves before using EDIT_MINOR.
*
* @param CommentStoreComment $summary Edit summary
* @param int $flags Bitfield:
* EDIT_NEW
* Create a new page, or fail with "edit-already-exists" if the page exists.
* EDIT_UPDATE
* Create a new revision, or fail with "edit-gone-missing" if the page does not exist.
* EDIT_MINOR
* Mark this revision as minor
* EDIT_SUPPRESS_RC
* Do not log the change in recentchanges
* EDIT_FORCE_BOT
* Mark the revision as automated ("bot edit")
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
* EDIT_INTERNAL
* Signal that the page retrieve/save cycle happened entirely in this request.
* @param int $flags Bitfield, will be combined with the flags set via setFlags(). See
* there for details.
*
* If neither EDIT_NEW nor EDIT_UPDATE is specified, the expected state is detected
* @note If neither EDIT_NEW nor EDIT_UPDATE is specified, the expected state is detected
* automatically via grabParentRevision(). In this case, the "edit-already-exists" or
* "edit-gone-missing" errors may still be triggered due to race conditions, if the page
* was unexpectedly created or deleted while revision creation is in progress. This can be
@ -721,6 +746,8 @@ class PageUpdater {
* @throws RuntimeException
*/
public function saveRevision( CommentStoreComment $summary, int $flags = 0 ) {
$this->setFlags( $flags );
if ( $this->wasCommitted() ) {
throw new RuntimeException(
'saveRevision() or updateRevision() has already been called on this PageUpdater!'
@ -769,25 +796,19 @@ class PageUpdater {
// NOTE: This grabs the parent revision as the CAS token, if grabParentRevision
// wasn't called yet. If the page is modified by another process before we are done with
// it, this method must fail (with status 'edit-conflict')!
// NOTE: The parent revision may be different from $this->originalRevisionId.
$this->grabParentRevision();
$flags = $this->checkFlags( $flags );
// NOTE: The parent revision may be different from the edit's base revision.
$this->prepareUpdate();
// Avoid statsd noise and wasted cycles check the edit stash (T136678)
$useStashed = !( ( $flags & EDIT_INTERNAL ) || ( $flags & EDIT_FORCE_BOT ) );
// Prepare the update. This performs PST and generates the canonical ParserOutput.
$this->derivedDataUpdater->prepareContent(
$this->author,
$this->slotsUpdate,
$useStashed
);
// Detect whether update or creation should be performed.
if ( !( $this->flags & EDIT_NEW ) && !( $this->flags & EDIT_UPDATE ) ) {
$this->flags |= ( $this->derivedDataUpdater->pageExisted() ) ? EDIT_UPDATE : EDIT_NEW;
}
// Trigger pre-save hook (using provided edit summary)
$renderedRevision = $this->derivedDataUpdater->getRenderedRevision();
$hookStatus = Status::newGood( [] );
$allowedByHook = $this->hookRunner->onMultiContentSave(
$renderedRevision, $this->author, $summary, $flags, $hookStatus
$renderedRevision, $this->author, $summary, $this->flags, $hookStatus
);
if ( $allowedByHook && $this->hookContainer->isRegistered( 'PageContentSave' ) ) {
// Also run the legacy hook.
@ -800,7 +821,7 @@ class PageUpdater {
// Deprecated since 1.35.
$allowedByHook = $this->hookRunner->onPageContentSave(
$this->getWikiPage(), $legacyUser, $mainContent, $summary,
$flags & EDIT_MINOR, null, null, $flags, $hookStatus
$this->flags & EDIT_MINOR, null, null, $this->flags, $hookStatus
);
}
@ -819,15 +840,15 @@ class PageUpdater {
// XXX: $summary == null seems logical, but the empty string may actually come from the user
// XXX: Move this logic out of the storage layer! It does not belong here! Use a callback?
if ( $summary->text === '' && $summary->data === null ) {
$summary = $this->makeAutoSummary( $flags );
$summary = $this->makeAutoSummary();
}
// Actually create the revision and create/update the page.
// Do NOT yet set $this->status!
if ( $flags & EDIT_UPDATE ) {
$status = $this->doModify( $summary, $this->author, $flags );
if ( $this->flags & EDIT_UPDATE ) {
$status = $this->doModify( $summary );
} else {
$status = $this->doCreate( $summary, $this->author, $flags );
$status = $this->doCreate( $summary );
}
// Promote user to any groups they meet the criteria for
@ -917,9 +938,10 @@ class PageUpdater {
}
}
// do we need PST?
// XXX: do we need PST?
$this->status = $this->doUpdate( $this->author, $revision );
$this->flags |= EDIT_INTERNAL;
$this->status = $this->doUpdate( $revision );
}
/**
@ -1010,16 +1032,12 @@ class PageUpdater {
* The $status parameter is updated with any errors or warnings found by Content::prepareSave().
*
* @param CommentStoreComment $comment
* @param UserIdentity $user
* @param int $flags
* @param Status $status
*
* @return MutableRevisionRecord
*/
private function makeNewRevision(
CommentStoreComment $comment,
UserIdentity $user,
$flags,
Status $status
) {
$wikiPage = $this->getWikiPage();
@ -1062,8 +1080,8 @@ class PageUpdater {
}
$rev->setComment( $comment );
$rev->setUser( $user );
$rev->setMinorEdit( ( $flags & EDIT_MINOR ) > 0 );
$rev->setUser( $this->author );
$rev->setMinorEdit( ( $this->flags & EDIT_MINOR ) > 0 );
foreach ( $rev->getSlots()->getSlots() as $slot ) {
$content = $slot->getContent();
@ -1071,8 +1089,8 @@ class PageUpdater {
// XXX: We may push this up to the "edit controller" level, see T192777.
// XXX: prepareSave() and isValid() could live in SlotRoleHandler
// XXX: PrepareSave should not take a WikiPage!
$legacyUser = self::toLegacyUser( $user );
$prepStatus = $content->prepareSave( $wikiPage, $flags, $oldid, $legacyUser );
$legacyUser = self::toLegacyUser( $this->author );
$prepStatus = $content->prepareSave( $wikiPage, $this->flags, $oldid, $legacyUser );
// TODO: MCR: record which problem arose in which slot.
$status->merge( $prepStatus );
@ -1107,11 +1125,10 @@ class PageUpdater {
* here, so, as opposed to doCreate(), updating recentchanges is left as the responsibility
* of the caller.
*
* @param UserIdentity $user
* @param RevisionRecord $revision
* @return Status
*/
private function doUpdate( UserIdentity $user, RevisionRecord $revision ): Status {
private function doUpdate( RevisionRecord $revision ): Status {
$currentRevision = $this->grabParentRevision();
if ( !$currentRevision ) {
// Article gone missing
@ -1148,9 +1165,7 @@ class PageUpdater {
$dbw,
$wikiPage,
$newRevisionRecord,
$user,
$revision->getComment(),
EDIT_INTERNAL,
[ 'changed' => false, ]
),
DeferredUpdates::PRESEND
@ -1162,13 +1177,11 @@ class PageUpdater {
/**
* @param CommentStoreComment $summary The edit summary
* @param UserIdentity $user The revision's author
* @param int $flags EDIT_XXX constants
*
* @throws MWException
* @return Status
*/
private function doModify( CommentStoreComment $summary, UserIdentity $user, $flags ) {
private function doModify( CommentStoreComment $summary ) {
$wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
// Update article, but only if changed.
@ -1186,8 +1199,6 @@ class PageUpdater {
$newRevisionRecord = $this->makeNewRevision(
$summary,
$user,
$flags,
$status
);
@ -1245,23 +1256,27 @@ class PageUpdater {
}
$editResult = $this->getEditResult();
$tags = $this->computeEffectiveTags( $flags );
$tags = $this->computeEffectiveTags();
$this->hookRunner->onRevisionFromEditComplete(
$wikiPage, $newRevisionRecord, $editResult->getOriginalRevisionId(), $user, $tags
$wikiPage,
$newRevisionRecord,
$editResult->getOriginalRevisionId(),
$this->author,
$tags
);
// Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
if ( !( $this->flags & EDIT_SUPPRESS_RC ) ) {
// Add RC row to the DB
RecentChange::notifyEdit(
$now,
$this->getPage(),
$newRevisionRecord->isMinor(),
$user,
$this->author,
$summary->text, // TODO: pass object when that becomes possible
$oldid,
$newRevisionRecord->getTimestamp(),
( $flags & EDIT_FORCE_BOT ) > 0,
( $this->flags & EDIT_FORCE_BOT ) > 0,
'',
$oldRev->getSize(),
$newRevisionRecord->getSize(),
@ -1272,7 +1287,7 @@ class PageUpdater {
);
}
$this->userEditTracker->incrementUserEditCount( $user );
$this->userEditTracker->incrementUserEditCount( $this->author );
$dbw->endAtomic( __METHOD__ );
@ -1303,9 +1318,7 @@ class PageUpdater {
$dbw,
$wikiPage,
$newRevisionRecord,
$user,
$summary,
$flags,
[ 'changed' => $changed, ]
),
DeferredUpdates::PRESEND
@ -1316,14 +1329,12 @@ class PageUpdater {
/**
* @param CommentStoreComment $summary The edit summary
* @param UserIdentity $user The revision's author
* @param int $flags EDIT_XXX constants
*
* @throws DBUnexpectedError
* @throws MWException
* @return Status
*/
private function doCreate( CommentStoreComment $summary, UserIdentity $user, $flags ) {
private function doCreate( CommentStoreComment $summary ) {
$wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
if ( !$this->derivedDataUpdater->getSlots()->hasSlot( SlotRecord::MAIN ) ) {
@ -1336,8 +1347,6 @@ class PageUpdater {
$newRevisionRecord = $this->makeNewRevision(
$summary,
$user,
$flags,
$status
);
@ -1374,21 +1383,21 @@ class PageUpdater {
throw new PageUpdateException( "Failed to update page row to use new revision." );
}
$tags = $this->computeEffectiveTags( $flags );
$tags = $this->computeEffectiveTags();
$this->hookRunner->onRevisionFromEditComplete(
$wikiPage, $newRevisionRecord, false, $user, $tags
$wikiPage, $newRevisionRecord, false, $this->author, $tags
);
// Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
if ( !( $this->flags & EDIT_SUPPRESS_RC ) ) {
// Add RC row to the DB
RecentChange::notifyNew(
$now,
$this->getPage(),
$newRevisionRecord->isMinor(),
$user,
$this->author,
$summary->text, // TODO: pass object when that becomes possible
( $flags & EDIT_FORCE_BOT ) > 0,
( $this->flags & EDIT_FORCE_BOT ) > 0,
'',
$newRevisionRecord->getSize(),
$newRevisionRecord->getId(),
@ -1397,13 +1406,13 @@ class PageUpdater {
);
}
$this->userEditTracker->incrementUserEditCount( $user );
$this->userEditTracker->incrementUserEditCount( $this->author );
if ( $this->usePageCreationLog ) {
// Log the page creation
// @TODO: Do we want a 'recreate' action?
$logEntry = new ManualLogEntry( 'create', 'create' );
$logEntry->setPerformer( $user );
$logEntry->setPerformer( $this->author );
$logEntry->setTarget( $this->getPage() );
$logEntry->setComment( $summary->text );
$logEntry->setTimestamp( $now );
@ -1425,9 +1434,7 @@ class PageUpdater {
$dbw,
$wikiPage,
$newRevisionRecord,
$user,
$summary,
$flags,
[ 'created' => true ]
),
DeferredUpdates::PRESEND
@ -1440,21 +1447,19 @@ class PageUpdater {
IDatabase $dbw,
WikiPage $wikiPage,
RevisionRecord $newRevisionRecord,
UserIdentity $user,
CommentStoreComment $summary,
$flags,
$hints = []
array $hints = []
) {
return new AtomicSectionUpdate(
$dbw,
__METHOD__,
function () use (
$wikiPage, $newRevisionRecord, $user,
$summary, $flags, $hints
$wikiPage, $newRevisionRecord,
$summary, $hints
) {
// set debug data
$hints['causeAction'] = 'edit-page';
$hints['causeAgent'] = $user->getName();
$hints['causeAgent'] = $this->author->getName();
$editResult = $this->getEditResult();
$hints['editResult'] = $editResult;
@ -1470,9 +1475,9 @@ class PageUpdater {
// Allow extensions to override the patrolling subsystem.
$this->hookRunner->onBeforeRevertedTagUpdate(
$wikiPage,
$user,
$this->author,
$summary,
$flags,
$this->flags,
$newRevisionRecord,
$editResult,
$approved
@ -1485,15 +1490,15 @@ class PageUpdater {
$this->derivedDataUpdater->doUpdates();
$created = $hints['created'] ?? false;
$flags |= ( $created ? EDIT_NEW : EDIT_UPDATE );
$this->flags |= ( $created ? EDIT_NEW : EDIT_UPDATE );
// PageSaveComplete replaced old PageContentInsertComplete and
// PageContentSaveComplete hooks since 1.35
$this->hookRunner->onPageSaveComplete(
$wikiPage,
$user,
$this->author,
$summary->text,
$flags,
$this->flags,
$newRevisionRecord,
$editResult
);

View file

@ -110,7 +110,7 @@ class EditPageTest extends MediaWikiLangTestCase {
* * editRevId: revision ID of the edit's base revision (optional)
* * wpStarttime: timestamp when the edit started (will be inserted if not provided)
* * wpSectionTitle: the section to edit
* * wpMinorEdit: mark as minor edit
* * wpMinoredit: mark as minor edit
* * wpWatchthis: whether to watch the page
* @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants).
* Set to null to skip the check.
@ -185,12 +185,10 @@ class EditPageTest extends MediaWikiLangTestCase {
$ep->setContextTitle( $title );
$ep->importFormData( $req );
$bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false;
// this is where the edit happens!
// Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut
// and throws exceptions like PermissionsError
$status = $ep->internalAttemptSave( $result, $bot );
$status = $ep->attemptSave( $result );
if ( $expectedCode !== null ) {
// check edit code
@ -404,6 +402,63 @@ class EditPageTest extends MediaWikiLangTestCase {
$this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
}
/**
* @covers EditPage
*/
public function testUpdateNoMinor() {
$user = $this->getTestUser()->getUser();
$anon = new User(); // anon
// Test that page creation can never be minor
$edit = [
'wpTextbox1' => 'testing',
'wpSummary' => 'first update',
'wpMinoredit' => 'minor'
];
$page = $this->assertEdit( 'EditPageTest_testUpdateNoMinor', null, $user, $edit,
EditPage::AS_SUCCESS_NEW_ARTICLE, 'testing', "expected successful update" );
$this->assertFalse(
$page->getRevisionRecord()->isMinor(),
'page creation should not be minor'
);
// Test that anons can't make an update minor
$this->forceRevisionDate( $page, '20120101000000' );
$edit = [
'wpTextbox1' => 'testing 2',
'wpSummary' => 'second update',
'wpMinoredit' => 'minor'
];
$page = $this->assertEdit( 'EditPageTest_testUpdateNoMinor', null, $anon, $edit,
EditPage::AS_SUCCESS_UPDATE, 'testing 2', "expected successful update" );
$this->assertFalse(
$page->getRevisionRecord()->isMinor(),
'anon edit should not be minor'
);
// Test that users can make an update minor
$this->forceRevisionDate( $page, '20120102000000' );
$edit = [
'wpTextbox1' => 'testing 3',
'wpSummary' => 'third update',
'wpMinoredit' => 'minor'
];
$page = $this->assertEdit( 'EditPageTest_testUpdateNoMinor', null, $user, $edit,
EditPage::AS_SUCCESS_UPDATE, 'testing 3', "expected successful update" );
$this->assertTrue(
$page->getRevisionRecord()->isMinor(),
'users can make edits minor'
);
}
/**
* @covers EditPage
*/

View file

@ -6,13 +6,13 @@ use CommentStoreComment;
use Content;
use DeferredUpdates;
use FormatJson;
use MediaWiki\Permissions\SimpleAuthority;
use MediaWiki\Revision\RenderedRevision;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Storage\EditResult;
use MediaWiki\User\UserIdentity;
use MediaWikiIntegrationTestCase;
use Message;
use ParserOptions;
use RecentChange;
use Status;
@ -29,14 +29,6 @@ use WikitextContent;
*/
class PageUpdaterTest extends MediaWikiIntegrationTestCase {
private function newAuthority( UserIdentity $identity, $permissions = null ) {
if ( $permissions === null ) {
$permissions = [ 'view', 'edit', 'create' ];
}
return new SimpleAuthority( $identity, $permissions );
}
protected function setUp(): void {
parent::setUp();
@ -92,11 +84,10 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testCreatePage() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$oldStats = $this->db->selectRow( 'site_stats', '*', '1=1' );
@ -123,7 +114,8 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$this->assertTrue( $updater->hasEditConflict( 1 ), 'hasEditConflict' );
// TODO: test failure with EDIT_UPDATE
// TODO: test EDIT_MINOR, EDIT_BOT, etc
// TODO: test EDIT_BOT, etc
$updater->setFlags( EDIT_MINOR );
$summary = CommentStoreComment::newUnsavedComment( 'Just a test' );
$rev = $updater->saveRevision( $summary );
@ -154,6 +146,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$rev = $updater->getNewRevision();
$revContent = $rev->getContent( SlotRecord::MAIN );
$this->assertSame( 'Lorem Ipsum', $revContent->serialize(), 'revision content' );
$this->assertTrue( $rev->isMinor(), 'RevisionRecord::isMinor()' );
// were the WikiPage and Title objects updated?
$this->assertTrue( $page->exists(), 'WikiPage::exists()' );
@ -177,7 +170,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$this->assertSame( $oldStats->ss_total_edits + 1, (int)$stats->ss_total_edits );
// re-edit with same content - should be a "null-edit"
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->setContent( SlotRecord::MAIN, $content );
$summary = CommentStoreComment::newUnsavedComment( 'to to re-edit' );
@ -198,7 +191,6 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testUpdatePage() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
$this->insertPage( $title );
@ -206,7 +198,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$page = WikiPage::factory( $title );
$parentId = $page->getLatest();
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$oldStats = $this->db->selectRow( 'site_stats', '*', '1=1' );
@ -219,6 +211,9 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
// TODO: MCR: test additional slots
$updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
// Check that prepareUpdate() does not fail, and the flag is applied.
$updater->prepareUpdate( EDIT_MINOR );
// TODO: test all flags for saveRevision()!
$summary = CommentStoreComment::newUnsavedComment( 'Just a test' );
$rev = $updater->saveRevision( $summary );
@ -252,6 +247,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$rev = $updater->getNewRevision();
$revContent = $rev->getContent( SlotRecord::MAIN );
$this->assertSame( 'Lorem Ipsum', $revContent->serialize(), 'revision content' );
$this->assertTrue( $rev->isMinor(), 'RevisionRecord::isMinor()' );
// were the WikiPage and Title objects updated?
$this->assertTrue( $page->exists(), 'WikiPage::exists()' );
@ -270,7 +266,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$this->assertNotNull( $rc, 'RecentChange' );
// re-edit
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->setContent( SlotRecord::MAIN, new TextContent( 'dolor sit amet' ) );
$summary = CommentStoreComment::newUnsavedComment( 're-edit' );
@ -282,7 +278,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$topRevisionId = $updater->getNewRevision()->getId();
// perform a null edit
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->setContent( SlotRecord::MAIN, new TextContent( 'dolor sit amet' ) );
$summary = CommentStoreComment::newUnsavedComment( 'null edit' );
$updater->saveRevision( $summary );
@ -312,16 +308,15 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$page = $this->getExistingTestPage( __METHOD__ );
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$summary = CommentStoreComment::newUnsavedComment( '1' );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->setContent( SlotRecord::MAIN, new TextContent( '1' ) );
$updater->saveRevision( $summary );
$revId1 = $updater->getNewRevision()->getId();
$summary = CommentStoreComment::newUnsavedComment( '2' );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->setContent( SlotRecord::MAIN, new TextContent( '2' ) );
$updater->saveRevision( $summary );
$revId2 = $updater->getNewRevision()->getId();
@ -386,7 +381,6 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
private function createRevision( WikiPage $page, $summary, $content = null ) {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$comment = CommentStoreComment::newUnsavedComment( $summary );
@ -394,7 +388,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$content = new TextContent( $content ?? $summary );
}
return $page->newPageUpdater( $authority )
return $page->newPageUpdater( $user )
->setContent( SlotRecord::MAIN, $content )
->saveRevision( $comment );
}
@ -405,7 +399,6 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testMultiContentSaveHook() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
@ -416,7 +409,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
// start editing non-existing page
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
foreach ( $slots as $slot => $content ) {
$updater->setContent( $slot, $content );
}
@ -468,12 +461,11 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testMultiContentSaveHookAbort() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
// start editing non-existing page
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
$summary = CommentStoreComment::newUnsavedComment( 'Just a test' );
@ -506,13 +498,12 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testCompareAndSwapFailure() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
// start editing non-existing page
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->grabParentRevision();
// create page concurrently
@ -532,7 +523,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
// start editing existing page
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->grabParentRevision();
// update page concurrently
@ -556,13 +547,12 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testFailureOnEditFlags() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
// start editing non-existing page
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
// update with EDIT_UPDATE flag should fail
$summary = CommentStoreComment::newUnsavedComment( 'udpate?!' );
@ -580,7 +570,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
// update with EDIT_NEW flag should fail
$summary = CommentStoreComment::newUnsavedComment( 'create?!' );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->setContent( SlotRecord::MAIN, new TextContent( 'dolor sit amet' ) );
$updater->saveRevision( $summary, EDIT_NEW );
$status = $updater->getStatus();
@ -596,13 +586,12 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testFailureOnBadContentModel() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
// start editing non-existing page
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
// plain text content should fail in aux slot (the main slot doesn't care)
$updater->setContent( 'main', new TextContent( 'Main Content' ) );
@ -634,14 +623,12 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$revisionStore = $this->getServiceContainer()->getRevisionStore();
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
$page = WikiPage::factory( $title );
$summary = CommentStoreComment::newUnsavedComment( 'Lorem ipsum ' . $patrolled );
$rev = $page->newPageUpdater( $authority )
$rev = $page->newPageUpdater( $user )
->setContent( SlotRecord::MAIN, new TextContent( 'Lorem ipsum ' . $patrolled ) )
->setRcPatrolStatus( $patrolled )
->saveRevision( $summary );
@ -655,14 +642,13 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testStalePageID() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
$summary = CommentStoreComment::newUnsavedComment( 'testing...' );
// Create page
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->setContent( 'main', new TextContent( 'Content 1' ) );
$updater->saveRevision( $summary, EDIT_NEW );
$this->assertTrue( $updater->wasSuccessful(), 'wasSuccessful()' );
@ -672,7 +658,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$page = WikiPage::factory( $title );
// start editing existing page using bad page ID
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$updater->grabParentRevision();
$updater->setContent( 'main', new TextContent( 'Content 2' ) );
@ -694,22 +680,21 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testInheritSlot() {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$summary = CommentStoreComment::newUnsavedComment( 'one' );
$updater->setContent( SlotRecord::MAIN, new TextContent( 'Lorem ipsum' ) );
$rev1 = $updater->saveRevision( $summary, EDIT_NEW );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$summary = CommentStoreComment::newUnsavedComment( 'two' );
$updater->setContent( SlotRecord::MAIN, new TextContent( 'Foo Bar' ) );
$rev2 = $updater->saveRevision( $summary, EDIT_UPDATE );
$updater = $page->newPageUpdater( $authority );
$updater = $page->newPageUpdater( $user );
$summary = CommentStoreComment::newUnsavedComment( 'three' );
$updater->inheritSlot( $rev1->getSlot( SlotRecord::MAIN ) );
$rev3 = $updater->saveRevision( $summary, EDIT_UPDATE );
@ -812,12 +797,11 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
public function testSetUseAutomaticEditSummaries() {
$this->setContentLang( 'qqx' );
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ );
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority )
$updater = $page->newPageUpdater( $user )
->setUseAutomaticEditSummaries( true )
->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
@ -830,7 +814,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$this->assertSame( '(autosumm-new: Lorem Ipsum)', $comment->text, 'comment text' );
// check that this also works when blanking the page
$updater = $page->newPageUpdater( $authority )
$updater = $page->newPageUpdater( $user )
->setUseAutomaticEditSummaries( true )
->setContent( SlotRecord::MAIN, new TextContent( '' ) );
@ -845,7 +829,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$title2 = $this->getDummyTitle( __METHOD__ . '/2' );
$page2 = WikiPage::factory( $title2 );
$updater = $page2->newPageUpdater( $authority )
$updater = $page2->newPageUpdater( $user )
->setUseAutomaticEditSummaries( false )
->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
@ -857,7 +841,7 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$this->assertSame( '', $comment->text, 'comment text should still be blank' );
// check that we don't do auto.summaries without the EDIT_AUTOSUMMARY flag
$updater = $page2->newPageUpdater( $authority )
$updater = $page2->newPageUpdater( $user )
->setUseAutomaticEditSummaries( true )
->setContent( SlotRecord::MAIN, new TextContent( '' ) );
@ -879,13 +863,12 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
*/
public function testSetUsePageCreationLog( $use, $expected ) {
$user = $this->getTestUser()->getUser();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ . ( $use ? '_logged' : '_unlogged' ) );
$page = WikiPage::factory( $title );
$summary = CommentStoreComment::newUnsavedComment( 'cmt' );
$updater = $page->newPageUpdater( $authority )
$updater = $page->newPageUpdater( $user )
->setUsePageCreationLog( $use )
->setContent( SlotRecord::MAIN, new TextContent( 'Lorem Ipsum' ) );
$updater->saveRevision( $summary, EDIT_NEW );
@ -959,13 +942,11 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$user = User::newFromName( 'A user for ' . __METHOD__ );
$user->addToDatabase();
$authority = $this->newAuthority( $user );
$title = $this->getDummyTitle( __METHOD__ . '-' . $this->getName() );
$this->insertPage( $title );
$page = WikiPage::factory( $title );
$updater = $page->newPageUpdater( $authority )
$updater = $page->newPageUpdater( $user )
->setContent( SlotRecord::MAIN, new \WikitextContent( $wikitext ) );
$summary = CommentStoreComment::newUnsavedComment( 'Just a test' );