MediaWiki master
RevDelList.php
Go to the documentation of this file.
1<?php
31
43abstract class RevDelList extends RevisionListBase {
44
46 protected const SUPPRESS_BIT = RevisionRecord::DELETED_RESTRICTED;
47
49 private $lbFactory;
50
57 public function __construct(
58 IContextSource $context,
59 PageIdentity $page,
60 array $ids,
61 LBFactory $lbFactory
62 ) {
63 parent::__construct( $context, $page );
64
65 // ids is a protected variable in RevisionListBase
66 $this->ids = $ids;
67 $this->lbFactory = $lbFactory;
68 }
69
76 public static function getRelationType() {
77 return null;
78 }
79
86 public static function getRestriction() {
87 return null;
88 }
89
96 public static function getRevdelConstant() {
97 return null;
98 }
99
108 public static function suggestTarget( $target, array $ids ) {
109 return $target;
110 }
111
117 public function areAnySuppressed() {
119 foreach ( $this as $item ) {
120 if ( $item->getBits() & self::SUPPRESS_BIT ) {
121 return true;
122 }
123 }
124
125 return false;
126 }
127
140 public function setVisibility( array $params ) {
141 $status = Status::newGood();
142
143 $bitPars = $params['value'];
144 $comment = $params['comment'];
145 $perItemStatus = $params['perItemStatus'] ?? false;
146
147 // T387638 - Always ensure ->value['itemStatuses'] is set if requested
148 if ( $perItemStatus ) {
149 $status->value['itemStatuses'] = [];
150 }
151
152 // CAS-style checks are done on the _deleted fields so the select
153 // does not need to use FOR UPDATE nor be in the atomic section
154 $dbw = $this->lbFactory->getPrimaryDatabase();
155 $this->res = $this->doQuery( $dbw );
156
157 $status->merge( $this->acquireItemLocks() );
158 if ( !$status->isGood() ) {
159 return $status;
160 }
161
162 $dbw->startAtomic( __METHOD__, $dbw::ATOMIC_CANCELABLE );
163 $dbw->onTransactionResolution(
164 function () {
165 // Release locks on commit or error
166 $this->releaseItemLocks();
167 },
168 __METHOD__
169 );
170
171 $missing = array_fill_keys( $this->ids, true );
172 $this->clearFileOps();
173 $idsForLog = [];
174 $authorActors = [];
175
176 // For multi-item deletions, set the old/new bitfields in log_params such that "hid X"
177 // shows in logs if field X was hidden from ANY item and likewise for "unhid Y". Note the
178 // form does not let the same field get hidden and unhidden in different items at once.
179 $virtualOldBits = 0;
180 $virtualNewBits = 0;
181 $logType = 'delete';
182 $useSuppressLog = false;
183
184 // Will be filled with id => [old, new bits] information and
185 // passed to doPostCommitUpdates().
186 $visibilityChangeMap = [];
187
189 foreach ( $this as $item ) {
190 unset( $missing[$item->getId()] );
191
192 if ( $perItemStatus ) {
193 $itemStatus = Status::newGood();
194 $status->value['itemStatuses'][$item->getId()] = $itemStatus;
195 } else {
196 $itemStatus = $status;
197 }
198
199 $oldBits = $item->getBits();
200 // Build the actual new rev_deleted bitfield
201 $newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits );
202
203 if ( $oldBits == $newBits ) {
204 $itemStatus->warning(
205 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
206 $status->failCount++;
207 continue;
208 } elseif ( $oldBits == 0 && $newBits != 0 ) {
209 $opType = 'hide';
210 } elseif ( $oldBits != 0 && $newBits == 0 ) {
211 $opType = 'show';
212 } else {
213 $opType = 'modify';
214 }
215
216 if ( $item->isHideCurrentOp( $newBits ) ) {
217 // Cannot hide current version text
218 $itemStatus->error(
219 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
220 $status->failCount++;
221 continue;
222 } elseif ( !$item->canView() ) {
223 // Cannot access this revision
224 $msg = ( $opType == 'show' ) ?
225 'revdelete-show-no-access' : 'revdelete-modify-no-access';
226 $itemStatus->error( $msg, $item->formatDate(), $item->formatTime() );
227 $status->failCount++;
228 continue;
229 // Cannot just "hide from Sysops" without hiding any fields
230 } elseif ( $newBits == self::SUPPRESS_BIT ) {
231 $itemStatus->warning(
232 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
233 $status->failCount++;
234 continue;
235 }
236
237 // Update the revision
238 $ok = $item->setBits( $newBits );
239
240 if ( $ok ) {
241 $idsForLog[] = $item->getId();
242 // If any item field was suppressed or unsuppressed
243 if ( ( $oldBits | $newBits ) & self::SUPPRESS_BIT ) {
244 $logType = 'suppress';
245 $useSuppressLog = true;
246 }
247 // Track which fields where (un)hidden for each item
248 $addedBits = ( $oldBits ^ $newBits ) & $newBits;
249 $removedBits = ( $oldBits ^ $newBits ) & $oldBits;
250 $virtualNewBits |= $addedBits;
251 $virtualOldBits |= $removedBits;
252
253 $status->successCount++;
254 $authorActors[] = $item->getAuthorActor();
255
256 // Save the old and new bits in $visibilityChangeMap for
257 // later use.
258 $visibilityChangeMap[$item->getId()] = [
259 'oldBits' => $oldBits,
260 'newBits' => $newBits,
261 ];
262 } else {
263 $itemStatus->error(
264 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
265 $status->failCount++;
266 }
267 }
268
269 // Handle missing revisions
270 foreach ( $missing as $id => $unused ) {
271 if ( $perItemStatus ) {
272 $status->value['itemStatuses'][$id] = Status::newFatal( 'revdelete-modify-missing', $id );
273 } else {
274 $status->error( 'revdelete-modify-missing', $id );
275 }
276 $status->failCount++;
277 }
278
279 if ( $status->successCount == 0 ) {
280 $dbw->endAtomic( __METHOD__ );
281 return $status;
282 }
283
284 // Save success count
285 $successCount = $status->successCount;
286
287 // Move files, if there are any
288 $status->merge( $this->doPreCommitUpdates() );
289 if ( !$status->isOK() ) {
290 // Fatal error, such as no configured archive directory or I/O failures
291 $dbw->cancelAtomic( __METHOD__ );
292 return $status;
293 }
294
295 // Log it
296 $authorFields = [];
297 $authorFields['authorActors'] = $authorActors;
298
299 $tags = $params['tags'] ?? [];
300
301 $logEntry = $this->updateLog(
302 $logType,
303 [
304 'page' => $this->page,
305 'count' => $successCount,
306 'newBits' => $virtualNewBits,
307 'oldBits' => $virtualOldBits,
308 'comment' => $comment,
309 'ids' => $idsForLog,
310 'tags' => $tags,
311 ] + $authorFields
312 );
313
314 $this->emitEvents( $bitPars, $visibilityChangeMap, $tags, $logEntry, $useSuppressLog );
315
316 // Clear caches after commit
317 DeferredUpdates::addCallableUpdate(
318 function () use ( $visibilityChangeMap ) {
319 $this->doPostCommitUpdates( $visibilityChangeMap );
320 },
321 DeferredUpdates::PRESEND,
322 $dbw
323 );
324
325 $dbw->endAtomic( __METHOD__ );
326
327 return $status;
328 }
329
337 protected function emitEvents(
338 array $bitPars,
339 array $visibilityChangeMap,
340 array $tags,
341 LogEntry $logEntry,
342 bool $suppressed
343 ) {
344 // stub
345 }
346
347 final protected function acquireItemLocks(): Status {
348 $status = Status::newGood();
350 foreach ( $this as $item ) {
351 $status->merge( $item->lock() );
352 }
353
354 return $status;
355 }
356
357 final protected function releaseItemLocks(): Status {
358 $status = Status::newGood();
360 foreach ( $this as $item ) {
361 $status->merge( $item->unlock() );
362 }
363
364 return $status;
365 }
366
372 public function reloadFromPrimary() {
373 $dbw = $this->lbFactory->getPrimaryDatabase();
374 $this->res = $this->doQuery( $dbw );
375 }
376
389 private function updateLog( $logType, $params ): LogEntry {
390 // Get the URL param's corresponding DB field
391 $field = RevisionDeleter::getRelationType( $this->getType() );
392 if ( !$field ) {
393 throw new UnexpectedValueException( "Bad log URL param type!" );
394 }
395 // Add params for affected page and ids
396 $logParams = $this->getLogParams( $params );
397 // Actually add the deletion log entry
398 $logEntry = new ManualLogEntry( $logType, $this->getLogAction() );
399 $logEntry->setTarget( $params['page'] );
400 $logEntry->setComment( $params['comment'] );
401 $logEntry->setParameters( $logParams );
402 $logEntry->setPerformer( $this->getUser() );
403 // Allow for easy searching of deletion log items for revision/log items
404 $relations = [
405 $field => $params['ids'],
406 ];
407 if ( isset( $params['authorActors'] ) ) {
408 $relations += [
409 'target_author_actor' => $params['authorActors'],
410 ];
411 }
412 $logEntry->setRelations( $relations );
413 // Apply change tags to the log entry
414 $logEntry->addTags( $params['tags'] );
415 $logId = $logEntry->insert();
416 $logEntry->publish( $logId );
417
418 return $logEntry;
419 }
420
425 public function getLogAction() {
426 return 'revision';
427 }
428
434 public function getLogParams( $params ) {
435 return [
436 '4::type' => $this->getType(),
437 '5::ids' => $params['ids'],
438 '6::ofield' => $params['oldBits'],
439 '7::nfield' => $params['newBits'],
440 ];
441 }
442
447 public function clearFileOps() {
448 }
449
455 public function doPreCommitUpdates() {
456 return Status::newGood();
457 }
458
465 public function doPostCommitUpdates( array $visibilityChangeMap ) {
466 return Status::newGood();
467 }
468
469}
Defer callable updates to run later in the PHP process.
Class for creating new log entries and inserting them into the database.
List for revision table items for a single page.
doQuery( $db)
Do the DB query to iterate through the objects.
Page revision base class.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Represents a title within MediaWiki.
Definition Title.php:78
Abstract base class for deletable items.
setVisibility(array $params)
Set the visibility for the revisions in this list.
areAnySuppressed()
Indicate whether any item in this list is suppressed.
const SUPPRESS_BIT
Flag used for suppression, depending on the type of log.
static suggestTarget( $target, array $ids)
Suggest a target for the revision deletion Optionally override this function.
doPreCommitUpdates()
A hook for setVisibility(): do batch updates pre-commit.
emitEvents(array $bitPars, array $visibilityChangeMap, array $tags, LogEntry $logEntry, bool $suppressed)
static getRestriction()
Get the user right required for this list type Override this function.
getLogAction()
Get the log action for this list type.
clearFileOps()
Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates() STUB.
static getRelationType()
Get the DB field name associated with the ID list.
doPostCommitUpdates(array $visibilityChangeMap)
A hook for setVisibility(): do any necessary updates post-commit.
getLogParams( $params)
Get log parameter array.
static getRevdelConstant()
Get the revision deletion constant for this list type Override this function.
reloadFromPrimary()
Reload the list data from the primary DB.
__construct(IContextSource $context, PageIdentity $page, array $ids, LBFactory $lbFactory)
General controller for RevDel, used by both SpecialRevisiondelete and ApiRevisionDelete.
merge( $other, $overwriteValue=false)
Merge another status object into this one.
Interface for objects which can provide a MediaWiki context on request.
Interface for objects (potentially) representing an editable wiki page.
array $params
The job parameters.