MediaWiki master
RollbackAction.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Actions;
24
44use Profiler;
45
52
53 private IContentHandlerFactory $contentHandlerFactory;
54 private RollbackPageFactory $rollbackPageFactory;
55 private UserOptionsLookup $userOptionsLookup;
56 private WatchlistManager $watchlistManager;
57 private CommentFormatter $commentFormatter;
58
68 public function __construct(
69 Article $article,
71 IContentHandlerFactory $contentHandlerFactory,
72 RollbackPageFactory $rollbackPageFactory,
73 UserOptionsLookup $userOptionsLookup,
74 WatchlistManager $watchlistManager,
75 CommentFormatter $commentFormatter
76 ) {
77 parent::__construct( $article, $context );
78 $this->contentHandlerFactory = $contentHandlerFactory;
79 $this->rollbackPageFactory = $rollbackPageFactory;
80 $this->userOptionsLookup = $userOptionsLookup;
81 $this->watchlistManager = $watchlistManager;
82 $this->commentFormatter = $commentFormatter;
83 }
84
85 public function getName() {
86 return 'rollback';
87 }
88
89 public function getRestriction() {
90 return 'rollback';
91 }
92
93 protected function usesOOUI() {
94 return true;
95 }
96
97 protected function getDescription() {
98 return '';
99 }
100
101 public function doesWrites() {
102 return true;
103 }
104
105 public function onSuccess() {
106 return false;
107 }
108
109 public function onSubmit( $data ) {
110 return false;
111 }
112
113 protected function alterForm( HTMLForm $form ) {
114 $form->setWrapperLegendMsg( 'confirm-rollback-top' );
115 $form->setSubmitTextMsg( 'confirm-rollback-button' );
116 $form->setTokenSalt( 'rollback' );
117
118 $from = $this->getRequest()->getVal( 'from' );
119 if ( $from === null ) {
120 throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' );
121 }
122 foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) {
123 $val = $this->getRequest()->getVal( $param );
124 if ( $val !== null ) {
125 $form->addHiddenField( $param, $val );
126 }
127 }
128 }
129
135 public function show() {
136 $this->setHeaders();
137 // This will throw exceptions if there's a problem
138 $this->checkCanExecute( $this->getUser() );
139
140 if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) ||
141 $this->getRequest()->wasPosted()
142 ) {
143 $this->handleRollbackRequest();
144 } else {
145 $this->showRollbackConfirmationForm();
146 }
147 }
148
149 public function handleRollbackRequest() {
150 $this->enableTransactionalTimelimit();
151 $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
152
153 $request = $this->getRequest();
154 $user = $this->getUser();
155 $from = $request->getVal( 'from' );
156 $rev = $this->getWikiPage()->getRevisionRecord();
157 if ( $from === null ) {
158 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
159 }
160 if ( !$rev ) {
161 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' );
162 }
163
164 $revUser = $rev->getUser();
165 $userText = $revUser ? $revUser->getName() : '';
166 if ( $from !== $userText ) {
167 throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [
168 $this->getTitle()->getPrefixedText(),
169 wfEscapeWikiText( $from ),
170 $userText
171 ] );
172 }
173
174 if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
175 throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
176 }
177
178 // The revision has the user suppressed, so the rollback has empty 'from',
179 // so the check above would succeed in that case.
180 // T307278 - Also check if the user has rights to view suppressed usernames
181 if ( !$revUser ) {
182 if ( $this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
183 $revUser = $rev->getUser( RevisionRecord::RAW );
184 } else {
185 $userFactory = MediaWikiServices::getInstance()->getUserFactory();
186 $revUser = $userFactory->newFromName( $this->context->msg( 'rev-deleted-user' )->plain() );
187 }
188 }
189
190 $rollbackResult = $this->rollbackPageFactory
191 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here
192 ->newRollbackPage( $this->getWikiPage(), $this->getAuthority(), $revUser )
193 ->setSummary( $request->getText( 'summary' ) )
194 ->markAsBot( $request->getBool( 'bot' ) )
195 ->rollbackIfAllowed();
196 $data = $rollbackResult->getValue();
197
198 if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) {
199 throw new ThrottledError;
200 }
201
202 # NOTE: Permission errors already handled by Action::checkExecute.
203 if ( $rollbackResult->hasMessage( 'readonlytext' ) ) {
204 throw new ReadOnlyError;
205 }
206
207 if ( $rollbackResult->getMessages() ) {
208 $this->getOutput()->setPageTitleMsg( $this->msg( 'rollbackfailed' ) );
209
210 foreach ( $rollbackResult->getMessages() as $msg ) {
211 $this->getOutput()->addWikiMsg( $msg );
212 }
213
214 if (
215 ( $rollbackResult->hasMessage( 'alreadyrolled' ) || $rollbackResult->hasMessage( 'cantrollback' ) )
216 && isset( $data['current-revision-record'] )
217 ) {
219 $current = $data['current-revision-record'];
220
221 if ( $current->getComment() != null ) {
222 $this->getOutput()->addWikiMsg(
223 'editcomment',
225 $this->commentFormatter
226 ->format( $current->getComment()->text )
227 )
228 );
229 }
230 }
231
232 return;
233 }
234
236 $current = $data['current-revision-record'];
237 $target = $data['target-revision-record'];
238 $newId = $data['newid'];
239 $this->getOutput()->setPageTitleMsg( $this->msg( 'actioncomplete' ) );
240 $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
241
242 $old = Linker::revUserTools( $current );
243 $new = Linker::revUserTools( $target );
244
245 $currentUser = $current->getUser( RevisionRecord::FOR_THIS_USER, $user );
246 $targetUser = $target->getUser( RevisionRecord::FOR_THIS_USER, $user );
247 $this->getOutput()->addHTML(
248 $this->msg( 'rollback-success' )
249 ->rawParams( $old, $new )
250 ->params( $currentUser ? $currentUser->getName() : '' )
251 ->params( $targetUser ? $targetUser->getName() : '' )
252 ->parseAsBlock()
253 );
254 // Load the mediawiki.misc-authed-curate module, so that we can fire the JavaScript
255 // postEdit hook on a successful rollback.
256 $this->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
257 // Export a success flag to the frontend, so that the mediawiki.misc-authed-curate
258 // ResourceLoader module can use this as an indicator to fire the postEdit hook.
259 $this->getOutput()->addJsConfigVars( [
260 'wgRollbackSuccess' => true,
261 // Don't show an edit confirmation with mw.notify(), the rollback success page
262 // is already a visual confirmation.
263 'wgPostEditConfirmationDisabled' => true,
264 ] );
265
266 if ( $this->userOptionsLookup->getBoolOption( $user, 'watchrollback' ) ) {
267 $this->watchlistManager->addWatchIgnoringRights( $user, $this->getTitle() );
268 }
269
270 $this->getOutput()->returnToMain( false, $this->getTitle() );
271
272 if ( !$request->getBool( 'hidediff', false ) &&
273 !$this->userOptionsLookup->getBoolOption( $this->getUser(), 'norollbackdiff' )
274 ) {
275 $contentModel = $current->getMainContentModel();
276 $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
277 $de = $contentHandler->createDifferenceEngine(
278 $this->getContext(),
279 $current->getId(),
280 $newId,
281 0,
282 true
283 );
284 $de->showDiff( '', '' );
285 }
286 }
287
292 private function enableTransactionalTimelimit() {
293 // If Rollbacks are made POST-only, use $this->useTransactionalTimeLimit()
295 if ( !$this->getRequest()->wasPosted() ) {
300 $fname = __METHOD__;
301 $trxLimits = $this->context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
302 $trxProfiler = Profiler::instance()->getTransactionProfiler();
303 $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
304 DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname
305 ) {
306 $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
307 } );
308 }
309 }
310
311 private function showRollbackConfirmationForm() {
312 $form = $this->getForm();
313 if ( $form->show() ) {
314 $this->onSuccess();
315 }
316 }
317
318 protected function getFormFields() {
319 return [
320 'intro' => [
321 'type' => 'info',
322 'raw' => true,
323 'default' => $this->msg( 'confirm-rollback-bottom' )->parse()
324 ]
325 ];
326 }
327}
328
330class_alias( RollbackAction::class, 'RollbackAction' );
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTransactionalTimeLimit()
Raise the request time limit to $wgTransactionalTimeLimit.
getContext()
Get the IContextSource in use here.
Definition Action.php:132
getWikiPage()
Get a WikiPage object.
Definition Action.php:205
IContextSource null $context
IContextSource if specified; otherwise we'll use the Context from the Page.
Definition Action.php:79
setHeaders()
Set output headers for noindexing etc.
Definition Action.php:420
getUser()
Shortcut to get the User being used for this instance.
Definition Action.php:166
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:240
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
Definition Action.php:340
getTitle()
Shortcut to get the Title object from the page.
Definition Action.php:226
getRequest()
Get the WebRequest being used for this instance.
Definition Action.php:146
getOutput()
Get the OutputPage being used for this instance.
Definition Action.php:156
getAuthority()
Shortcut to get the Authority executing this instance.
Definition Action.php:176
An action which shows a form and does something based on the input from the form.
getForm()
Get the HTMLForm to control behavior.
User interface for the rollback action.
getRestriction()
Get the permission required to perform this action.
onSuccess()
Do something exciting on successful processing of the form.
__construct(Article $article, IContextSource $context, IContentHandlerFactory $contentHandlerFactory, RollbackPageFactory $rollbackPageFactory, UserOptionsLookup $userOptionsLookup, WatchlistManager $watchlistManager, CommentFormatter $commentFormatter)
usesOOUI()
Whether the form should use OOUI.
getDescription()
Returns the description that goes below the <h1> element.
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
getName()
Return the name of the action this object responds to.
getFormFields()
Get an HTMLForm descriptor array.
onSubmit( $data)
Process the form on POST submission.
This is the main service interface for converting single-line comments from various DB comment fields...
Exceptions for config failures.
Defer callable updates to run later in the PHP process.
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dependeeDbws=[])
Add an update to the pending update queue that invokes the specified callback when run.
An error page that emits an HTTP 400 Bad Request status code.
An error page which can definitely be safely rendered using the OutputPage.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Show an error when the user hits a rate limit.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:210
setWrapperLegendMsg( $msg)
Prompt the whole form to be wrapped in a "<fieldset>", with this message as its "<legend>" element.
setTokenSalt( $salt)
Set the salt for the edit token.
addHiddenField( $name, $value, array $attribs=[])
Add a hidden field to the output Array values are discarded for security reasons (per WebRequest::get...
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
Some internal bits split of from Skin.php.
Definition Linker.php:62
A class containing constants representing the names of configuration variables.
const TrxProfilerLimits
Name constant for the TrxProfilerLimits setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:76
Page revision base class.
Provides access to user options.
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:37
static instance()
Definition Profiler.php:105
Interface for objects which can provide a MediaWiki context on request.
Service for page rollback actions.