Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 192 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
RevisionReview | |
0.00% |
0 / 192 |
|
0.00% |
0 / 7 |
3422 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 90 |
|
0.00% |
0 / 1 |
812 | |||
approvalSuccessHTML | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
deapprovalSuccessHTML | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getSpecialLinks | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
doReview | |
0.00% |
0 / 71 |
|
0.00% |
0 / 1 |
552 |
1 | <?php |
2 | |
3 | use MediaWiki\MediaWikiServices; |
4 | use MediaWiki\Permissions\PermissionManager; |
5 | use MediaWiki\Session\CsrfTokenSet; |
6 | use MediaWiki\SpecialPage\SpecialPage; |
7 | use MediaWiki\SpecialPage\UnlistedSpecialPage; |
8 | use MediaWiki\Title\Title; |
9 | |
10 | class RevisionReview extends UnlistedSpecialPage { |
11 | /** @var RevisionReviewForm|null */ |
12 | private $form; |
13 | /** @var Title|null */ |
14 | private $title; |
15 | |
16 | /** @var PermissionManager */ |
17 | private $permissionManager; |
18 | |
19 | public function __construct() { |
20 | parent::__construct( 'RevisionReview', 'review' ); |
21 | |
22 | // TODO use dependency injection |
23 | $this->permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); |
24 | } |
25 | |
26 | /** |
27 | * @inheritDoc |
28 | */ |
29 | public function doesWrites() { |
30 | return true; |
31 | } |
32 | |
33 | /** |
34 | * @inheritDoc |
35 | */ |
36 | public function execute( $par ) { |
37 | $out = $this->getOutput(); |
38 | $user = $this->getUser(); |
39 | $request = $this->getRequest(); |
40 | |
41 | # Our target page |
42 | $this->title = Title::newFromText( $request->getVal( 'target' ) ); |
43 | if ( !$this->title ) { |
44 | $out->showErrorPage( 'notargettitle', 'notargettext' ); |
45 | return; |
46 | } |
47 | |
48 | if ( !$this->permissionManager->userHasRight( $user, 'review' ) ) { |
49 | throw new PermissionsError( 'review' ); |
50 | } |
51 | |
52 | $confirmed = $user->matchEditToken( $request->getVal( 'wpEditToken' ) ); |
53 | if ( $this->permissionManager->isBlockedFrom( $user, $this->title, !$confirmed ) ) { |
54 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Guaranteed via isBlockedFrom() above |
55 | throw new UserBlockedError( $user->getBlock( !$confirmed ) ); |
56 | } |
57 | |
58 | $this->checkReadOnly(); |
59 | $this->setHeaders(); |
60 | |
61 | # Basic page permission checks... |
62 | $permErrors = $this->permissionManager->getPermissionErrors( |
63 | 'review', |
64 | $user, |
65 | $this->title, |
66 | PermissionManager::RIGOR_QUICK |
67 | ); |
68 | if ( $permErrors ) { |
69 | $out->showPermissionsErrorPage( $permErrors, 'review' ); |
70 | return; |
71 | } |
72 | |
73 | $form = new RevisionReviewForm( $user ); |
74 | $this->form = $form; |
75 | |
76 | $form->setTitle( $this->title ); |
77 | # Param for sites with binary flagging |
78 | if ( $request->getCheck( 'wpApprove' ) ) { |
79 | $form->setAction( RevisionReviewForm::ACTION_APPROVE ); |
80 | } elseif ( $request->getCheck( 'wpUnapprove' ) ) { |
81 | $form->setAction( RevisionReviewForm::ACTION_UNAPPROVE ); |
82 | } elseif ( $request->getCheck( 'wpReject' ) ) { |
83 | $form->setAction( RevisionReviewForm::ACTION_REJECT ); |
84 | } |
85 | # Rev ID |
86 | $form->setOldId( $request->getInt( 'oldid' ) ); |
87 | $form->setRefId( $request->getInt( 'refid' ) ); |
88 | # Special parameter mapping |
89 | $form->setTemplateParams( $request->getVal( 'templateParams' ) ); |
90 | # Special token to discourage fiddling... |
91 | $form->setValidatedParams( $request->getVal( 'validatedParams' ) ); |
92 | # Conflict handling |
93 | $form->setLastChangeTime( $request->getVal( 'changetime' ) ); |
94 | # Session key |
95 | $form->setSessionKey( $request->getSessionData( 'wsFlaggedRevsKey' ) ); |
96 | # Tag values |
97 | # This can be NULL if we uncheck a checkbox |
98 | $form->setTag( $request->getInt( 'wp' . FlaggedRevs::getTagName() ) ); |
99 | # Log comment |
100 | $form->setComment( $request->getText( 'wpReason' ) ); |
101 | $form->ready(); |
102 | |
103 | if ( !$request->wasPosted() ) { |
104 | // No form to view (GET) |
105 | $out->returnToMain( false, $this->title ); |
106 | return; |
107 | } |
108 | // Review the edit if requested (POST)... |
109 | |
110 | // Check the edit token... |
111 | if ( !$confirmed ) { |
112 | $out->addWikiMsg( 'sessionfailure' ); |
113 | $out->returnToMain( false, $this->title ); |
114 | return; |
115 | } |
116 | |
117 | // Use confirmation screen for reject... |
118 | if ( $form->getAction() == RevisionReviewForm::ACTION_REJECT && !$request->getBool( 'wpRejectConfirm' ) ) { |
119 | $rejectForm = new RejectConfirmationFormUI( $form ); |
120 | [ $html, $status ] = $rejectForm->getHtml(); |
121 | if ( $status === true ) { |
122 | // Success... |
123 | $out->addHTML( $html ); |
124 | } else { |
125 | // Failure... |
126 | if ( $status === 'review_page_unreviewable' ) { |
127 | $out->addWikiMsg( 'revreview-main' ); |
128 | return; |
129 | } elseif ( $status === 'review_page_notexists' ) { |
130 | $out->showErrorPage( 'internalerror', 'nopagetext' ); |
131 | return; |
132 | } elseif ( $status === 'review_bad_oldid' ) { |
133 | $out->showErrorPage( 'internalerror', 'revreview-revnotfound' ); |
134 | } else { |
135 | $out->showErrorPage( 'internalerror', $status ); |
136 | } |
137 | $out->returnToMain( false, $this->title ); |
138 | } |
139 | return; |
140 | } |
141 | |
142 | // Otherwise submit... |
143 | $status = $form->submit(); |
144 | if ( $status === true ) { |
145 | // Success... |
146 | $out->setPageTitleMsg( $this->msg( 'actioncomplete' ) ); |
147 | if ( $form->getAction() === RevisionReviewForm::ACTION_APPROVE ) { |
148 | $out->addHTML( $this->approvalSuccessHTML() ); |
149 | } elseif ( $form->getAction() === RevisionReviewForm::ACTION_UNAPPROVE ) { |
150 | $out->addHTML( $this->deapprovalSuccessHTML() ); |
151 | } elseif ( $form->getAction() === RevisionReviewForm::ACTION_REJECT ) { |
152 | $query = $this->title->isRedirect() ? [ 'redirect' => 'no' ] : []; |
153 | $out->redirect( $this->title->getFullURL( $query ) ); |
154 | } |
155 | } else { |
156 | // Failure... |
157 | if ( $status === 'review_page_unreviewable' ) { |
158 | $out->addWikiMsg( 'revreview-main' ); |
159 | return; |
160 | } elseif ( $status === 'review_page_notexists' ) { |
161 | $out->showErrorPage( 'internalerror', 'nopagetext' ); |
162 | return; |
163 | } elseif ( $status === 'review_denied' || $status === 'review_bad_key' ) { |
164 | throw new PermissionsError( 'badaccess-group0' ); |
165 | } elseif ( $status === 'review_bad_oldid' ) { |
166 | $out->showErrorPage( 'internalerror', 'revreview-revnotfound' ); |
167 | } elseif ( $status === 'review_not_flagged' ) { |
168 | $out->redirect( $this->title->getFullURL() ); // already unflagged |
169 | } elseif ( $status === 'review_too_low' ) { |
170 | $out->addWikiMsg( 'revreview-toolow' ); |
171 | } else { |
172 | $out->showErrorPage( 'internalerror', $status ); |
173 | } |
174 | $out->returnToMain( false, $this->title ); |
175 | } |
176 | } |
177 | |
178 | /** |
179 | * @return string HTML |
180 | */ |
181 | private function approvalSuccessHTML() { |
182 | $title = $this->form->getTitle(); |
183 | # Show success message |
184 | $s = "<div class='plainlinks'>"; |
185 | $s .= $this->msg( 'revreview-successful', |
186 | $title->getPrefixedText(), $title->getPrefixedURL() )->parseAsBlock(); |
187 | $s .= $this->msg( 'revreview-stable1', |
188 | $title->getPrefixedURL(), $this->form->getOldId() )->parseAsBlock(); |
189 | $s .= "</div>"; |
190 | # Handy links to special pages |
191 | if ( $this->permissionManager->userHasRight( $this->getUser(), 'unreviewedpages' ) ) { |
192 | $s .= $this->getSpecialLinks(); |
193 | } |
194 | return $s; |
195 | } |
196 | |
197 | /** |
198 | * @return string HTML |
199 | */ |
200 | private function deapprovalSuccessHTML() { |
201 | $title = $this->form->getTitle(); |
202 | # Show success message |
203 | $s = "<div class='plainlinks'>"; |
204 | $s .= $this->msg( 'revreview-successful2', |
205 | $title->getPrefixedText(), $title->getPrefixedURL() )->parseAsBlock(); |
206 | $s .= $this->msg( 'revreview-stable2', |
207 | $title->getPrefixedURL(), $this->form->getOldId() )->parseAsBlock(); |
208 | $s .= "</div>"; |
209 | # Handy links to special pages |
210 | if ( $this->permissionManager->userHasRight( $this->getUser(), 'unreviewedpages' ) ) { |
211 | $s .= $this->getSpecialLinks(); |
212 | } |
213 | return $s; |
214 | } |
215 | |
216 | /** |
217 | * @return string HTML |
218 | */ |
219 | private function getSpecialLinks() { |
220 | $linkRenderer = $this->getLinkRenderer(); |
221 | $s = '<p>' . $this->msg( 'returnto' )->rawParams( |
222 | $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'UnreviewedPages' ) ) |
223 | )->escaped() . '</p>'; |
224 | $s .= '<p>' . $this->msg( 'returnto' )->rawParams( |
225 | $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'PendingChanges' ) ) |
226 | )->escaped() . '</p>'; |
227 | return $s; |
228 | } |
229 | |
230 | /** |
231 | * @param array $argsMap |
232 | * @return array |
233 | */ |
234 | public static function doReview( $argsMap ) { |
235 | $context = RequestContext::getMain(); |
236 | $user = $context->getUser(); |
237 | $out = $context->getOutput(); |
238 | $request = $context->getRequest(); |
239 | if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) { |
240 | return [ 'error-html' => wfMessage( 'revreview-failed' )->parse() . |
241 | wfMessage( 'revreview-submission-invalid' )->parse() ]; |
242 | } |
243 | // Make review interface object |
244 | $form = new RevisionReviewForm( $user ); |
245 | $title = null; // target page |
246 | $editToken = ''; // edit token |
247 | |
248 | foreach ( $argsMap as $par => $val ) { |
249 | switch ( $par ) { |
250 | case "target": |
251 | $title = Title::newFromURL( $val ); |
252 | break; |
253 | case "oldid": |
254 | $form->setOldId( $val ); |
255 | break; |
256 | case "refid": |
257 | $form->setRefId( $val ); |
258 | break; |
259 | case "validatedParams": |
260 | $form->setValidatedParams( $val ); |
261 | break; |
262 | case "templateParams": |
263 | $form->setTemplateParams( $val ); |
264 | break; |
265 | case "wpApprove": |
266 | if ( $val ) { |
267 | $form->setAction( RevisionReviewForm::ACTION_APPROVE ); |
268 | } |
269 | break; |
270 | case "wpUnapprove": |
271 | if ( $val ) { |
272 | $form->setAction( RevisionReviewForm::ACTION_UNAPPROVE ); |
273 | } |
274 | break; |
275 | case "wpReject": |
276 | if ( $val ) { |
277 | $form->setAction( RevisionReviewForm::ACTION_REJECT ); |
278 | } |
279 | break; |
280 | case "wpReason": |
281 | $form->setComment( $val ?? '' ); |
282 | break; |
283 | case "changetime": |
284 | $form->setLastChangeTime( $val ); |
285 | break; |
286 | case "wpEditToken": |
287 | $editToken = $val; |
288 | break; |
289 | case 'wp' . FlaggedRevs::getTagName(): |
290 | $form->setTag( $val ); |
291 | break; |
292 | } |
293 | } |
294 | |
295 | # Valid target title? |
296 | if ( !$title ) { |
297 | return [ 'error-html' => wfMessage( 'notargettext' )->parse() ]; |
298 | } |
299 | |
300 | $form->setTitle( $title ); |
301 | $form->setSessionKey( $request->getSessionData( 'wsFlaggedRevsKey' ) ); |
302 | |
303 | $form->ready(); // all params loaded |
304 | # Check session via user token |
305 | $userToken = new CsrfTokenSet( $request ); |
306 | if ( !$userToken->matchToken( $editToken ) ) { |
307 | return [ 'error-html' => wfMessage( 'sessionfailure' )->parse() ]; |
308 | } |
309 | # Basic permission checks... |
310 | $permErrors = MediaWikiServices::getInstance()->getPermissionManager() |
311 | ->getPermissionErrors( 'review', $user, $title, PermissionManager::RIGOR_QUICK ); |
312 | if ( $permErrors ) { |
313 | return [ 'error-html' => $out->parseAsInterface( |
314 | $out->formatPermissionsErrorMessage( $permErrors, 'review' ) |
315 | ) ]; |
316 | } |
317 | # Try submission... |
318 | $status = $form->submit(); |
319 | # Failure... |
320 | if ( $status !== true ) { |
321 | return [ 'error-html' => wfMessage( 'revreview-failed' )->parseAsBlock() . |
322 | '<p>' . wfMessage( $status )->escaped() . '</p>' ]; |
323 | } elseif ( !$form->getAction() ) { |
324 | return [ 'error-html' => wfMessage( 'revreview-failed' )->parse() ]; |
325 | } |
326 | |
327 | # Sent new lastChangeTime TS to client for later submissions... |
328 | return [ 'change-time' => $form->getNewLastChangeTime() ]; |
329 | } |
330 | } |