MediaWiki  master
RollbackAction.php
Go to the documentation of this file.
1 <?php
31 
37 class RollbackAction extends FormAction {
38 
41 
44 
47 
50 
53 
63  public function __construct(
64  Page $page,
71  ) {
72  parent::__construct( $page, $context );
73  $this->contentHandlerFactory = $contentHandlerFactory;
74  $this->rollbackPageFactory = $rollbackPageFactory;
75  $this->userOptionsLookup = $userOptionsLookup;
76  $this->watchlistManager = $watchlistManager;
77  $this->commentFormatter = $commentFormatter;
78  }
79 
80  public function getName() {
81  return 'rollback';
82  }
83 
84  public function getRestriction() {
85  return 'rollback';
86  }
87 
88  protected function usesOOUI() {
89  return true;
90  }
91 
92  protected function getDescription() {
93  return '';
94  }
95 
96  public function doesWrites() {
97  return true;
98  }
99 
100  public function onSuccess() {
101  return false;
102  }
103 
104  public function onSubmit( $data ) {
105  return false;
106  }
107 
108  protected function alterForm( HTMLForm $form ) {
109  $form->setWrapperLegendMsg( 'confirm-rollback-top' );
110  $form->setSubmitTextMsg( 'confirm-rollback-button' );
111  $form->setTokenSalt( 'rollback' );
112 
113  $from = $this->getRequest()->getVal( 'from' );
114  if ( $from === null ) {
115  throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' );
116  }
117  foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) {
118  $val = $this->getRequest()->getVal( $param );
119  if ( $val !== null ) {
120  $form->addHiddenField( $param, $val );
121  }
122  }
123  }
124 
130  public function show() {
131  $this->setHeaders();
132  // This will throw exceptions if there's a problem
133  $this->checkCanExecute( $this->getUser() );
134 
135  if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) ||
136  $this->getRequest()->wasPosted()
137  ) {
138  $this->handleRollbackRequest();
139  } else {
141  }
142  }
143 
144  public function handleRollbackRequest() {
146  $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
147 
148  $request = $this->getRequest();
149  $user = $this->getUser();
150  $from = $request->getVal( 'from' );
151  $rev = $this->getWikiPage()->getRevisionRecord();
152  if ( $from === null ) {
153  throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
154  }
155  if ( !$rev ) {
156  throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' );
157  }
158 
159  $revUser = $rev->getUser();
160  $userText = $revUser ? $revUser->getName() : '';
161  if ( $from !== $userText ) {
162  throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [
163  $this->getTitle()->getPrefixedText(),
164  wfEscapeWikiText( $from ),
165  $userText
166  ] );
167  }
168 
169  if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
170  throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
171  }
172 
173  // The revision has the user suppressed, so the rollback has empty 'from',
174  // so the check above would succeed in that case.
175  if ( !$revUser ) {
176  $revUser = $rev->getUser( RevisionRecord::RAW );
177  }
178 
179  $rollbackResult = $this->rollbackPageFactory
180  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here
181  ->newRollbackPage( $this->getWikiPage(), $this->getContext()->getAuthority(), $revUser )
182  ->setSummary( $request->getText( 'summary' ) )
183  ->markAsBot( $request->getBool( 'bot' ) )
184  ->rollbackIfAllowed();
185  $data = $rollbackResult->getValue();
186 
187  if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) {
188  throw new ThrottledError;
189  }
190 
191  if ( $rollbackResult->hasMessage( 'alreadyrolled' ) || $rollbackResult->hasMessage( 'cantrollback' ) ) {
192  $this->getOutput()->setPageTitle( $this->msg( 'rollbackfailed' ) );
193  $errArray = $rollbackResult->getErrors()[0];
194  $this->getOutput()->addWikiMsgArray( $errArray['message'], $errArray['params'] );
195 
196  if ( isset( $data['current-revision-record'] ) ) {
198  $current = $data['current-revision-record'];
199 
200  if ( $current->getComment() != null ) {
201  $this->getOutput()->addWikiMsg(
202  'editcomment',
204  $this->commentFormatter
205  ->format( $current->getComment()->text )
206  )
207  );
208  }
209  }
210 
211  return;
212  }
213 
214  # NOTE: Permission errors already handled by Action::checkExecute.
215  if ( $rollbackResult->hasMessage( 'readonlytext' ) ) {
216  throw new ReadOnlyError;
217  }
218 
219  # XXX: Would be nice if ErrorPageError could take multiple errors, and/or a status object.
220  # Right now, we only show the first error
221  foreach ( $rollbackResult->getErrors() as $error ) {
222  throw new ErrorPageError( 'rollbackfailed', $error['message'], $error['params'] );
223  }
224 
226  $current = $data['current-revision-record'];
227  $target = $data['target-revision-record'];
228  $newId = $data['newid'];
229  $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
230  $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
231 
232  $old = Linker::revUserTools( $current );
233  $new = Linker::revUserTools( $target );
234 
235  $currentUser = $current->getUser( RevisionRecord::FOR_THIS_USER, $user );
236  $targetUser = $target->getUser( RevisionRecord::FOR_THIS_USER, $user );
237  $this->getOutput()->addHTML(
238  $this->msg( 'rollback-success' )
239  ->rawParams( $old, $new )
240  ->params( $currentUser ? $currentUser->getName() : '' )
241  ->params( $targetUser ? $targetUser->getName() : '' )
242  ->parseAsBlock()
243  );
244 
245  if ( $this->userOptionsLookup->getBoolOption( $user, 'watchrollback' ) ) {
246  $this->watchlistManager->addWatchIgnoringRights( $user, $this->getTitle() );
247  }
248 
249  $this->getOutput()->returnToMain( false, $this->getTitle() );
250 
251  if ( !$request->getBool( 'hidediff', false ) &&
252  !$this->userOptionsLookup->getBoolOption( $this->getUser(), 'norollbackdiff' )
253  ) {
254  $contentModel = $current->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )
255  ->getModel();
256  $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
257  $de = $contentHandler->createDifferenceEngine(
258  $this->getContext(),
259  $current->getId(),
260  $newId,
261  0,
262  true
263  );
264  $de->showDiff( '', '' );
265  }
266  }
267 
272  private function enableTransactionalTimelimit() {
273  // If Rollbacks are made POST-only, use $this->useTransactionalTimeLimit()
275  if ( !$this->getRequest()->wasPosted() ) {
280  $fname = __METHOD__;
281  $trxLimits = $this->context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
282  $trxProfiler = Profiler::instance()->getTransactionProfiler();
283  $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
284  DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname
285  ) {
286  $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
287  } );
288  }
289  }
290 
291  private function showRollbackConfirmationForm() {
292  $form = $this->getForm();
293  if ( $form->show() ) {
294  $this->onSuccess();
295  }
296  }
297 
298  protected function getFormFields() {
299  return [
300  'intro' => [
301  'type' => 'info',
302  'raw' => true,
303  'default' => $this->msg( 'confirm-rollback-bottom' )->parse()
304  ]
305  ];
306  }
307 }
getAuthority()
wfTransactionalTimeLimit()
Raise the request time limit to $wgTransactionalTimeLimit.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
getWikiPage()
Get a WikiPage object.
Definition: Action.php:198
IContextSource null $context
IContextSource if specified; otherwise we'll use the Context from the Page.
Definition: Action.php:66
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
Definition: Action.php:342
WikiPage Article ImagePage CategoryPage Page $page
Page on which we're performing the action.
Definition: Action.php:53
getTitle()
Shortcut to get the Title object from the page.
Definition: Action.php:219
getContext()
Get the IContextSource in use here.
Definition: Action.php:135
getOutput()
Get the OutputPage being used for this instance.
Definition: Action.php:159
getUser()
Shortcut to get the User being used for this instance.
Definition: Action.php:169
setHeaders()
Set output headers for noindexing etc.
Definition: Action.php:408
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: Action.php:231
getRequest()
Get the WebRequest being used for this instance.
Definition: Action.php:149
An error page that emits an HTTP 400 Bad Request status code.
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
An error page which can definitely be safely rendered using the OutputPage.
An action which shows a form and does something based on the input from the form.
Definition: FormAction.php:30
getForm()
Get the HTMLForm to control behavior.
Definition: FormAction.php:81
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition: HTMLForm.php:150
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
Definition: HTMLForm.php:1578
setWrapperLegendMsg( $msg)
Prompt the whole form to be wrapped in a "<fieldset>", with this message as its "<legend>" element.
Definition: HTMLForm.php:1783
addHiddenField( $name, $value, array $attribs=[])
Add a hidden field to the output.
Definition: HTMLForm.php:1120
setTokenSalt( $salt)
Set the salt for the edit token.
Definition: HTMLForm.php:1209
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1346
This is the main service interface for converting single-line comments from various DB comment fields...
A class containing constants representing the names of configuration variables.
Page revision base class.
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
Provides access to user options.
static rawParam( $raw)
Definition: Message.php:1155
static instance()
Singleton.
Definition: Profiler.php:69
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
User interface for the rollback action.
UserOptionsLookup $userOptionsLookup
getFormFields()
Get an HTMLForm descriptor array.
CommentFormatter $commentFormatter
getDescription()
Returns the description that goes below the <h1> element.
enableTransactionalTimelimit()
Enables transactional time limit for POST and GET requests to RollbackAction.
usesOOUI()
Whether the form should use OOUI.
onSubmit( $data)
Process the form on POST submission.
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
getRestriction()
Get the permission required to perform this action.
IContentHandlerFactory $contentHandlerFactory
__construct(Page $page, ?IContextSource $context, IContentHandlerFactory $contentHandlerFactory, RollbackPageFactory $rollbackPageFactory, UserOptionsLookup $userOptionsLookup, WatchlistManager $watchlistManager, CommentFormatter $commentFormatter)
RollbackPageFactory $rollbackPageFactory
onSuccess()
Do something exciting on successful processing of the form.
WatchlistManager $watchlistManager
getName()
Return the name of the action this object responds to.
Show an error when the user hits a rate limit.
Interface for objects which can provide a MediaWiki context on request.
Service for page rollback actions.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:29