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