MediaWiki master
RollbackAction.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Actions;
11
32
39
40 public function __construct(
41 Article $article,
42 IContextSource $context,
43 private readonly IContentHandlerFactory $contentHandlerFactory,
44 private readonly RollbackPageFactory $rollbackPageFactory,
45 private readonly UserFactory $userFactory,
46 private readonly UserOptionsLookup $userOptionsLookup,
47 private readonly WatchlistManager $watchlistManager,
48 private readonly CommentFormatter $commentFormatter,
49 ) {
50 parent::__construct( $article, $context );
51 }
52
54 public function getName() {
55 return 'rollback';
56 }
57
59 public function getRestriction() {
60 return 'rollback';
61 }
62
64 protected function usesOOUI() {
65 return true;
66 }
67
69 protected function getDescription() {
70 return '';
71 }
72
74 public function doesWrites() {
75 return true;
76 }
77
79 public function onSuccess() {
80 return false;
81 }
82
84 public function onSubmit( $data ) {
85 return false;
86 }
87
88 protected function alterForm( HTMLForm $form ) {
89 $form->setWrapperLegendMsg( 'confirm-rollback-top' );
90 $form->setSubmitTextMsg( 'confirm-rollback-button' );
91 $form->setTokenSalt( 'rollback' );
92
93 $from = $this->getRequest()->getVal( 'from' );
94 if ( $from === null ) {
95 throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' );
96 }
97 foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) {
98 $val = $this->getRequest()->getVal( $param );
99 if ( $val !== null ) {
100 $form->addHiddenField( $param, $val );
101 }
102 }
103 }
104
110 public function show() {
111 $this->setHeaders();
112 // This will throw exceptions if there's a problem
113 $this->checkCanExecute( $this->getUser() );
114
115 if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) ||
116 $this->getRequest()->wasPosted()
117 ) {
118 $this->handleRollbackRequest();
119 } else {
120 $this->showRollbackConfirmationForm();
121 }
122 }
123
124 public function handleRollbackRequest() {
125 $this->enableTransactionalTimelimit();
126 $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
127
128 $request = $this->getRequest();
129 $user = $this->getUser();
130 $from = $request->getVal( 'from' );
131 $rev = $this->getWikiPage()->getRevisionRecord();
132 if ( $from === null ) {
133 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
134 }
135 if ( !$rev ) {
136 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' );
137 }
138
139 $revUser = $rev->getUser();
140 $userText = $revUser ? $revUser->getName() : '';
141 if ( $from !== $userText ) {
142 throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [
143 $this->getTitle()->getPrefixedText(),
144 wfEscapeWikiText( $from ),
145 $userText
146 ] );
147 }
148
149 if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
150 throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
151 }
152
153 // The revision has the user suppressed, so the rollback has empty 'from',
154 // so the check above would succeed in that case.
155 // T307278 - Also check if the user has rights to view suppressed usernames
156 if ( !$revUser ) {
157 if ( $this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
158 $revUser = $rev->getUser( RevisionRecord::RAW );
159 } else {
160 $revUser = $this->userFactory->newFromName( $this->context->msg( 'rev-deleted-user' )->plain() );
161 }
162 }
163
164 $rollbackResult = $this->rollbackPageFactory
165 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here
166 ->newRollbackPage( $this->getWikiPage(), $this->getAuthority(), $revUser )
167 ->setSummary( $request->getText( 'summary' ) )
168 ->markAsBot( $request->getBool( 'bot' ) )
169 ->rollbackIfAllowed();
170 $data = $rollbackResult->getValue();
171
172 if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) {
173 throw new ThrottledError;
174 }
175
176 # NOTE: Permission errors already handled by Action::checkExecute.
177 if ( $rollbackResult->hasMessage( 'readonlytext' ) ) {
178 throw new ReadOnlyError;
179 }
180
181 if ( $rollbackResult->getMessages() ) {
182 $this->getOutput()->setPageTitleMsg( $this->msg( 'rollbackfailed' ) );
183
184 foreach ( $rollbackResult->getMessages() as $msg ) {
185 $this->getOutput()->addWikiMsg( $msg );
186 }
187
188 if (
189 ( $rollbackResult->hasMessage( 'alreadyrolled' ) ||
190 $rollbackResult->hasMessage( 'cantrollback' ) ||
191 $rollbackResult->hasMessage( 'rollback-nochange' ) )
192 && isset( $data['current-revision-record'] )
193 ) {
195 $current = $data['current-revision-record'];
196 $comment = $current->getComment()?->text;
197
198 if ( $comment !== null && $comment !== '' ) {
199 $this->getOutput()->addWikiMsg(
200 'editcomment',
202 $this->commentFormatter
203 ->format( $comment )
204 )
205 );
206 }
207 }
208
209 return;
210 }
211
213 $current = $data['current-revision-record'];
214 $target = $data['target-revision-record'];
215 $newId = $data['newid'];
216 $this->getOutput()->setPageTitleMsg( $this->msg( 'actioncomplete' ) );
217 $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
218
219 $old = Linker::revUserTools( $current );
220 $new = Linker::revUserTools( $target );
221
222 $currentUser = $current->getUser( RevisionRecord::FOR_THIS_USER, $user );
223 $targetUser = $target->getUser( RevisionRecord::FOR_THIS_USER, $user );
224 $userOptionsLookup = $this->userOptionsLookup;
225 $this->getOutput()->addHTML(
226 $this->msg( 'rollback-success' )
227 ->rawParams( $old, $new )
228 ->params( $currentUser ? $currentUser->getName() : '' )
229 ->params( $targetUser ? $targetUser->getName() : '' )
230 ->parseAsBlock()
231 );
232 // Load the mediawiki.misc-authed-curate module, so that we can fire the JavaScript
233 // postEdit hook on a successful rollback.
234 $this->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
235 // Export a success flag to the frontend, so that the mediawiki.misc-authed-curate
236 // ResourceLoader module can use this as an indicator to fire the postEdit hook.
237 $this->getOutput()->addJsConfigVars( [
238 'wgRollbackSuccess' => true,
239 // Don't show an edit confirmation with mw.notify(), the rollback success page
240 // is already a visual confirmation.
241 'wgPostEditConfirmationDisabled' => true,
242 ] );
243
244 // Watch the page for the user-chosen period of time, unless the page is already watched.
245 if ( $userOptionsLookup->getBoolOption( $user, 'watchrollback' ) &&
246 !$this->watchlistManager->isWatchedIgnoringRights( $user, $this->getTitle() )
247 ) {
248 $this->watchlistManager->addWatchIgnoringRights( $user, $this->getTitle(),
249 $userOptionsLookup->getOption( $user, 'watchrollback-expiry' ) );
250 }
251
252 $this->getOutput()->returnToMain( false, $this->getTitle() );
253
254 if ( !$request->getBool( 'hidediff', false ) &&
255 !$userOptionsLookup->getBoolOption( $this->getUser(), 'norollbackdiff' )
256 ) {
257 $contentModel = $current->getMainContentModel();
258 $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
259 $de = $contentHandler->createDifferenceEngine(
260 $this->getContext(),
261 $current->getId(),
262 $newId,
263 0,
264 true
265 );
266 $de->showDiff( '', '' );
267 }
268 }
269
274 private function enableTransactionalTimelimit() {
275 // If Rollbacks are made POST-only, use $this->useTransactionalTimeLimit()
277 if ( !$this->getRequest()->wasPosted() ) {
282 $fname = __METHOD__;
283 $trxLimits = $this->context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
284 $trxProfiler = Profiler::instance()->getTransactionProfiler();
285 $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
286 DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname
287 ) {
288 $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
289 } );
290 }
291 }
292
293 private function showRollbackConfirmationForm() {
294 $form = $this->getForm();
295 if ( $form->show() ) {
296 $this->onSuccess();
297 }
298 }
299
301 protected function getFormFields() {
302 return [
303 'intro' => [
304 'type' => 'info',
305 'raw' => true,
306 'default' => $this->msg( 'confirm-rollback-bottom' )->parse()
307 ]
308 ];
309 }
310}
311
313class_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:102
getWikiPage()
Get a WikiPage object.
Definition Action.php:171
setHeaders()
Set output headers for noindexing etc.
Definition Action.php:380
getUser()
Shortcut to get the User being used for this instance.
Definition Action.php:132
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:205
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
Definition Action.php:300
getTitle()
Shortcut to get the Title object from the page.
Definition Action.php:191
getRequest()
Get the WebRequest being used for this instance.
Definition Action.php:112
getOutput()
Get the OutputPage being used for this instance.
Definition Action.php:122
getAuthority()
Shortcut to get the Authority executing this instance.
Definition Action.php:142
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, private readonly IContentHandlerFactory $contentHandlerFactory, private readonly RollbackPageFactory $rollbackPageFactory, private readonly UserFactory $userFactory, private readonly UserOptionsLookup $userOptionsLookup, private readonly WatchlistManager $watchlistManager, private readonly 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:208
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()
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:66
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:26
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.
Create User objects.
Interface for objects which can provide a MediaWiki context on request.
Service for page rollback actions.