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 | use Wikimedia\Rdbms\IDBAccessObject; |
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 ); |
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 | $article = FlaggableWikiPage::newInstance( $this->title ); |
286 | if ( FlaggedRevs::useOnlyIfProtected() ) { |
287 | # Config may have changed to allow stable versions, so refresh |
288 | # the tracking table to account for any hidden reviewed versions... |
289 | $frev = FlaggedRevision::determineStable( $this->title ); |
290 | if ( $frev ) { |
291 | $article->updateStableVersion( $frev ); |
292 | } else { |
293 | $article->clearStableVersion(); |
294 | } |
295 | } |
296 | # Update logs and make a null edit |
297 | $nullRevRecord = $this->updateLogsAndHistory( $article ); |
298 | # Null edit may have been auto-reviewed already |
299 | $frev = FlaggedRevision::newFromTitle( |
300 | $this->title, |
301 | $nullRevRecord->getId(), |
302 | IDBAccessObject::READ_LATEST |
303 | ); |
304 | $updatesDone = (bool)$frev; // stableVersionUpdates() already called? |
305 | # Check if this null edit is to be reviewed... |
306 | if ( $this->reviewThis && !$frev ) { |
307 | $flags = null; |
308 | # Review this revision of the page... |
309 | $ok = FlaggedRevs::autoReviewEdit( |
310 | $article, |
311 | $this->user, |
312 | $nullRevRecord, |
313 | $flags |
314 | ); |
315 | if ( $ok ) { |
316 | FlaggedRevs::markRevisionPatrolled( $nullRevRecord ); // reviewed -> patrolled |
317 | $updatesDone = true; // stableVersionUpdates() already called |
318 | } |
319 | } |
320 | # Update page and tracking tables and clear cache. |
321 | if ( !$updatesDone ) { |
322 | FlaggedRevs::stableVersionUpdates( $this->title ); |
323 | } |
324 | } |
325 | # Apply watchlist checkbox value (may be NULL) |
326 | $this->updateWatchlist(); |
327 | return true; |
328 | } |
329 | |
330 | /** |
331 | * Do history & log updates: |
332 | * (a) Add a new stability log entry |
333 | * (b) Add a null edit like the log entry |
334 | * @param FlaggableWikiPage $article |
335 | * @return RevisionRecord |
336 | */ |
337 | private function updateLogsAndHistory( FlaggableWikiPage $article ) { |
338 | $newConfig = $this->getNewConfig(); |
339 | $oldConfig = $this->getOldConfig(); |
340 | $reason = $this->getReason(); |
341 | |
342 | # Insert stability log entry... |
343 | FlaggedRevsLog::updateStabilityLog( $this->title, $newConfig, $oldConfig, $reason, $this->user ); |
344 | |
345 | # Build null-edit comment...<action: reason [settings] (expiry)> |
346 | if ( FRPageConfig::configIsReset( $newConfig ) ) { |
347 | $type = "stable-logentry-reset"; |
348 | $settings = ''; // no level, expiry info |
349 | } else { |
350 | $type = "stable-logentry-config"; |
351 | // Settings message in text form (e.g. [x=a,y=b,z]) |
352 | $params = FlaggedRevsLog::stabilityLogParams( $newConfig ); |
353 | $settings = FlaggedRevsStableLogFormatter::stabilitySettings( $params, true /*content*/ ); |
354 | } |
355 | // action |
356 | $services = MediaWikiServices::getInstance(); |
357 | $comment = $services->getContentLanguage()->ucfirst( |
358 | wfMessage( $type, $this->title->getPrefixedText() )->inContentLanguage()->text() |
359 | ); |
360 | if ( $reason != '' ) { |
361 | $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason; // add reason |
362 | } |
363 | if ( $settings != '' ) { |
364 | $comment .= ' ' . $settings; |
365 | } |
366 | |
367 | # Insert a null revision... |
368 | $revStore = $services->getRevisionStore(); |
369 | $dbw = $services->getConnectionProvider()->getPrimaryDatabase(); |
370 | $nullRevRecord = $revStore->newNullRevision( |
371 | $dbw, |
372 | $article->getTitle(), |
373 | CommentStoreComment::newUnsavedComment( $comment ), |
374 | true, // minor |
375 | $this->user |
376 | ); |
377 | $insertedRevRecord = $revStore->insertRevisionOn( $nullRevRecord, $dbw ); |
378 | # Update page record and touch page |
379 | $oldLatest = $insertedRevRecord->getParentId(); |
380 | |
381 | $article->updateRevisionOn( $dbw, $insertedRevRecord, $oldLatest ); |
382 | |
383 | $tags = []; // passed by reference |
384 | $hookContainer = $services->getHookContainer(); |
385 | |
386 | $hookRunner = new FlaggedRevsHookRunner( $hookContainer ); |
387 | $hookRunner->onRevisionFromEditComplete( |
388 | $article, $insertedRevRecord, $oldLatest, $this->user, $tags |
389 | ); |
390 | |
391 | # Return null RevisionRecord object for autoreview check |
392 | return $insertedRevRecord; |
393 | } |
394 | |
395 | /** |
396 | * Get current stability config array |
397 | * @return array |
398 | */ |
399 | public function getOldConfig() { |
400 | if ( $this->getState() == self::FORM_UNREADY ) { |
401 | throw new LogicException( __CLASS__ . " input fields not set yet.\n" ); |
402 | } |
403 | if ( $this->oldConfig === [] && $this->title ) { |
404 | $this->oldConfig = FRPageConfig::getStabilitySettings( $this->title ); |
405 | } |
406 | return $this->oldConfig; |
407 | } |
408 | |
409 | /** |
410 | * Get proposed stability config array |
411 | * @return array |
412 | */ |
413 | public function getNewConfig() { |
414 | return [ |
415 | 'override' => $this->override, |
416 | 'autoreview' => $this->autoreview, |
417 | 'expiry' => $this->getExpiry(), // TS_MW/infinity |
418 | ]; |
419 | } |
420 | |
421 | /** |
422 | * (a) Watch page if $watchThis is true |
423 | * (b) Unwatch if $watchThis is false |
424 | */ |
425 | private function updateWatchlist() { |
426 | # Apply watchlist checkbox value (may be NULL) |
427 | $watchlistManager = MediaWikiServices::getInstance()->getWatchlistManager(); |
428 | if ( $this->watchThis === true ) { |
429 | $watchlistManager->addWatch( $this->user, $this->title ); |
430 | } elseif ( $this->watchThis === false ) { |
431 | $watchlistManager->removeWatch( $this->user, $this->title ); |
432 | } |
433 | } |
434 | } |