Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
81.48% |
110 / 135 |
|
42.31% |
11 / 26 |
CRAP | |
0.00% |
0 / 1 |
PageStabilityForm | |
81.48% |
110 / 135 |
|
42.31% |
11 / 26 |
86.41 | |
0.00% |
0 / 1 |
getTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setTitle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setWatchThis | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getReasonExtra | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setReasonExtra | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getReasonSelection | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setReasonSelection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExpiryCustom | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setExpiryCustom | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExpirySelection | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setExpirySelection | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAutoreview | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setAutoreview | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExpiry | |
84.62% |
11 / 13 |
|
0.00% |
0 / 1 |
8.23 | |||
getReason | |
42.86% |
3 / 7 |
|
0.00% |
0 / 1 |
4.68 | |||
doCheckTargetGiven | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
doCheckTarget | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
doCheckParameters | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
reallyDoCheckParameters | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isAllowed | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
doPreloadParameters | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
reallyDoPreloadParameters | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
doSubmit | |
97.56% |
40 / 41 |
|
0.00% |
0 / 1 |
12 | |||
updateLogsAndHistory | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
4 | |||
getOldConfig | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
5.02 | |||
getNewConfig | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
updateWatchlist | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
3.58 |
1 | <?php |
2 | |
3 | use MediaWiki\MediaWikiServices; |
4 | use MediaWiki\Revision\RevisionRecord; |
5 | use MediaWiki\Title\Title; |
6 | use Wikimedia\Rdbms\IDBAccessObject; |
7 | use Wikimedia\Timestamp\ConvertibleTimestamp; |
8 | |
9 | /** |
10 | * Class containing stability settings form business logic |
11 | */ |
12 | abstract class PageStabilityForm extends FRGenericSubmitForm { |
13 | |
14 | /** @var Title|false Target page obj */ |
15 | protected $title = false; |
16 | |
17 | /** @var bool|null Watch checkbox */ |
18 | protected $watchThis = null; |
19 | |
20 | /** @var bool|null Auto-review option */ |
21 | protected $reviewThis = null; |
22 | |
23 | /** @var string Custom/extra reason */ |
24 | protected $reasonExtra = ''; |
25 | |
26 | /** @var string Reason dropdown key */ |
27 | protected $reasonSelection = ''; |
28 | |
29 | /** @var string Custom expiry */ |
30 | protected $expiryCustom = ''; |
31 | |
32 | /** @var string Expiry dropdown key */ |
33 | protected $expirySelection = ''; |
34 | |
35 | /** @var int Default version */ |
36 | protected $override = -1; |
37 | |
38 | /** @var string Autoreview restrictions */ |
39 | protected $autoreview = ''; |
40 | |
41 | /** @var array Old page config */ |
42 | protected $oldConfig = []; |
43 | |
44 | /** |
45 | * @return Title|false |
46 | */ |
47 | public function getTitle() { |
48 | return $this->title; |
49 | } |
50 | |
51 | /** |
52 | * @param Title $value |
53 | */ |
54 | public function setTitle( Title $value ) { |
55 | $this->trySet( $this->title, $value ); |
56 | } |
57 | |
58 | /** |
59 | * @param bool|null $value |
60 | */ |
61 | public function setWatchThis( $value ) { |
62 | $this->trySet( $this->watchThis, $value ); |
63 | } |
64 | |
65 | /** |
66 | * @return string |
67 | */ |
68 | public function getReasonExtra() { |
69 | return $this->reasonExtra; |
70 | } |
71 | |
72 | /** |
73 | * @param string $value |
74 | */ |
75 | public function setReasonExtra( $value ) { |
76 | $this->trySet( $this->reasonExtra, $value ); |
77 | } |
78 | |
79 | /** |
80 | * @return string |
81 | */ |
82 | public function getReasonSelection() { |
83 | return $this->reasonSelection; |
84 | } |
85 | |
86 | /** |
87 | * @param string $value |
88 | */ |
89 | public function setReasonSelection( $value ) { |
90 | $this->trySet( $this->reasonSelection, $value ); |
91 | } |
92 | |
93 | /** |
94 | * @return string |
95 | */ |
96 | public function getExpiryCustom() { |
97 | return $this->expiryCustom; |
98 | } |
99 | |
100 | /** |
101 | * @param string $value |
102 | */ |
103 | public function setExpiryCustom( $value ) { |
104 | $this->trySet( $this->expiryCustom, $value ); |
105 | } |
106 | |
107 | /** |
108 | * @return string |
109 | */ |
110 | public function getExpirySelection() { |
111 | return $this->expirySelection; |
112 | } |
113 | |
114 | /** |
115 | * @param string $value |
116 | */ |
117 | public function setExpirySelection( $value ) { |
118 | $this->trySet( $this->expirySelection, $value ); |
119 | } |
120 | |
121 | /** |
122 | * @return string |
123 | */ |
124 | public function getAutoreview() { |
125 | return $this->autoreview; |
126 | } |
127 | |
128 | /** |
129 | * @param string $value |
130 | */ |
131 | public function setAutoreview( $value ) { |
132 | $this->trySet( $this->autoreview, $value ); |
133 | } |
134 | |
135 | /** |
136 | * Get the final expiry, all inputs considered |
137 | * Note: does not check if the expiration is less than wfTimestampNow() |
138 | * @return string|bool 14-char timestamp or "infinity", or false if the input was invalid |
139 | */ |
140 | public function getExpiry() { |
141 | $oldConfig = $this->getOldConfig(); |
142 | if ( $this->expirySelection == 'existing' ) { |
143 | return $oldConfig['expiry']; |
144 | } elseif ( $this->expirySelection == 'othertime' ) { |
145 | $value = $this->expiryCustom; |
146 | } else { |
147 | $value = $this->expirySelection; |
148 | } |
149 | if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) { |
150 | $time = 'infinity'; |
151 | } else { |
152 | $unix = strtotime( $value, ConvertibleTimestamp::time() ); |
153 | # On error returns -1 for PHP <5.1 and false for PHP >=5.1 |
154 | if ( !$unix || $unix === -1 ) { |
155 | return false; |
156 | } |
157 | // FIXME: non-qualified absolute times are not in users |
158 | // specified timezone and there isn't notice about it in the ui |
159 | $time = wfTimestamp( TS_MW, $unix ); |
160 | } |
161 | return $time; |
162 | } |
163 | |
164 | /** |
165 | * Get the final reason, all inputs considered |
166 | * @return string |
167 | */ |
168 | private function getReason() { |
169 | # Custom reason replaces dropdown |
170 | if ( $this->reasonSelection != 'other' ) { |
171 | $comment = $this->reasonSelection; // start with dropdown reason |
172 | if ( $this->reasonExtra != '' ) { |
173 | # Append custom reason |
174 | $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . |
175 | $this->reasonExtra; |
176 | } |
177 | } else { |
178 | $comment = $this->reasonExtra; // just use custom reason |
179 | } |
180 | return $comment; |
181 | } |
182 | |
183 | /** |
184 | * Check that a target is given (e.g. from GET/POST request) |
185 | * @return true|string true on success, error string on failure |
186 | */ |
187 | protected function doCheckTargetGiven() { |
188 | if ( $this->title === null ) { |
189 | return 'stabilize_page_invalid'; |
190 | } |
191 | return true; |
192 | } |
193 | |
194 | /** |
195 | * Check that the target page is valid |
196 | * @param int $flags FOR_SUBMISSION (set on submit) |
197 | * @return true|string true on success, error string on failure |
198 | */ |
199 | protected function doCheckTarget( $flags = 0 ) { |
200 | $flgs = ( $flags & self::FOR_SUBMISSION ) ? IDBAccessObject::READ_LATEST : 0; |
201 | if ( !$this->title->getArticleID( $flgs ) ) { |
202 | return 'stabilize_page_notexists'; |
203 | } elseif ( !FlaggedRevs::inReviewNamespace( $this->title ) ) { |
204 | return 'stabilize_page_unreviewable'; |
205 | } |
206 | return true; |
207 | } |
208 | |
209 | /** |
210 | * Verify and clean up parameters (e.g. from POST request) |
211 | * @return true|string true on success, error string on failure |
212 | */ |
213 | protected function doCheckParameters() { |
214 | # Load old config settings from the primary DB |
215 | $this->oldConfig = FRPageConfig::getStabilitySettings( $this->title, IDBAccessObject::READ_LATEST ); |
216 | if ( $this->expiryCustom != '' ) { |
217 | // Custom expiry takes precedence |
218 | $this->expirySelection = 'othertime'; |
219 | } |
220 | // check other params... |
221 | return $this->reallyDoCheckParameters(); |
222 | } |
223 | |
224 | /** |
225 | * @return true|string true on success, error string on failure |
226 | */ |
227 | protected function reallyDoCheckParameters() { |
228 | return true; |
229 | } |
230 | |
231 | /** |
232 | * Can the user change the settings for this page? |
233 | * Note: if the current autoreview restriction is too high for this user |
234 | * then this will return false. Useful for form selectors. |
235 | * @return bool |
236 | */ |
237 | public function isAllowed() { |
238 | # Users who cannot edit or review the page cannot set this |
239 | $pm = MediaWikiServices::getInstance()->getPermissionManager(); |
240 | return ( $this->getTitle() |
241 | && $pm->userCan( 'stablesettings', $this->getUser(), $this->getTitle() ) |
242 | && $pm->userCan( 'review', $this->getUser(), $this->getTitle() ) |
243 | ); |
244 | } |
245 | |
246 | /** |
247 | * Preload existing page settings (e.g. from GET request). |
248 | */ |
249 | protected function doPreloadParameters() { |
250 | $oldConfig = $this->getOldConfig(); |
251 | if ( $oldConfig['expiry'] == 'infinity' ) { |
252 | $this->expirySelection = 'infinite'; // no settings set OR indefinite |
253 | } else { |
254 | $this->expirySelection = 'existing'; // settings set and NOT indefinite |
255 | } |
256 | $this->reallyDoPreloadParameters(); |
257 | } |
258 | |
259 | /** |
260 | * Override this in subclasses to preload parameters other than expirySelection |
261 | */ |
262 | abstract protected function reallyDoPreloadParameters(); |
263 | |
264 | /** |
265 | * Submit the form parameters for the page config to the DB. |
266 | * |
267 | * @return true|string true on success, error string on failure |
268 | */ |
269 | protected function doSubmit() { |
270 | # Double-check permissions |
271 | if ( !$this->isAllowed() ) { |
272 | return 'stabilize_denied'; |
273 | } |
274 | # Parse and cleanup the expiry time given... |
275 | $expiry = $this->getExpiry(); |
276 | if ( $expiry === false ) { |
277 | return 'stabilize_expiry_invalid'; |
278 | } elseif ( $expiry !== 'infinity' && $expiry < wfTimestampNow() ) { |
279 | return 'stabilize_expiry_old'; |
280 | } |
281 | # Update the DB row with the new config... |
282 | $changed = FRPageConfig::setStabilitySettings( $this->title, $this->getNewConfig() ); |
283 | # Log if this actually changed anything... |
284 | if ( $changed ) { |
285 | // Inform other code that the stabilisation settings for a page have changed. |
286 | $services = MediaWikiServices::getInstance(); |
287 | ( new FlaggedRevsHookRunner( $services->getHookContainer() ) )->onFlaggedRevsStabilitySettingsChanged( |
288 | $this->getTitle(), $this->getNewConfig(), $this->getUser(), $this->getReason() |
289 | ); |
290 | |
291 | $article = FlaggableWikiPage::newInstance( $this->title ); |
292 | if ( FlaggedRevs::useOnlyIfProtected() ) { |
293 | # Config may have changed to allow stable versions, so refresh |
294 | # the tracking table to account for any hidden reviewed versions... |
295 | $frev = FlaggedRevision::determineStable( $this->title ); |
296 | if ( $frev ) { |
297 | $article->updateStableVersion( $frev ); |
298 | } else { |
299 | $article->clearStableVersion(); |
300 | } |
301 | } |
302 | # Update logs and make a null edit |
303 | $nullRevRecord = $this->updateLogsAndHistory( $article ); |
304 | # Null edit may have been auto-reviewed already |
305 | $frev = FlaggedRevision::newFromTitle( |
306 | $this->title, |
307 | $nullRevRecord->getId(), |
308 | IDBAccessObject::READ_LATEST |
309 | ); |
310 | $updatesDone = (bool)$frev; // stableVersionUpdates() already called? |
311 | # Check if this null edit is to be reviewed... |
312 | if ( $this->reviewThis && !$frev ) { |
313 | $flags = null; |
314 | # Review this revision of the page... |
315 | $ok = FlaggedRevs::autoReviewEdit( |
316 | $article, |
317 | $this->user, |
318 | $nullRevRecord, |
319 | $flags |
320 | ); |
321 | if ( $ok ) { |
322 | FlaggedRevs::markRevisionPatrolled( $nullRevRecord ); // reviewed -> patrolled |
323 | $updatesDone = true; // stableVersionUpdates() already called |
324 | } |
325 | } |
326 | # Update page and tracking tables and clear cache. |
327 | if ( !$updatesDone ) { |
328 | FlaggedRevs::stableVersionUpdates( $this->title ); |
329 | } |
330 | } |
331 | # Apply watchlist checkbox value (may be NULL) |
332 | $this->updateWatchlist(); |
333 | return true; |
334 | } |
335 | |
336 | /** |
337 | * Do history & log updates: |
338 | * (a) Add a new stability log entry |
339 | * (b) Add a null edit like the log entry |
340 | * @param FlaggableWikiPage $article |
341 | * @return RevisionRecord |
342 | */ |
343 | private function updateLogsAndHistory( FlaggableWikiPage $article ) { |
344 | $newConfig = $this->getNewConfig(); |
345 | $oldConfig = $this->getOldConfig(); |
346 | $reason = $this->getReason(); |
347 | |
348 | # Insert stability log entry... |
349 | FlaggedRevsLog::updateStabilityLog( $this->title, $newConfig, $oldConfig, $reason, $this->user ); |
350 | |
351 | # Build null-edit comment...<action: reason [settings] (expiry)> |
352 | if ( FRPageConfig::configIsReset( $newConfig ) ) { |
353 | $type = "stable-logentry-reset"; |
354 | $settings = ''; // no level, expiry info |
355 | } else { |
356 | $type = "stable-logentry-config"; |
357 | // Settings message in text form (e.g. [x=a,y=b,z]) |
358 | $params = FlaggedRevsLog::stabilityLogParams( $newConfig ); |
359 | $settings = FlaggedRevsStableLogFormatter::stabilitySettings( $params, true /*content*/ ); |
360 | } |
361 | // action |
362 | $services = MediaWikiServices::getInstance(); |
363 | $comment = $services->getContentLanguage()->ucfirst( |
364 | wfMessage( $type, $this->title->getPrefixedText() )->inContentLanguage()->text() |
365 | ); |
366 | if ( $reason != '' ) { |
367 | $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason; // add reason |
368 | } |
369 | if ( $settings != '' ) { |
370 | $comment .= ' ' . $settings; |
371 | } |
372 | |
373 | # Insert a null revision... |
374 | $insertedRevRecord = $services->getPageUpdaterFactory() |
375 | ->newPageUpdater( $article->getTitle(), $this->user ) |
376 | ->saveDummyRevision( $comment, EDIT_MINOR ); |
377 | |
378 | # Return null RevisionRecord object for autoreview check |
379 | return $insertedRevRecord; |
380 | } |
381 | |
382 | /** |
383 | * Get current stability config array |
384 | * @return array |
385 | */ |
386 | public function getOldConfig() { |
387 | if ( $this->getState() == self::FORM_UNREADY ) { |
388 | throw new LogicException( __CLASS__ . " input fields not set yet.\n" ); |
389 | } |
390 | if ( $this->oldConfig === [] && $this->title ) { |
391 | $this->oldConfig = FRPageConfig::getStabilitySettings( $this->title ); |
392 | } |
393 | return $this->oldConfig; |
394 | } |
395 | |
396 | /** |
397 | * Get proposed stability config array |
398 | * @return array |
399 | */ |
400 | public function getNewConfig() { |
401 | return [ |
402 | 'override' => $this->override, |
403 | 'autoreview' => $this->autoreview, |
404 | 'expiry' => $this->getExpiry(), // TS_MW/infinity |
405 | ]; |
406 | } |
407 | |
408 | /** |
409 | * (a) Watch page if $watchThis is true |
410 | * (b) Unwatch if $watchThis is false |
411 | */ |
412 | private function updateWatchlist() { |
413 | # Apply watchlist checkbox value (may be NULL) |
414 | $watchlistManager = MediaWikiServices::getInstance()->getWatchlistManager(); |
415 | if ( $this->watchThis === true ) { |
416 | $watchlistManager->addWatch( $this->user, $this->title ); |
417 | } elseif ( $this->watchThis === false ) { |
418 | $watchlistManager->removeWatch( $this->user, $this->title ); |
419 | } |
420 | } |
421 | } |