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() { if ( $this->getUser()->getOption( 'showrollbackconfirmation' ) == false || $this->getRequest()->wasPosted() ) { $this->handleRollbackRequest(); } else { $this->showRollbackConfirmationForm(); } } public function handleRollbackRequest() { $this->enableTransactionalTimelimit(); $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(), $from, $userText ] ); } $data = null; $errors = $this->getWikiPage()->doRollback( $from, $request->getText( 'summary' ), $request->getVal( 'token' ), $request->getBool( 'bot' ), $data, $this->getUser() ); if ( in_array( [ 'actionthrottledtext' ], $errors ) ) { throw new ThrottledError; } if ( $this->hasRollbackRelatedErrors( $errors ) ) { $this->getOutput()->setPageTitle( $this->msg( 'rollbackfailed' ) ); $errArray = $errors[0]; $errMsg = array_shift( $errArray ); $this->getOutput()->addWikiMsgArray( $errMsg, $errArray ); if ( isset( $data['current-revision-record'] ) ) { /** @var RevisionRecord $current */ $current = $data['current-revision-record']; if ( $current->getComment() != null ) { $this->getOutput()->addWikiMsg( 'editcomment', Message::rawParam( Linker::formatComment( $current->getComment()->text ) ) ); } } return; } # NOTE: Permission errors already handled by Action::checkExecute. if ( $errors == [ [ 'readonlytext' ] ] ) { throw new ReadOnlyError; } # XXX: Would be nice if ErrorPageError could take multiple errors, and/or a status object. # Right now, we only show the first error foreach ( $errors as $error ) { throw new ErrorPageError( 'rollbackfailed', $error[0], array_slice( $error, 1 ) ); } /** @var RevisionRecord $current */ $current = $data['current-revision-record']; $target = $data['target-revision-record']; $newId = $data['newid']; $this->getOutput()->setPageTitle( $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() ); if ( $user->getBoolOption( 'watchrollback' ) ) { $user->addWatch( $this->getTitle(), User::IGNORE_USER_RIGHTS ); } $this->getOutput()->returnToMain( false, $this->getTitle() ); if ( !$request->getBool( 'hidediff', false ) && !$this->getUser()->getBoolOption( 'norollbackdiff' ) ) { $contentModel = $current->getSlot( SlotRecord::MAIN, RevisionRecord::RAW ) ->getModel(); $contentHandler = MediaWikiServices::getInstance() ->getContentHandlerFactory() ->getContentHandler( $contentModel ); $de = $contentHandler->createDifferenceEngine( $this->getContext(), $current->getId(), $newId, false, 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( 'TrxProfilerLimits' ); $trxProfiler = Profiler::instance()->getTransactionProfiler(); $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname ); DeferredUpdates::addCallableUpdate( 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', 'vertical-label' => true, 'raw' => true, 'default' => $this->msg( 'confirm-rollback-bottom' )->parse() ] ]; } private function hasRollbackRelatedErrors( array $errors ) { return isset( $errors[0][0] ) && ( $errors[0][0] == 'alreadyrolled' || $errors[0][0] == 'cantrollback' ); } }