Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 212
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Stabilization
0.00% covered (danger)
0.00%
0 / 212
0.00% covered (danger)
0.00%
0 / 7
1332
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
 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 / 43
0.00% covered (danger)
0.00%
0 / 1
132
 showForm
0.00% covered (danger)
0.00%
0 / 141
0.00% covered (danger)
0.00%
0 / 1
182
 buildSelector
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 getOptionLabel
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 disabledAttr
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3use MediaWiki\CommentStore\CommentStore;
4use MediaWiki\Html\Html;
5use MediaWiki\Linker\Linker;
6use MediaWiki\MediaWikiServices;
7use MediaWiki\SpecialPage\UnlistedSpecialPage;
8use MediaWiki\Title\Title;
9
10/** Assumes $wgFlaggedRevsProtection is off */
11class Stabilization extends UnlistedSpecialPage {
12    /** @var PageStabilityGeneralForm|null */
13    private $form = null;
14
15    public function __construct() {
16        parent::__construct( 'Stabilization', 'stablesettings' );
17    }
18
19    /**
20     * @inheritDoc
21     */
22    public function doesWrites() {
23        return true;
24    }
25
26    /**
27     * @inheritDoc
28     */
29    public function execute( $par ) {
30        $out = $this->getOutput();
31        $user = $this->getUser();
32        $request = $this->getRequest();
33
34        $confirmed = $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
35
36        # Target page
37        $title = Title::newFromText( $request->getVal( 'page', $par ) );
38        if ( !$title ) {
39            $out->showErrorPage( 'notargettitle', 'notargettext' );
40            return;
41        }
42
43        # Let anyone view, but not submit...
44        if ( $request->wasPosted() ) {
45            $pm = MediaWikiServices::getInstance()->getPermissionManager();
46            if ( !$pm->userHasRight( $user, 'stablesettings' ) ) {
47                throw new PermissionsError( 'stablesettings' );
48            }
49            if ( $pm->isBlockedFrom( $user, $title, !$confirmed ) ) {
50                // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Guaranteed via isBlockedFrom() above
51                throw new UserBlockedError( $user->getBlock( !$confirmed ) );
52            }
53            $this->checkReadOnly();
54        }
55
56        # Set page title
57        $this->setHeaders();
58
59        $this->getSkin()->setRelevantTitle( $title );
60
61        $this->form = new PageStabilityGeneralForm( $user );
62        $form = $this->form; // convenience
63
64        $form->setTitle( $title );
65        # Watch checkbox
66        $form->setWatchThis( $request->getCheck( 'wpWatchthis' ) );
67        # Get auto-review option...
68        $form->setReviewThis( $request->getCheck( 'wpReviewthis' ) );
69        # Reason
70        $form->setReasonExtra( $request->getText( 'wpReason' ) );
71        $form->setReasonSelection( $request->getVal( 'wpReasonSelection' ) );
72        # Expiry
73        $form->setExpiryCustom( $request->getText( 'mwStabilize-expiry' ) );
74        $form->setExpirySelection( $request->getVal( 'wpExpirySelection' ) );
75        # Default version
76        $form->setOverride( (int)$request->getBool( 'wpStableconfig-override' ) );
77        # Get autoreview restrictions...
78        $form->setAutoreview( $request->getVal( 'mwProtect-level-autoreview' ) );
79        $form->ready(); // params all set
80
81        $status = $form->checkTarget();
82        if ( $status === 'stabilize_page_notexists' ) {
83            $out->addWikiMsg( 'stabilization-notexists', $title->getPrefixedText() );
84            return;
85        } elseif ( $status === 'stabilize_page_unreviewable' ) {
86            $out->addWikiMsg( 'stabilization-notcontent', $title->getPrefixedText() );
87            return;
88        }
89
90        # Form POST request...
91        if ( $request->wasPosted() && $confirmed && $form->isAllowed() ) {
92            $status = $form->submit();
93            if ( $status === true ) {
94                $out->redirect( $title->getFullURL() );
95            } else {
96                $this->showForm( $this->msg( $status )->escaped() );
97            }
98        # Form GET request...
99        } else {
100            $form->preload();
101            $this->showForm();
102        }
103    }
104
105    /**
106     * @param string|null $err
107     */
108    private function showForm( $err = null ) {
109        $out = $this->getOutput();
110        $user = $this->getUser();
111
112        $form = $this->form; // convenience
113        $title = $this->form->getTitle();
114        $oldConfig = $form->getOldConfig();
115
116        $s = ''; // form HTML string
117        # Add any error messages
118        if ( $err !== '' ) {
119            $out->setSubtitle( $this->msg( 'formerror' ) );
120            $out->addHTML( "<p class='error'>{$err}</p>\n" );
121        }
122        # Add header text
123        $msg = $form->isAllowed() ? 'stabilization-text' : 'stabilization-perm';
124        $s .= $this->msg( $msg, $title->getPrefixedText() )->parseAsBlock();
125        # Traditionally, the list of reasons for stabilization is the same as
126        # for protection.  In some cases, however, it might be desirable to
127        # use a different list for stabilization.
128        $defaultReasons = $this->msg( 'stabilization-dropdown' );
129        if ( $defaultReasons->isDisabled() ) {
130            $defaultReasons = $this->msg( 'protect-dropdown' );
131        }
132        $reasonDropdown = Xml::listDropdown(
133            'wpReasonSelection',
134            $defaultReasons->inContentLanguage()->text(),
135            $this->msg( 'protect-otherreason-op' )->inContentLanguage()->escaped(),
136            $form->getReasonSelection(),
137            'mwStabilize-reason',
138            4
139        );
140        $scExpiryOptions = $this->msg( 'protect-expiry-options' )->inContentLanguage()->text();
141        $showProtectOptions = ( $scExpiryOptions !== '-' && $form->isAllowed() );
142        $dropdownOptions = []; // array of <label,value>
143        # Add the current expiry as a dropdown option
144        if ( $oldConfig['expiry'] && $oldConfig['expiry'] != 'infinity' ) {
145            $timestamp = $this->getLanguage()->timeanddate( $oldConfig['expiry'] );
146            $d = $this->getLanguage()->date( $oldConfig['expiry'] );
147            $t = $this->getLanguage()->time( $oldConfig['expiry'] );
148            $dropdownOptions[] = [
149                $this->msg( 'protect-existing-expiry', $timestamp, $d, $t )->text(), 'existing' ];
150        }
151        # Add "other time" expiry dropdown option
152        $dropdownOptions[] = [ $this->msg( 'protect-othertime-op' )->text(), 'othertime' ];
153        # Add custom expiry dropdown options (from MediaWiki message)
154        $value = null;
155        foreach ( explode( ',', $scExpiryOptions ) as $option ) {
156            $pair = explode( ':', $option, 2 );
157            $show = $pair[0];
158            $value = $pair[1] ?? $show;
159            $dropdownOptions[] = [ $show, $value ];
160        }
161
162        # Actually build the options HTML...
163        $expiryFormOptions = '';
164        foreach ( $dropdownOptions as $option ) {
165            $expiryFormOptions .= Xml::option(
166                $option[0],
167                $option[1],
168                $form->getExpirySelection() === $value
169            ) . "\n";
170        }
171
172        # Build up the form...
173        $s .= Html::openElement( 'form', [ 'name' => 'stabilization',
174            'action' => $this->getPageTitle()->getLocalURL(), 'method' => 'post' ] );
175        # Add stable version override and selection options
176        $s .=
177            Xml::fieldset( $this->msg( 'stabilization-def' )->text() ) . "\n" .
178            Xml::radioLabel( $this->msg( 'stabilization-def1' )->text(), 'wpStableconfig-override', '1',
179                'default-stable', $form->getOverride() == 1, $this->disabledAttr() ) .
180                '<br />' . "\n" .
181            Xml::radioLabel( $this->msg( 'stabilization-def2' )->text(), 'wpStableconfig-override', '0',
182                'default-current', $form->getOverride() == 0, $this->disabledAttr() ) . "\n" .
183            Html::closeElement( 'fieldset' );
184        # Add autoreview restriction select
185        $s .= Xml::fieldset( $this->msg( 'stabilization-restrict' )->text() ) .
186            $this->buildSelector( $form->getAutoreview() ) .
187            Html::closeElement( 'fieldset' ) .
188
189            Xml::fieldset( $this->msg( 'stabilization-leg' )->text() ) .
190            Html::openElement( 'table' );
191        # Add expiry dropdown to form...
192        if ( $showProtectOptions && $form->isAllowed() ) {
193            $s .= "
194                <tr>
195                    <td class='mw-label'>" .
196                        Xml::label( $this->msg( 'stabilization-expiry' )->text(),
197                            'mwStabilizeExpirySelection' ) .
198                    "</td>
199                    <td class='mw-input'>" .
200                        Html::rawElement( 'select',
201                            [
202                                'id'        => 'mwStabilizeExpirySelection',
203                                'name'      => 'wpExpirySelection',
204                                'onchange'  => 'onFRChangeExpiryDropdown()',
205                            ] + $this->disabledAttr(),
206                            $expiryFormOptions ) .
207                    "</td>
208                </tr>";
209        }
210        # Add custom expiry field to form...
211        $attribs = [ 'id' => "mwStabilizeExpiryOther",
212            'onkeyup' => 'onFRChangeExpiryField()' ] + $this->disabledAttr();
213        $s .= "
214            <tr>
215                <td class='mw-label'>" .
216                    Xml::label( $this->msg( 'stabilization-othertime' )->text(),
217                        'mwStabilizeExpiryOther' ) .
218                '</td>
219                <td class="mw-input">' .
220                    Xml::input( "mwStabilize-expiry", 50, $form->getExpiryCustom(), $attribs ) .
221                '</td>
222            </tr>';
223        # Add comment input and submit button
224        if ( $form->isAllowed() ) {
225            $watchAttribs = [ 'accesskey' => $this->msg( 'accesskey-watch' )->text(),
226                'id' => 'wpWatchthis' ];
227            $services = MediaWikiServices::getInstance();
228            $userOptionsLookup = $services->getUserOptionsLookup();
229            $watchlistManager = $services->getWatchlistManager();
230            $watchChecked = ( $userOptionsLookup->getOption( $user, 'watchdefault' )
231                || $watchlistManager->isWatched( $user, $title ) );
232
233            $s .= ' <tr>
234                    <td class="mw-label">' .
235                        Xml::label( $this->msg( 'stabilization-comment' )->text(),
236                            'wpReasonSelection' ) .
237                    '</td>
238                    <td class="mw-input">' .
239                        $reasonDropdown .
240                    '</td>
241                </tr>
242                <tr>
243                    <td class="mw-label">' .
244                        Xml::label( $this->msg( 'stabilization-otherreason' )->text(), 'wpReason' ) .
245                    '</td>
246                    <td class="mw-input">' .
247                        Xml::input( 'wpReason', 70, $form->getReasonExtra(), [
248                            'id' => 'wpReason',
249                            'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT
250                        ] ) .
251                    '</td>
252                </tr>
253                <tr>
254                    <td></td>
255                    <td class="mw-input">' .
256                        Xml::check( 'wpReviewthis', $form->getReviewThis(),
257                            [ 'id' => 'wpReviewthis' ] ) .
258                        Xml::label( $this->msg( 'stabilization-review' )->text(), 'wpReviewthis' ) .
259                        '&#160;&#160;&#160;&#160;&#160;' .
260                        Xml::check( 'wpWatchthis', $watchChecked, $watchAttribs ) .
261                        "&#160;" .
262                        Xml::label( $this->msg( 'watchthis' )->text(), 'wpWatchthis',
263                            [ 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ] ) .
264                    '</td>
265                </tr>
266                <tr>
267                    <td></td>
268                    <td class="mw-submit">' .
269                        Xml::submitButton( $this->msg( 'stabilization-submit' )->text() ) .
270                    '</td>
271                </tr>' . Html::closeElement( 'table' ) .
272                Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
273                Html::hidden( 'page', $title->getPrefixedText() ) .
274                Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
275        } else {
276            $s .= Html::closeElement( 'table' );
277        }
278        $s .= Html::closeElement( 'fieldset' ) . Html::closeElement( 'form' );
279
280        $out->addHTML( $s );
281
282        $log = new LogPage( 'stable' );
283        $out->addHTML( Html::element( 'h2', [],
284            $log->getName()->setContext( $this->getContext() )->text() ) );
285        LogEventsList::showLogExtract( $out, 'stable',
286            $title->getPrefixedText(), '', [ 'lim' => 25 ] );
287
288        # Add some javascript for expiry dropdowns
289        $out->addScript(
290            "<script type=\"text/javascript\">
291                function onFRChangeExpiryDropdown() {
292                    document.getElementById('mwStabilizeExpiryOther').value = '';
293                }
294                function onFRChangeExpiryField() {
295                    document.getElementById('mwStabilizeExpirySelection').value = 'othertime';
296                }
297            </script>"
298        );
299    }
300
301    /**
302     * @param string $selected
303     * @return string HTML
304     */
305    private function buildSelector( $selected ) {
306        $allowedLevels = [];
307        // Add a "none" level
308        $levels = [ '', ...FlaggedRevs::getRestrictionLevels() ];
309        foreach ( $levels as $key ) {
310            # Don't let them choose levels they can't set,
311            # but *show* them all when the form is disabled.
312            if ( $this->form->isAllowed()
313                && !FlaggedRevs::userCanSetAutoreviewLevel( $this->getUser(), $key )
314            ) {
315                continue;
316            }
317            $allowedLevels[] = $key;
318        }
319        $id = 'mwProtect-level-autoreview';
320        $attribs = [
321            'id' => $id,
322            'name' => $id,
323            'size' => count( $allowedLevels ),
324        ] + $this->disabledAttr();
325
326        $out = '';
327        foreach ( $allowedLevels as $key ) {
328            $out .= Xml::option( $this->getOptionLabel( $key ), $key, $key == $selected );
329        }
330        return Html::rawElement( 'select', $attribs, $out );
331    }
332
333    /**
334     * Prepare the label for a protection selector option
335     *
336     * @param string $permission Permission required
337     * @return string
338     */
339    private function getOptionLabel( $permission ) {
340        if ( !$permission ) {
341            return $this->msg( 'stabilization-restrict-none' )->text();
342        }
343
344        $msg = $this->msg( "protect-level-$permission" );
345        if ( $msg->isDisabled() ) {
346            $msg = $this->msg( 'protect-fallback', $permission );
347        }
348        return $msg->text();
349    }
350
351    /**
352     * If the this form is disabled, then return the "disabled" attr array
353     * @return string[]
354     */
355    private function disabledAttr() {
356        return $this->form->isAllowed()
357            ? []
358            : [ 'disabled' => 'disabled' ];
359    }
360}