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\MediaWikiServices;
4use MediaWiki\Permissions\PermissionManager;
5use MediaWiki\Session\CsrfTokenSet;
6use MediaWiki\SpecialPage\SpecialPage;
7use MediaWiki\SpecialPage\UnlistedSpecialPage;
8use MediaWiki\Title\Title;
9
10class 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}