MediaWiki master
RollbackAction.php
Go to the documentation of this file.
1<?php
38
45
46 private IContentHandlerFactory $contentHandlerFactory;
47 private RollbackPageFactory $rollbackPageFactory;
48 private UserOptionsLookup $userOptionsLookup;
49 private WatchlistManager $watchlistManager;
50 private CommentFormatter $commentFormatter;
51
61 public function __construct(
62 Article $article,
63 IContextSource $context,
64 IContentHandlerFactory $contentHandlerFactory,
65 RollbackPageFactory $rollbackPageFactory,
66 UserOptionsLookup $userOptionsLookup,
67 WatchlistManager $watchlistManager,
68 CommentFormatter $commentFormatter
69 ) {
70 parent::__construct( $article, $context );
71 $this->contentHandlerFactory = $contentHandlerFactory;
72 $this->rollbackPageFactory = $rollbackPageFactory;
73 $this->userOptionsLookup = $userOptionsLookup;
74 $this->watchlistManager = $watchlistManager;
75 $this->commentFormatter = $commentFormatter;
76 }
77
78 public function getName() {
79 return 'rollback';
80 }
81
82 public function getRestriction() {
83 return 'rollback';
84 }
85
86 protected function usesOOUI() {
87 return true;
88 }
89
90 protected function getDescription() {
91 return '';
92 }
93
94 public function doesWrites() {
95 return true;
96 }
97
98 public function onSuccess() {
99 return false;
100 }
101
102 public function onSubmit( $data ) {
103 return false;
104 }
105
106 protected function alterForm( HTMLForm $form ) {
107 $form->setWrapperLegendMsg( 'confirm-rollback-top' );
108 $form->setSubmitTextMsg( 'confirm-rollback-button' );
109 $form->setTokenSalt( 'rollback' );
110
111 $from = $this->getRequest()->getVal( 'from' );
112 if ( $from === null ) {
113 throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' );
114 }
115 foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) {
116 $val = $this->getRequest()->getVal( $param );
117 if ( $val !== null ) {
118 $form->addHiddenField( $param, $val );
119 }
120 }
121 }
122
128 public function show() {
129 $this->setHeaders();
130 // This will throw exceptions if there's a problem
131 $this->checkCanExecute( $this->getUser() );
132
133 if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) ||
134 $this->getRequest()->wasPosted()
135 ) {
136 $this->handleRollbackRequest();
137 } else {
138 $this->showRollbackConfirmationForm();
139 }
140 }
141
142 public function handleRollbackRequest() {
143 $this->enableTransactionalTimelimit();
144 $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
145
146 $request = $this->getRequest();
147 $user = $this->getUser();
148 $from = $request->getVal( 'from' );
149 $rev = $this->getWikiPage()->getRevisionRecord();
150 if ( $from === null ) {
151 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
152 }
153 if ( !$rev ) {
154 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' );
155 }
156
157 $revUser = $rev->getUser();
158 $userText = $revUser ? $revUser->getName() : '';
159 if ( $from !== $userText ) {
160 throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [
161 $this->getTitle()->getPrefixedText(),
162 wfEscapeWikiText( $from ),
163 $userText
164 ] );
165 }
166
167 if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
168 throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
169 }
170
171 // The revision has the user suppressed, so the rollback has empty 'from',
172 // so the check above would succeed in that case.
173 // T307278 - Also check if the user has rights to view suppressed usernames
174 if ( !$revUser ) {
175 if ( $this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
176 $revUser = $rev->getUser( RevisionRecord::RAW );
177 } else {
178 $userFactory = MediaWikiServices::getInstance()->getUserFactory();
179 $revUser = $userFactory->newFromName( $this->context->msg( 'rev-deleted-user' )->plain() );
180 }
181 }
182
183 $rollbackResult = $this->rollbackPageFactory
184 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here
185 ->newRollbackPage( $this->getWikiPage(), $this->getAuthority(), $revUser )
186 ->setSummary( $request->getText( 'summary' ) )
187 ->markAsBot( $request->getBool( 'bot' ) )
188 ->rollbackIfAllowed();
189 $data = $rollbackResult->getValue();
190
191 if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) {
192 throw new ThrottledError;
193 }
194
195 # NOTE: Permission errors already handled by Action::checkExecute.
196 if ( $rollbackResult->hasMessage( 'readonlytext' ) ) {
197 throw new ReadOnlyError;
198 }
199
200 if ( $rollbackResult->getMessages() ) {
201 $this->getOutput()->setPageTitleMsg( $this->msg( 'rollbackfailed' ) );
202
203 foreach ( $rollbackResult->getMessages() as $msg ) {
204 $this->getOutput()->addWikiMsg( $msg );
205 }
206
207 if (
208 ( $rollbackResult->hasMessage( 'alreadyrolled' ) || $rollbackResult->hasMessage( 'cantrollback' ) )
209 && isset( $data['current-revision-record'] )
210 ) {
212 $current = $data['current-revision-record'];
213
214 if ( $current->getComment() != null ) {
215 $this->getOutput()->addWikiMsg(
216 'editcomment',
217 Message::rawParam(
218 $this->commentFormatter
219 ->format( $current->getComment()->text )
220 )
221 );
222 }
223 }
224
225 return;
226 }
227
229 $current = $data['current-revision-record'];
230 $target = $data['target-revision-record'];
231 $newId = $data['newid'];
232 $this->getOutput()->setPageTitleMsg( $this->msg( 'actioncomplete' ) );
233 $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
234
235 $old = Linker::revUserTools( $current );
236 $new = Linker::revUserTools( $target );
237
238 $currentUser = $current->getUser( RevisionRecord::FOR_THIS_USER, $user );
239 $targetUser = $target->getUser( RevisionRecord::FOR_THIS_USER, $user );
240 $this->getOutput()->addHTML(
241 $this->msg( 'rollback-success' )
242 ->rawParams( $old, $new )
243 ->params( $currentUser ? $currentUser->getName() : '' )
244 ->params( $targetUser ? $targetUser->getName() : '' )
245 ->parseAsBlock()
246 );
247 // Load the mediawiki.misc-authed-curate module, so that we can fire the JavaScript
248 // postEdit hook on a successful rollback.
249 $this->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
250 // Export a success flag to the frontend, so that the mediawiki.misc-authed-curate
251 // ResourceLoader module can use this as an indicator to fire the postEdit hook.
252 $this->getOutput()->addJsConfigVars( [
253 'wgRollbackSuccess' => true,
254 // Don't show an edit confirmation with mw.notify(), the rollback success page
255 // is already a visual confirmation.
256 'wgPostEditConfirmationDisabled' => true,
257 ] );
258
259 if ( $this->userOptionsLookup->getBoolOption( $user, 'watchrollback' ) ) {
260 $this->watchlistManager->addWatchIgnoringRights( $user, $this->getTitle() );
261 }
262
263 $this->getOutput()->returnToMain( false, $this->getTitle() );
264
265 if ( !$request->getBool( 'hidediff', false ) &&
266 !$this->userOptionsLookup->getBoolOption( $this->getUser(), 'norollbackdiff' )
267 ) {
268 $contentModel = $current->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )
269 ->getModel();
270 $contentHandler = $this->contentHandlerFactory->getContentHandler( $contentModel );
271 $de = $contentHandler->createDifferenceEngine(
272 $this->getContext(),
273 $current->getId(),
274 $newId,
275 0,
276 true
277 );
278 $de->showDiff( '', '' );
279 }
280 }
281
286 private function enableTransactionalTimelimit() {
287 // If Rollbacks are made POST-only, use $this->useTransactionalTimeLimit()
289 if ( !$this->getRequest()->wasPosted() ) {
294 $fname = __METHOD__;
295 $trxLimits = $this->context->getConfig()->get( MainConfigNames::TrxProfilerLimits );
296 $trxProfiler = Profiler::instance()->getTransactionProfiler();
297 $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
298 DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname
299 ) {
300 $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
301 } );
302 }
303 }
304
305 private function showRollbackConfirmationForm() {
306 $form = $this->getForm();
307 if ( $form->show() ) {
308 $this->onSuccess();
309 }
310 }
311
312 protected function getFormFields() {
313 return [
314 'intro' => [
315 'type' => 'info',
316 'raw' => true,
317 'default' => $this->msg( 'confirm-rollback-bottom' )->parse()
318 ]
319 ];
320 }
321}
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.
getWikiPage()
Get a WikiPage object.
Definition Action.php:192
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
Definition Action.php:325
getContext()
Get the IContextSource in use here.
Definition Action.php:119
getOutput()
Get the OutputPage being used for this instance.
Definition Action.php:143
getUser()
Shortcut to get the User being used for this instance.
Definition Action.php:153
setHeaders()
Set output headers for noindexing etc.
Definition Action.php:405
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:225
getAuthority()
Shortcut to get the Authority executing this instance.
Definition Action.php:163
getRequest()
Get the WebRequest being used for this instance.
Definition Action.php:133
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:72
An error page that emits an HTTP 400 Bad Request status code.
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.
getForm()
Get the HTMLForm to control behavior.
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.
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:63
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:155
Page revision base class.
Value object representing a content slot associated with a page revision.
Provides access to user options.
static instance()
Definition Profiler.php:105
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.