contentHandlerFactory = $contentHandlerFactory; $this->rollbackPageFactory = $rollbackPageFactory; $this->userOptionsLookup = $userOptionsLookup; $this->watchlistManager = $watchlistManager; $this->commentFormatter = $commentFormatter; } public function getName() { return 'rollback'; } public function getRestriction() { return 'rollback'; } protected function usesOOUI() { return true; } protected function getDescription() { return ''; } public function doesWrites() { return true; } public function onSuccess() { return false; } public function onSubmit( $data ) { return false; } protected function alterForm( HTMLForm $form ) { $form->setWrapperLegendMsg( 'confirm-rollback-top' ); $form->setSubmitTextMsg( 'confirm-rollback-button' ); $form->setTokenSalt( 'rollback' ); $from = $this->getRequest()->getVal( 'from' ); if ( $from === null ) { throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' ); } foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) { $val = $this->getRequest()->getVal( $param ); if ( $val !== null ) { $form->addHiddenField( $param, $val ); } } } /** * @throws ErrorPageError * @throws ReadOnlyError * @throws ThrottledError */ public function show() { $this->setHeaders(); // This will throw exceptions if there's a problem $this->checkCanExecute( $this->getUser() ); if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) || $this->getRequest()->wasPosted() ) { $this->handleRollbackRequest(); } else { $this->showRollbackConfirmationForm(); } } public function handleRollbackRequest() { $this->enableTransactionalTimelimit(); $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' ); $request = $this->getRequest(); $user = $this->getUser(); $from = $request->getVal( 'from' ); $rev = $this->getWikiPage()->getRevisionRecord(); if ( $from === null ) { throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' ); } if ( !$rev ) { throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' ); } $revUser = $rev->getUser(); $userText = $revUser ? $revUser->getName() : ''; if ( $from !== $userText ) { throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [ $this->getTitle()->getPrefixedText(), wfEscapeWikiText( $from ), $userText ] ); } if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) { throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' ); } // The revision has the user suppressed, so the rollback has empty 'from', // so the check above would succeed in that case. // T307278 - Also check if the user has rights to view suppressed usernames if ( !$revUser ) { if ( $this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) { $revUser = $rev->getUser( RevisionRecord::RAW ); } else { $userFactory = MediaWikiServices::getInstance()->getUserFactory(); $revUser = $userFactory->newFromName( $this->context->msg( 'rev-deleted-user' )->plain() ); } } $rollbackResult = $this->rollbackPageFactory // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here ->newRollbackPage( $this->getWikiPage(), $this->getAuthority(), $revUser ) ->setSummary( $request->getText( 'summary' ) ) ->markAsBot( $request->getBool( 'bot' ) ) ->rollbackIfAllowed(); $data = $rollbackResult->getValue(); if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) { throw new ThrottledError; } # NOTE: Permission errors already handled by Action::checkExecute. if ( $rollbackResult->hasMessage( 'readonlytext' ) ) { throw new ReadOnlyError; } if ( $rollbackResult->getMessages() ) { $this->getOutput()->setPageTitleMsg( $this->msg( 'rollbackfailed' ) ); foreach ( $rollbackResult->getMessages() as $msg ) { $this->getOutput()->addWikiMsg( $msg ); } if ( ( $rollbackResult->hasMessage( 'alreadyrolled' ) || $rollbackResult->hasMessage( 'cantrollback' ) ) && isset( $data['current-revision-record'] ) ) { /** @var RevisionRecord $current */ $current = $data['current-revision-record']; if ( $current->getComment() != null ) { $this->getOutput()->addWikiMsg( 'editcomment', Message::rawParam( $this->commentFormatter ->format( $current->getComment()->text ) ) ); } } return; } /** @var RevisionRecord $current */ $current = $data['current-revision-record']; $target = $data['target-revision-record']; $newId = $data['newid']; $this->getOutput()->setPageTitleMsg( $this->msg( 'actioncomplete' ) ); $this->getOutput()->setRobotPolicy( 'noindex,nofollow' ); $old = Linker::revUserTools( $current ); $new = Linker::revUserTools( $target ); $currentUser = $current->getUser( RevisionRecord::FOR_THIS_USER, $user ); $targetUser = $target->getUser( RevisionRecord::FOR_THIS_USER, $user ); $this->getOutput()->addHTML( $this->msg( 'rollback-success' ) ->rawParams( $old, $new ) ->params( $currentUser ? $currentUser->getName() : '' ) ->params( $targetUser ? $targetUser->getName() : '' ) ->parseAsBlock() ); // Load the mediawiki.misc-authed-curate module, so that we can fire the JavaScript // postEdit hook on a successful rollback. $this->getOutput()->addModules( 'mediawiki.misc-authed-curate' ); // Export a success flag to the frontend, so that the mediawiki.misc-authed-curate // ResourceLoader module can use this as an indicator to fire the postEdit hook. $this->getOutput()->addJsConfigVars( [ 'wgRollbackSuccess' => true, // Don't show an edit confirmation with mw.notify(), the rollback success page // is already a visual confirmation. 'wgPostEditConfirmationDisabled' => true, ] ); if ( $this->userOptionsLookup->getBoolOption( $user, 'watchrollback' ) ) { $this->watchlistManager->addWatchIgnoringRights( $user, $this->getTitle() ); } $this->getOutput()->returnToMain( false, $this->getTitle() ); if ( !$request->getBool( 'hidediff', false ) && !$this->userOptionsLookup->getBoolOption( $this->getUser(), 'norollbackdiff' ) ) { $contentModel = $current->getMainContentModel(); $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel ); $de = $contentHandler->createDifferenceEngine( $this->getContext(), $current->getId(), $newId, 0, true ); $de->showDiff( '', '' ); } } /** * Enables transactional time limit for POST and GET requests to RollbackAction * @throws ConfigException */ private function enableTransactionalTimelimit() { // If Rollbacks are made POST-only, use $this->useTransactionalTimeLimit() wfTransactionalTimeLimit(); if ( !$this->getRequest()->wasPosted() ) { /** * We apply the higher POST limits on GET requests * to prevent logstash.wikimedia.org from being spammed */ $fname = __METHOD__; $trxLimits = $this->context->getConfig()->get( MainConfigNames::TrxProfilerLimits ); $trxProfiler = Profiler::instance()->getTransactionProfiler(); $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname ); DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname ) { $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname ); } ); } } private function showRollbackConfirmationForm() { $form = $this->getForm(); if ( $form->show() ) { $this->onSuccess(); } } protected function getFormFields() { return [ 'intro' => [ 'type' => 'info', 'raw' => true, 'default' => $this->msg( 'confirm-rollback-bottom' )->parse() ] ]; } } /** @deprecated class alias since 1.44 */ class_alias( RollbackAction::class, 'RollbackAction' );