62 public const CONSTRUCTOR_OPTIONS = [
138 HookContainer $hookContainer,
146 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
153 $this->hookRunner =
new HookRunner( $hookContainer );
181 if ( $bot && $this->performer->isAllowedAny(
'markbotedits',
'bot' ) ) {
198 $this->tags = $tags ?: [];
208 $permissionStatus = PermissionStatus::newEmpty();
209 $this->performer->authorizeWrite(
'edit', $this->page, $permissionStatus );
210 $this->performer->authorizeWrite(
'rollback', $this->page, $permissionStatus );
212 if ( $this->readOnlyMode->isReadOnly() ) {
213 $permissionStatus->fatal(
'readonlytext' );
216 $user = $this->userFactory->newFromAuthority( $this->performer );
217 if ( $user->pingLimiter(
'rollback' ) || $user->pingLimiter() ) {
218 $permissionStatus->fatal(
'actionthrottledtext' );
220 return $permissionStatus;
233 $permissionStatus = $this->authorizeRollback();
234 if ( !$permissionStatus->isGood() ) {
235 return $permissionStatus;
237 return $this->rollback();
259 $updater = $this->wikiPageFactory->newFromTitle( $this->page )->newPageUpdater( $this->performer );
260 $currentRevision = $updater->grabParentRevision();
262 if ( !$currentRevision ) {
267 $currentEditor = $currentRevision->getUser( RevisionRecord::RAW );
268 $currentEditorForPublic = $currentRevision->getUser( RevisionRecord::FOR_PUBLIC );
270 if ( !$this->byUser->equals( $currentEditor ) ) {
272 'current-revision-record' => $currentRevision
276 htmlspecialchars( $this->titleFormatter->getPrefixedText( $this->page ) ),
277 htmlspecialchars( $this->byUser->getName() ),
278 htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' )
283 $dbw = $this->loadBalancer->getConnectionRef(
DB_PRIMARY );
285 $revIndex = $dbw->indexExists(
'revision',
'page_timestamp', __METHOD__ )
287 :
'rev_page_timestamp';
292 $actorWhere = $this->actorMigration->getWhere( $dbw,
'rev_user', $currentEditor );
293 $targetRevisionRow = $dbw->selectRow(
294 [
'revision' ] + $actorWhere[
'tables'],
295 [
'rev_id',
'rev_timestamp',
'rev_deleted' ],
297 'rev_page' => $currentRevision->getPageId(),
298 'NOT(' . $actorWhere[
'conds'] .
')',
302 'USE INDEX' => [
'revision' => $revIndex ],
303 'ORDER BY' => [
'rev_timestamp DESC',
'rev_id DESC' ]
308 if ( $targetRevisionRow ===
false ) {
311 } elseif ( $targetRevisionRow->rev_deleted & RevisionRecord::DELETED_TEXT
312 || $targetRevisionRow->rev_deleted & RevisionRecord::DELETED_USER
319 $targetRevision = $this->revisionStore
320 ->getRevisionById( $targetRevisionRow->rev_id, RevisionStore::READ_LATEST );
325 if ( $this->performer->isAllowed(
'minoredit' ) ) {
334 $currentContent = $currentRevision->getContent( SlotRecord::MAIN );
335 $targetContent = $targetRevision->getContent( SlotRecord::MAIN );
336 $changingContentModel = $targetContent->getModel() !== $currentContent->getModel();
341 foreach ( $targetRevision->getSlots()->getSlots() as $slot ) {
342 $updater->inheritSlot( $slot );
347 foreach ( $currentRevision->getSlotRoles() as $role ) {
348 if ( !$targetRevision->hasSlot( $role ) ) {
349 $updater->removeSlot( $role );
353 $updater->markAsRevert(
354 EditResult::REVERT_ROLLBACK,
355 $currentRevision->getId(),
356 $targetRevision->getId()
362 if ( $this->options->get( MainConfigNames::UseRCPatrol ) &&
363 $this->performer->authorizeWrite(
'autopatrol', $this->page )
368 $summary = $this->getSummary( $currentRevision, $targetRevision );
371 $rev = $updater->saveRevision(
377 $this->updateRecentChange( $dbw, $currentRevision, $targetRevision );
379 if ( !$updater->wasSuccessful() ) {
380 return $updater->getStatus();
384 if ( !$updater->wasRevisionCreated() ) {
386 'current-revision-record' => $currentRevision
390 htmlspecialchars( $this->titleFormatter->getPrefixedText( $this->page ) ),
391 htmlspecialchars( $this->byUser->getName() ),
392 htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() :
'' )
397 if ( $changingContentModel ) {
401 $log->setPerformer( $this->performer->getUser() );
402 $log->setTarget(
new TitleValue( $this->page->getNamespace(), $this->page->getDBkey() ) );
403 $log->setComment( $summary );
404 $log->setParameters( [
405 '4::oldmodel' => $currentContent->getModel(),
406 '5::newmodel' => $targetContent->getModel(),
409 $logId = $log->insert( $dbw );
410 $log->publish( $logId );
413 $wikiPage = $this->wikiPageFactory->newFromTitle( $this->page );
415 $this->hookRunner->onRollbackComplete(
417 $this->performer->getUser(),
423 'summary' => $summary,
424 'current-revision-record' => $currentRevision,
425 'target-revision-record' => $targetRevision,
426 'newid' => $rev->getId(),
427 'tags' => array_merge( $this->tags, $updater->getEditResult()->getRevertTags() )
440 RevisionRecord $current,
441 RevisionRecord $target
449 if ( $this->options->get( MainConfigNames::UseRCPatrol ) ) {
455 $actorId = $this->actorNormalization
456 ->acquireActorId( $current->getUser( RevisionRecord::RAW ), $dbw );
461 'rc_cur_id' => $current->getPageId(),
462 'rc_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $target->getTimestamp() ) ),
463 'rc_actor' => $actorId
477 private function getSummary( RevisionRecord $current, RevisionRecord $target ): string {
478 $currentEditorForPublic = $current->
getUser( RevisionRecord::FOR_PUBLIC );
479 if ( $this->summary ===
'' ) {
480 if ( !$currentEditorForPublic ) {
481 $summary = MessageValue::new(
'revertpage-nouser' );
482 } elseif ( $this->options->get( MainConfigNames::DisableAnonTalk ) &&
483 !$currentEditorForPublic->isRegistered() ) {
484 $summary = MessageValue::new(
'revertpage-anon' );
486 $summary = MessageValue::new(
'revertpage' );
489 $summary = $this->summary;
492 $targetEditorForPublic = $target->getUser( RevisionRecord::FOR_PUBLIC );
495 $targetEditorForPublic ? $targetEditorForPublic->getName() :
null,
496 $currentEditorForPublic ? $currentEditorForPublic->getName() :
null,
502 if ( $summary instanceof MessageValue ) {
503 $summary = (
new Converter() )->convertMessageValue( $summary );
504 $summary = $summary->params(
$args )->inContentLanguage()->text();
506 $summary = (
new RawMessage( $summary,
$args ) )->inContentLanguage()->plain();
510 return trim( $summary );
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
This is not intended to be a long-term part of MediaWiki; it will be deprecated and removed once acto...
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
const UseRCPatrol
Name constant for the UseRCPatrol setting, for use with Config::get()
const DisableAnonTalk
Name constant for the DisableAnonTalk setting, for use with Config::get()
Backend logic for performing a page rollback action.
markAsBot(?bool $bot)
Mark all reverted edits as bot.
ILoadBalancer $loadBalancer
rollback()
Backend implementation of rollbackIfAllowed().
rollbackIfAllowed()
Rollback the most recent consecutive set of edits to a page from the same user; fails if there are no...
getSummary(RevisionRecord $current, RevisionRecord $target)
Generate and format summary for the rollback.
ActorMigration $actorMigration
ActorNormalization $actorNormalization
updateRecentChange(IDatabase $dbw, RevisionRecord $current, RevisionRecord $target)
Set patrolling and bot flag on the edits, which gets rolled back.
ReadOnlyMode $readOnlyMode
setChangeTags(?array $tags)
Change tags to apply to the rollback.
RevisionStore $revisionStore
authorizeRollback()
Authorize the rollback.
__construct(ServiceOptions $options, ILoadBalancer $loadBalancer, UserFactory $userFactory, ReadOnlyMode $readOnlyMode, RevisionStore $revisionStore, TitleFormatter $titleFormatter, HookContainer $hookContainer, WikiPageFactory $wikiPageFactory, ActorMigration $actorMigration, ActorNormalization $actorNormalization, PageIdentity $page, Authority $performer, UserIdentity $byUser)
TitleFormatter $titleFormatter
UserIdentity $byUser
who made the edits we are rolling back
setSummary(?string $summary)
Set custom edit summary.
WikiPageFactory $wikiPageFactory
Service for creating WikiPage objects.
The Message class deals with fetching and processing of interface message into a variety of formats.
static dateTimeParam(string $dateTime)
Variant of the Message class.
A service class for fetching the wiki's current read-only mode.
Utility class for creating new RC entries.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
Represents a page (or page fragment) title within MediaWiki.
Interface for objects (potentially) representing an editable wiki page.