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