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