MediaWiki  master
RollbackAction.php
Go to the documentation of this file.
1 <?php
32 
38 class RollbackAction extends FormAction {
39 
41  private $contentHandlerFactory;
42 
44  private $rollbackPageFactory;
45 
47  private $userOptionsLookup;
48 
50  private $watchlistManager;
51 
53  private $commentFormatter;
54 
64  public function __construct(
65  Article $article,
67  IContentHandlerFactory $contentHandlerFactory,
68  RollbackPageFactory $rollbackPageFactory,
69  UserOptionsLookup $userOptionsLookup,
70  WatchlistManager $watchlistManager,
71  CommentFormatter $commentFormatter
72  ) {
73  parent::__construct( $article, $context );
74  $this->contentHandlerFactory = $contentHandlerFactory;
75  $this->rollbackPageFactory = $rollbackPageFactory;
76  $this->userOptionsLookup = $userOptionsLookup;
77  $this->watchlistManager = $watchlistManager;
78  $this->commentFormatter = $commentFormatter;
79  }
80 
81  public function getName() {
82  return 'rollback';
83  }
84 
85  public function getRestriction() {
86  return 'rollback';
87  }
88 
89  protected function usesOOUI() {
90  return true;
91  }
92 
93  protected function getDescription() {
94  return '';
95  }
96 
97  public function doesWrites() {
98  return true;
99  }
100 
101  public function onSuccess() {
102  return false;
103  }
104 
105  public function onSubmit( $data ) {
106  return false;
107  }
108 
109  protected function alterForm( HTMLForm $form ) {
110  $form->setWrapperLegendMsg( 'confirm-rollback-top' );
111  $form->setSubmitTextMsg( 'confirm-rollback-button' );
112  $form->setTokenSalt( 'rollback' );
113 
114  $from = $this->getRequest()->getVal( 'from' );
115  if ( $from === null ) {
116  throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' );
117  }
118  foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) {
119  $val = $this->getRequest()->getVal( $param );
120  if ( $val !== null ) {
121  $form->addHiddenField( $param, $val );
122  }
123  }
124  }
125 
131  public function show() {
132  $this->setHeaders();
133  // This will throw exceptions if there's a problem
134  $this->checkCanExecute( $this->getUser() );
135 
136  if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) ||
137  $this->getRequest()->wasPosted()
138  ) {
139  $this->handleRollbackRequest();
140  } else {
141  $this->showRollbackConfirmationForm();
142  }
143  }
144 
145  public function handleRollbackRequest() {
146  $this->enableTransactionalTimelimit();
147  $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
148 
149  $request = $this->getRequest();
150  $user = $this->getUser();
151  $from = $request->getVal( 'from' );
152  $rev = $this->getWikiPage()->getRevisionRecord();
153  if ( $from === null ) {
154  throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
155  }
156  if ( !$rev ) {
157  throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' );
158  }
159 
160  $revUser = $rev->getUser();
161  $userText = $revUser ? $revUser->getName() : '';
162  if ( $from !== $userText ) {
163  throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [
164  $this->getTitle()->getPrefixedText(),
165  wfEscapeWikiText( $from ),
166  $userText
167  ] );
168  }
169 
170  if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
171  throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
172  }
173 
174  // The revision has the user suppressed, so the rollback has empty 'from',
175  // so the check above would succeed in that case.
176  // T307278 - Also check if the user has rights to view suppressed usernames
177  if ( !$revUser ) {
178  if ( $this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
179  $revUser = $rev->getUser( RevisionRecord::RAW );
180  } else {
181  $userFactory = MediaWikiServices::getInstance()->getUserFactory();
182  $revUser = $userFactory->newFromName( $this->context->msg( 'rev-deleted-user' )->plain() );
183  }
184  }
185 
186  $rollbackResult = $this->rollbackPageFactory
187  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here
188  ->newRollbackPage( $this->getWikiPage(), $this->getAuthority(), $revUser )
189  ->setSummary( $request->getText( 'summary' ) )
190  ->markAsBot( $request->getBool( 'bot' ) )
191  ->rollbackIfAllowed();
192  $data = $rollbackResult->getValue();
193 
194  if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) {
195  throw new ThrottledError;
196  }
197 
198  if ( $rollbackResult->hasMessage( 'alreadyrolled' ) || $rollbackResult->hasMessage( 'cantrollback' ) ) {
199  $this->getOutput()->setPageTitle( $this->msg( 'rollbackfailed' ) );
200  $errArray = $rollbackResult->getErrors()[0];
201  $this->getOutput()->addWikiMsgArray( $errArray['message'], $errArray['params'] );
202 
203  if ( isset( $data['current-revision-record'] ) ) {
205  $current = $data['current-revision-record'];
206 
207  if ( $current->getComment() != null ) {
208  $this->getOutput()->addWikiMsg(
209  'editcomment',
211  $this->commentFormatter
212  ->format( $current->getComment()->text )
213  )
214  );
215  }
216  }
217 
218  return;
219  }
220 
221  # NOTE: Permission errors already handled by Action::checkExecute.
222  if ( $rollbackResult->hasMessage( 'readonlytext' ) ) {
223  throw new ReadOnlyError;
224  }
225 
226  # XXX: Would be nice if ErrorPageError could take multiple errors, and/or a status object.
227  # Right now, we only show the first error
228  foreach ( $rollbackResult->getErrors() as $error ) {
229  throw new ErrorPageError( 'rollbackfailed', $error['message'], $error['params'] );
230  }
231 
233  $current = $data['current-revision-record'];
234  $target = $data['target-revision-record'];
235  $newId = $data['newid'];
236  $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
237  $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
238 
239  $old = Linker::revUserTools( $current );
240  $new = Linker::revUserTools( $target );
241 
242  $currentUser = $current->getUser( RevisionRecord::FOR_THIS_USER, $user );
243  $targetUser = $target->getUser( RevisionRecord::FOR_THIS_USER, $user );
244  $this->getOutput()->addHTML(
245  $this->msg( 'rollback-success' )
246  ->rawParams( $old, $new )
247  ->params( $currentUser ? $currentUser->getName() : '' )
248  ->params( $targetUser ? $targetUser->getName() : '' )
249  ->parseAsBlock()
250  );
251 
252  if ( $this->userOptionsLookup->getBoolOption( $user, 'watchrollback' ) ) {
253  $this->watchlistManager->addWatchIgnoringRights( $user, $this->getTitle() );
254  }
255 
256  $this->getOutput()->returnToMain( false, $this->getTitle() );
257 
258  if ( !$request->getBool( 'hidediff', false ) &&
259  !$this->userOptionsLookup->getBoolOption( $this->getUser(), 'norollbackdiff' )
260  ) {
261  $contentModel = $current->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )
262  ->getModel();
263  $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
264  $de = $contentHandler->createDifferenceEngine(
265  $this->getContext(),
266  $current->getId(),
267  $newId,
268  0,
269  true
270  );
271  $de->showDiff( '', '' );
272  }
273  }
274 
279  private function enableTransactionalTimelimit() {
280  // If Rollbacks are made POST-only, use $this->useTransactionalTimeLimit()
282  if ( !$this->getRequest()->wasPosted() ) {
287  $fname = __METHOD__;
288  $trxLimits = $this->context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
289  $trxProfiler = Profiler::instance()->getTransactionProfiler();
290  $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
291  DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname
292  ) {
293  $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
294  } );
295  }
296  }
297 
298  private function showRollbackConfirmationForm() {
299  $form = $this->getForm();
300  if ( $form->show() ) {
301  $this->onSuccess();
302  }
303  }
304 
305  protected function getFormFields() {
306  return [
307  'intro' => [
308  'type' => 'info',
309  'raw' => true,
310  'default' => $this->msg( 'confirm-rollback-bottom' )->parse()
311  ]
312  ];
313  }
314 }
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:203
IContextSource null $context
IContextSource if specified; otherwise we'll use the Context from the Page.
Definition: Action.php:57
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
Definition: Action.php:328
getTitle()
Shortcut to get the Title object from the page.
Definition: Action.php:224
getContext()
Get the IContextSource in use here.
Definition: Action.php:130
getOutput()
Get the OutputPage being used for this instance.
Definition: Action.php:154
getUser()
Shortcut to get the User being used for this instance.
Definition: Action.php:164
setHeaders()
Set output headers for noindexing etc.
Definition: Action.php:394
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: Action.php:236
getAuthority()
Shortcut to get the Authority executing this instance.
Definition: Action.php:174
getRequest()
Get the WebRequest being used for this instance.
Definition: Action.php:144
Legacy class representing an editable page and handling UI for some page actions.
Definition: Article.php:49
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:1613
setWrapperLegendMsg( $msg)
Prompt the whole form to be wrapped in a "<fieldset>", with this message as its "<legend>" element.
Definition: HTMLForm.php:1818
addHiddenField( $name, $value, array $attribs=[])
Add a hidden field to the output.
Definition: HTMLForm.php:1135
setTokenSalt( $salt)
Set the salt for the edit token.
Definition: HTMLForm.php:1224
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:1315
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.
Service locator for MediaWiki core services.
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:1133
static instance()
Singleton.
Definition: Profiler.php:94
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.
getFormFields()
Get an HTMLForm descriptor array.
__construct(Article $article, IContextSource $context, IContentHandlerFactory $contentHandlerFactory, RollbackPageFactory $rollbackPageFactory, UserOptionsLookup $userOptionsLookup, WatchlistManager $watchlistManager, CommentFormatter $commentFormatter)
getDescription()
Returns the description that goes below the <h1> element.
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.
onSuccess()
Do something exciting on successful processing of the form.
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.