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