MediaWiki master
RollbackAction.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Actions;
11
31use Profiler;
32
39
40 private IContentHandlerFactory $contentHandlerFactory;
41 private RollbackPageFactory $rollbackPageFactory;
42 private UserOptionsLookup $userOptionsLookup;
43 private WatchlistManager $watchlistManager;
44 private CommentFormatter $commentFormatter;
45
55 public function __construct(
56 Article $article,
58 IContentHandlerFactory $contentHandlerFactory,
59 RollbackPageFactory $rollbackPageFactory,
60 UserOptionsLookup $userOptionsLookup,
61 WatchlistManager $watchlistManager,
62 CommentFormatter $commentFormatter
63 ) {
64 parent::__construct( $article, $context );
65 $this->contentHandlerFactory = $contentHandlerFactory;
66 $this->rollbackPageFactory = $rollbackPageFactory;
67 $this->userOptionsLookup = $userOptionsLookup;
68 $this->watchlistManager = $watchlistManager;
69 $this->commentFormatter = $commentFormatter;
70 }
71
73 public function getName() {
74 return 'rollback';
75 }
76
78 public function getRestriction() {
79 return 'rollback';
80 }
81
83 protected function usesOOUI() {
84 return true;
85 }
86
88 protected function getDescription() {
89 return '';
90 }
91
93 public function doesWrites() {
94 return true;
95 }
96
98 public function onSuccess() {
99 return false;
100 }
101
103 public function onSubmit( $data ) {
104 return false;
105 }
106
107 protected function alterForm( HTMLForm $form ) {
108 $form->setWrapperLegendMsg( 'confirm-rollback-top' );
109 $form->setSubmitTextMsg( 'confirm-rollback-button' );
110 $form->setTokenSalt( 'rollback' );
111
112 $from = $this->getRequest()->getVal( 'from' );
113 if ( $from === null ) {
114 throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' );
115 }
116 foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) {
117 $val = $this->getRequest()->getVal( $param );
118 if ( $val !== null ) {
119 $form->addHiddenField( $param, $val );
120 }
121 }
122 }
123
129 public function show() {
130 $this->setHeaders();
131 // This will throw exceptions if there's a problem
132 $this->checkCanExecute( $this->getUser() );
133
134 if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) ||
135 $this->getRequest()->wasPosted()
136 ) {
137 $this->handleRollbackRequest();
138 } else {
139 $this->showRollbackConfirmationForm();
140 }
141 }
142
143 public function handleRollbackRequest() {
144 $this->enableTransactionalTimelimit();
145 $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
146
147 $request = $this->getRequest();
148 $user = $this->getUser();
149 $from = $request->getVal( 'from' );
150 $rev = $this->getWikiPage()->getRevisionRecord();
151 if ( $from === null ) {
152 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
153 }
154 if ( !$rev ) {
155 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' );
156 }
157
158 $revUser = $rev->getUser();
159 $userText = $revUser ? $revUser->getName() : '';
160 if ( $from !== $userText ) {
161 throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [
162 $this->getTitle()->getPrefixedText(),
163 wfEscapeWikiText( $from ),
164 $userText
165 ] );
166 }
167
168 if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
169 throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
170 }
171
172 // The revision has the user suppressed, so the rollback has empty 'from',
173 // so the check above would succeed in that case.
174 // T307278 - Also check if the user has rights to view suppressed usernames
175 if ( !$revUser ) {
176 if ( $this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
177 $revUser = $rev->getUser( RevisionRecord::RAW );
178 } else {
179 $userFactory = MediaWikiServices::getInstance()->getUserFactory();
180 $revUser = $userFactory->newFromName( $this->context->msg( 'rev-deleted-user' )->plain() );
181 }
182 }
183
184 $rollbackResult = $this->rollbackPageFactory
185 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here
186 ->newRollbackPage( $this->getWikiPage(), $this->getAuthority(), $revUser )
187 ->setSummary( $request->getText( 'summary' ) )
188 ->markAsBot( $request->getBool( 'bot' ) )
189 ->rollbackIfAllowed();
190 $data = $rollbackResult->getValue();
191
192 if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) {
193 throw new ThrottledError;
194 }
195
196 # NOTE: Permission errors already handled by Action::checkExecute.
197 if ( $rollbackResult->hasMessage( 'readonlytext' ) ) {
198 throw new ReadOnlyError;
199 }
200
201 if ( $rollbackResult->getMessages() ) {
202 $this->getOutput()->setPageTitleMsg( $this->msg( 'rollbackfailed' ) );
203
204 foreach ( $rollbackResult->getMessages() as $msg ) {
205 $this->getOutput()->addWikiMsg( $msg );
206 }
207
208 if (
209 ( $rollbackResult->hasMessage( 'alreadyrolled' ) ||
210 $rollbackResult->hasMessage( 'cantrollback' ) ||
211 $rollbackResult->hasMessage( 'rollback-nochange' ) )
212 && isset( $data['current-revision-record'] )
213 ) {
215 $current = $data['current-revision-record'];
216 $comment = $current->getComment()?->text;
217
218 if ( $comment !== null && $comment !== '' ) {
219 $this->getOutput()->addWikiMsg(
220 'editcomment',
222 $this->commentFormatter
223 ->format( $comment )
224 )
225 );
226 }
227 }
228
229 return;
230 }
231
233 $current = $data['current-revision-record'];
234 $target = $data['target-revision-record'];
235 $newId = $data['newid'];
236 $this->getOutput()->setPageTitleMsg( $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 $userOptionsLookup = $this->userOptionsLookup;
245 $this->getOutput()->addHTML(
246 $this->msg( 'rollback-success' )
247 ->rawParams( $old, $new )
248 ->params( $currentUser ? $currentUser->getName() : '' )
249 ->params( $targetUser ? $targetUser->getName() : '' )
250 ->parseAsBlock()
251 );
252 // Load the mediawiki.misc-authed-curate module, so that we can fire the JavaScript
253 // postEdit hook on a successful rollback.
254 $this->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
255 // Export a success flag to the frontend, so that the mediawiki.misc-authed-curate
256 // ResourceLoader module can use this as an indicator to fire the postEdit hook.
257 $this->getOutput()->addJsConfigVars( [
258 'wgRollbackSuccess' => true,
259 // Don't show an edit confirmation with mw.notify(), the rollback success page
260 // is already a visual confirmation.
261 'wgPostEditConfirmationDisabled' => true,
262 ] );
263
264 // Watch the page for the user-chosen period of time, unless the page is already watched.
265 if ( $userOptionsLookup->getBoolOption( $user, 'watchrollback' ) &&
266 !$this->watchlistManager->isWatchedIgnoringRights( $user, $this->getTitle() )
267 ) {
268 $this->watchlistManager->addWatchIgnoringRights( $user, $this->getTitle(),
269 $userOptionsLookup->getOption( $user, 'watchrollback-expiry' ) );
270 }
271
272 $this->getOutput()->returnToMain( false, $this->getTitle() );
273
274 if ( !$request->getBool( 'hidediff', false ) &&
275 !$userOptionsLookup->getBoolOption( $this->getUser(), 'norollbackdiff' )
276 ) {
277 $contentModel = $current->getMainContentModel();
278 $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
279 $de = $contentHandler->createDifferenceEngine(
280 $this->getContext(),
281 $current->getId(),
282 $newId,
283 0,
284 true
285 );
286 $de->showDiff( '', '' );
287 }
288 }
289
294 private function enableTransactionalTimelimit() {
295 // If Rollbacks are made POST-only, use $this->useTransactionalTimeLimit()
297 if ( !$this->getRequest()->wasPosted() ) {
302 $fname = __METHOD__;
303 $trxLimits = $this->context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
304 $trxProfiler = Profiler::instance()->getTransactionProfiler();
305 $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
306 DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname
307 ) {
308 $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
309 } );
310 }
311 }
312
313 private function showRollbackConfirmationForm() {
314 $form = $this->getForm();
315 if ( $form->show() ) {
316 $this->onSuccess();
317 }
318 }
319
321 protected function getFormFields() {
322 return [
323 'intro' => [
324 'type' => 'info',
325 'raw' => true,
326 'default' => $this->msg( 'confirm-rollback-bottom' )->parse()
327 ]
328 ];
329 }
330}
331
333class_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:119
getWikiPage()
Get a WikiPage object.
Definition Action.php:192
IContextSource null $context
IContextSource if specified; otherwise we'll use the Context from the Page.
Definition Action.php:66
setHeaders()
Set output headers for noindexing etc.
Definition Action.php:407
getUser()
Shortcut to get the User being used for this instance.
Definition Action.php:153
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:227
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
Definition Action.php:327
getTitle()
Shortcut to get the Title object from the page.
Definition Action.php:213
getRequest()
Get the WebRequest being used for this instance.
Definition Action.php:133
getOutput()
Get the OutputPage being used for this instance.
Definition Action.php:143
getAuthority()
Shortcut to get the Authority executing this instance.
Definition Action.php:163
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.Often, but not always, the same as the action name...
onSuccess()
Do something exciting on successful processing of the form.This might be to show a confirmation messa...
__construct(Article $article, IContextSource $context, IContentHandlerFactory $contentHandlerFactory, RollbackPageFactory $rollbackPageFactory, UserOptionsLookup $userOptionsLookup, WatchlistManager $watchlistManager, CommentFormatter $commentFormatter)
usesOOUI()
Whether the form should use OOUI.to override bool
getDescription()
Returns the description that goes below the <h1> element.1.17 to override string HTML
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
getName()
Return the name of the action this object responds to.1.17string Lowercase name
getFormFields()
Get an HTMLForm descriptor array.to override array
onSubmit( $data)
Process the form on POST submission.If you don't want to do anything with the form,...
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:195
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:47
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:144
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:64
Page revision base class.
Provides access to user options.
getBoolOption(UserIdentity $user, string $oname, int $queryFlags=IDBAccessObject::READ_NORMAL)
Get the user's current setting for a given option, as a boolean value.
getOption(UserIdentity $user, string $oname, $defaultOverride=null, bool $ignoreHidden=false, int $queryFlags=IDBAccessObject::READ_NORMAL)
Get the user's current setting for a given option.
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:22
static instance()
Definition Profiler.php:90
Interface for objects which can provide a MediaWiki context on request.
Service for page rollback actions.