Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 212 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
Stabilization | |
0.00% |
0 / 212 |
|
0.00% |
0 / 7 |
1332 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 43 |
|
0.00% |
0 / 1 |
132 | |||
showForm | |
0.00% |
0 / 141 |
|
0.00% |
0 / 1 |
182 | |||
buildSelector | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
30 | |||
getOptionLabel | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
disabledAttr | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | use MediaWiki\CommentStore\CommentStore; |
4 | use MediaWiki\Html\Html; |
5 | use MediaWiki\Linker\Linker; |
6 | use MediaWiki\MediaWikiServices; |
7 | use MediaWiki\SpecialPage\UnlistedSpecialPage; |
8 | use MediaWiki\Title\Title; |
9 | |
10 | /** Assumes $wgFlaggedRevsProtection is off */ |
11 | class 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 | '     ' . |
260 | Xml::check( 'wpWatchthis', $watchChecked, $watchAttribs ) . |
261 | " " . |
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 | } |