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