MediaWiki master
RollbackAction.php
Go to the documentation of this file.
1<?php
37
44
45 private IContentHandlerFactory $contentHandlerFactory;
46 private RollbackPageFactory $rollbackPageFactory;
47 private UserOptionsLookup $userOptionsLookup;
48 private WatchlistManager $watchlistManager;
49 private CommentFormatter $commentFormatter;
50
60 public function __construct(
61 Article $article,
62 IContextSource $context,
63 IContentHandlerFactory $contentHandlerFactory,
64 RollbackPageFactory $rollbackPageFactory,
65 UserOptionsLookup $userOptionsLookup,
66 WatchlistManager $watchlistManager,
67 CommentFormatter $commentFormatter
68 ) {
69 parent::__construct( $article, $context );
70 $this->contentHandlerFactory = $contentHandlerFactory;
71 $this->rollbackPageFactory = $rollbackPageFactory;
72 $this->userOptionsLookup = $userOptionsLookup;
73 $this->watchlistManager = $watchlistManager;
74 $this->commentFormatter = $commentFormatter;
75 }
76
77 public function getName() {
78 return 'rollback';
79 }
80
81 public function getRestriction() {
82 return 'rollback';
83 }
84
85 protected function usesOOUI() {
86 return true;
87 }
88
89 protected function getDescription() {
90 return '';
91 }
92
93 public function doesWrites() {
94 return true;
95 }
96
97 public function onSuccess() {
98 return false;
99 }
100
101 public function onSubmit( $data ) {
102 return false;
103 }
104
105 protected function alterForm( HTMLForm $form ) {
106 $form->setWrapperLegendMsg( 'confirm-rollback-top' );
107 $form->setSubmitTextMsg( 'confirm-rollback-button' );
108 $form->setTokenSalt( 'rollback' );
109
110 $from = $this->getRequest()->getVal( 'from' );
111 if ( $from === null ) {
112 throw new BadRequestError( 'rollbackfailed', 'rollback-missingparam' );
113 }
114 foreach ( [ 'from', 'bot', 'hidediff', 'summary', 'token' ] as $param ) {
115 $val = $this->getRequest()->getVal( $param );
116 if ( $val !== null ) {
117 $form->addHiddenField( $param, $val );
118 }
119 }
120 }
121
127 public function show() {
128 $this->setHeaders();
129 // This will throw exceptions if there's a problem
130 $this->checkCanExecute( $this->getUser() );
131
132 if ( !$this->userOptionsLookup->getOption( $this->getUser(), 'showrollbackconfirmation' ) ||
133 $this->getRequest()->wasPosted()
134 ) {
135 $this->handleRollbackRequest();
136 } else {
137 $this->showRollbackConfirmationForm();
138 }
139 }
140
141 public function handleRollbackRequest() {
142 $this->enableTransactionalTimelimit();
143 $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
144
145 $request = $this->getRequest();
146 $user = $this->getUser();
147 $from = $request->getVal( 'from' );
148 $rev = $this->getWikiPage()->getRevisionRecord();
149 if ( $from === null ) {
150 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingparam' );
151 }
152 if ( !$rev ) {
153 throw new ErrorPageError( 'rollbackfailed', 'rollback-missingrevision' );
154 }
155
156 $revUser = $rev->getUser();
157 $userText = $revUser ? $revUser->getName() : '';
158 if ( $from !== $userText ) {
159 throw new ErrorPageError( 'rollbackfailed', 'alreadyrolled', [
160 $this->getTitle()->getPrefixedText(),
161 wfEscapeWikiText( $from ),
162 $userText
163 ] );
164 }
165
166 if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
167 throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
168 }
169
170 // The revision has the user suppressed, so the rollback has empty 'from',
171 // so the check above would succeed in that case.
172 // T307278 - Also check if the user has rights to view suppressed usernames
173 if ( !$revUser ) {
174 if ( $this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
175 $revUser = $rev->getUser( RevisionRecord::RAW );
176 } else {
177 $userFactory = MediaWikiServices::getInstance()->getUserFactory();
178 $revUser = $userFactory->newFromName( $this->context->msg( 'rev-deleted-user' )->plain() );
179 }
180 }
181
182 $rollbackResult = $this->rollbackPageFactory
183 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable use of raw avoids null here
184 ->newRollbackPage( $this->getWikiPage(), $this->getAuthority(), $revUser )
185 ->setSummary( $request->getText( 'summary' ) )
186 ->markAsBot( $request->getBool( 'bot' ) )
187 ->rollbackIfAllowed();
188 $data = $rollbackResult->getValue();
189
190 if ( $rollbackResult->hasMessage( 'actionthrottledtext' ) ) {
191 throw new ThrottledError;
192 }
193
194 if ( $rollbackResult->hasMessage( 'alreadyrolled' ) || $rollbackResult->hasMessage( 'cantrollback' ) ) {
195 $this->getOutput()->setPageTitleMsg( $this->msg( 'rollbackfailed' ) );
196 $errArray = $rollbackResult->getErrors()[0];
197 $this->getOutput()->addWikiMsgArray( $errArray['message'], $errArray['params'] );
198
199 if ( isset( $data['current-revision-record'] ) ) {
201 $current = $data['current-revision-record'];
202
203 if ( $current->getComment() != null ) {
204 $this->getOutput()->addWikiMsg(
205 'editcomment',
206 Message::rawParam(
207 $this->commentFormatter
208 ->format( $current->getComment()->text )
209 )
210 );
211 }
212 }
213
214 return;
215 }
216
217 # NOTE: Permission errors already handled by Action::checkExecute.
218 if ( $rollbackResult->hasMessage( 'readonlytext' ) ) {
219 throw new ReadOnlyError;
220 }
221
222 # XXX: Would be nice if ErrorPageError could take multiple errors, and/or a status object.
223 # Right now, we only show the first error
224 foreach ( $rollbackResult->getErrors() as $error ) {
225 throw new ErrorPageError( 'rollbackfailed', $error['message'], $error['params'] );
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}
getUser()
getRequest()
getAuthority()
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()
getWikiPage()
Get a WikiPage object.
Definition Action.php:190
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
Definition Action.php:323
getOutput()
Get the OutputPage being used for this instance.
Definition Action.php:141
setHeaders()
Set output headers for noindexing etc.
Definition Action.php:397
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:223
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:67
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.
Some internal bits split of from Skin.php.
Definition Linker.php:65
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:158
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.