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
86 public function getName() {
87 return 'rollback';
88 }
89
91 public function getRestriction() {
92 return 'rollback';
93 }
94
96 protected function usesOOUI() {
97 return true;
98 }
99
101 protected function getDescription() {
102 return '';
103 }
104
106 public function doesWrites() {
107 return true;
108 }
109
111 public function onSuccess() {
112 return false;
113 }
114
116 public function onSubmit( $data ) {
117 return false;
118 }
119
120 protected function alterForm( HTMLForm $form ) {
121 $form->setWrapperLegendMsg( 'confirm-rollback-top' );
122 $form->setSubmitTextMsg( 'confirm-rollback-button' );
123 $form->setTokenSalt( 'rollback' );
124
125 $from = $this->getRequest()->getVal( 'from' );
126 if ( $from === null ) {
127 throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' );
128 }
129 foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) {
130 $val = $this->getRequest()->getVal( $param );
131 if ( $val !== null ) {
132 $form->addHiddenField( $param, $val );
133 }
134 }
135 }
136
142 public function show() {
143 $this->setHeaders();
144 // This will throw exceptions if there's a problem
145 $this->checkCanExecute( $this->getUser() );
146
147 if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) ||
148 $this->getRequest()->wasPosted()
149 ) {
150 $this->handleRollbackRequest();
151 } else {
152 $this->showRollbackConfirmationForm();
153 }
154 }
155
156 public function handleRollbackRequest() {
157 $this->enableTransactionalTimelimit();
158 $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
159
160 $request = $this->getRequest();
161 $user = $this->getUser();
162 $from = $request->getVal( 'from' );
163 $rev = $this->getWikiPage()->getRevisionRecord();
164 if ( $from === null ) {
165 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
166 }
167 if ( !$rev ) {
168 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' );
169 }
170
171 $revUser = $rev->getUser();
172 $userText = $revUser ? $revUser->getName() : '';
173 if ( $from !== $userText ) {
174 throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [
175 $this->getTitle()->getPrefixedText(),
176 wfEscapeWikiText( $from ),
177 $userText
178 ] );
179 }
180
181 if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
182 throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
183 }
184
185 // The revision has the user suppressed, so the rollback has empty 'from',
186 // so the check above would succeed in that case.
187 // T307278 - Also check if the user has rights to view suppressed usernames
188 if ( !$revUser ) {
189 if ( $this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
190 $revUser = $rev->getUser( RevisionRecord::RAW );
191 } else {
192 $userFactory = MediaWikiServices::getInstance()->getUserFactory();
193 $revUser = $userFactory->newFromName( $this->context->msg( 'rev-deleted-user' )->plain() );
194 }
195 }
196
197 $rollbackResult = $this->rollbackPageFactory
198 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here
199 ->newRollbackPage( $this->getWikiPage(), $this->getAuthority(), $revUser )
200 ->setSummary( $request->getText( 'summary' ) )
201 ->markAsBot( $request->getBool( 'bot' ) )
202 ->rollbackIfAllowed();
203 $data = $rollbackResult->getValue();
204
205 if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) {
206 throw new ThrottledError;
207 }
208
209 # NOTE: Permission errors already handled by Action::checkExecute.
210 if ( $rollbackResult->hasMessage( 'readonlytext' ) ) {
211 throw new ReadOnlyError;
212 }
213
214 if ( $rollbackResult->getMessages() ) {
215 $this->getOutput()->setPageTitleMsg( $this->msg( 'rollbackfailed' ) );
216
217 foreach ( $rollbackResult->getMessages() as $msg ) {
218 $this->getOutput()->addWikiMsg( $msg );
219 }
220
221 if (
222 ( $rollbackResult->hasMessage( 'alreadyrolled' ) || $rollbackResult->hasMessage( 'cantrollback' ) )
223 && isset( $data['current-revision-record'] )
224 ) {
226 $current = $data['current-revision-record'];
227
228 if ( $current->getComment() != null ) {
229 $this->getOutput()->addWikiMsg(
230 'editcomment',
232 $this->commentFormatter
233 ->format( $current->getComment()->text )
234 )
235 );
236 }
237 }
238
239 return;
240 }
241
243 $current = $data['current-revision-record'];
244 $target = $data['target-revision-record'];
245 $newId = $data['newid'];
246 $this->getOutput()->setPageTitleMsg( $this->msg( 'actioncomplete' ) );
247 $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
248
249 $old = Linker::revUserTools( $current );
250 $new = Linker::revUserTools( $target );
251
252 $currentUser = $current->getUser( RevisionRecord::FOR_THIS_USER, $user );
253 $targetUser = $target->getUser( RevisionRecord::FOR_THIS_USER, $user );
254 $userOptionsLookup = $this->userOptionsLookup;
255 $this->getOutput()->addHTML(
256 $this->msg( 'rollback-success' )
257 ->rawParams( $old, $new )
258 ->params( $currentUser ? $currentUser->getName() : '' )
259 ->params( $targetUser ? $targetUser->getName() : '' )
260 ->parseAsBlock()
261 );
262 // Load the mediawiki.misc-authed-curate module, so that we can fire the JavaScript
263 // postEdit hook on a successful rollback.
264 $this->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
265 // Export a success flag to the frontend, so that the mediawiki.misc-authed-curate
266 // ResourceLoader module can use this as an indicator to fire the postEdit hook.
267 $this->getOutput()->addJsConfigVars( [
268 'wgRollbackSuccess' => true,
269 // Don't show an edit confirmation with mw.notify(), the rollback success page
270 // is already a visual confirmation.
271 'wgPostEditConfirmationDisabled' => true,
272 ] );
273
274 // Watch the page for the user-chosen period of time, unless the page is already watched.
275 if ( $userOptionsLookup->getBoolOption( $user, 'watchrollback' ) &&
276 !$this->watchlistManager->isWatchedIgnoringRights( $user, $this->getTitle() )
277 ) {
278 $this->watchlistManager->addWatchIgnoringRights( $user, $this->getTitle(),
279 $userOptionsLookup->getOption( $user, 'watchrollback-expiry' ) );
280 }
281
282 $this->getOutput()->returnToMain( false, $this->getTitle() );
283
284 if ( !$request->getBool( 'hidediff', false ) &&
285 !$userOptionsLookup->getBoolOption( $this->getUser(), 'norollbackdiff' )
286 ) {
287 $contentModel = $current->getMainContentModel();
288 $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
289 $de = $contentHandler->createDifferenceEngine(
290 $this->getContext(),
291 $current->getId(),
292 $newId,
293 0,
294 true
295 );
296 $de->showDiff( '', '' );
297 }
298 }
299
304 private function enableTransactionalTimelimit() {
305 // If Rollbacks are made POST-only, use $this->useTransactionalTimeLimit()
307 if ( !$this->getRequest()->wasPosted() ) {
312 $fname = __METHOD__;
313 $trxLimits = $this->context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
314 $trxProfiler = Profiler::instance()->getTransactionProfiler();
315 $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
316 DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname
317 ) {
318 $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
319 } );
320 }
321 }
322
323 private function showRollbackConfirmationForm() {
324 $form = $this->getForm();
325 if ( $form->show() ) {
326 $this->onSuccess();
327 }
328 }
329
331 protected function getFormFields() {
332 return [
333 'intro' => [
334 'type' => 'info',
335 'raw' => true,
336 'default' => $this->msg( 'confirm-rollback-bottom' )->parse()
337 ]
338 ];
339 }
340}
341
343class_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.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:209
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:61
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.
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:37
static instance()
Definition Profiler.php:105
Interface for objects which can provide a MediaWiki context on request.
Service for page rollback actions.