MediaWiki  master
RevDelList.php
Go to the documentation of this file.
1 <?php
24 
36 abstract class RevDelList extends RevisionListBase {
38  parent::__construct( $context, $title );
39  $this->ids = $ids;
40  }
41 
48  public static function getRelationType() {
49  return null;
50  }
51 
58  public static function getRestriction() {
59  return null;
60  }
61 
68  public static function getRevdelConstant() {
69  return null;
70  }
71 
80  public static function suggestTarget( $target, array $ids ) {
81  return $target;
82  }
83 
89  public function areAnySuppressed() {
90  $bit = $this->getSuppressBit();
91 
93  foreach ( $this as $item ) {
94  if ( $item->getBits() & $bit ) {
95  return true;
96  }
97  }
98 
99  return false;
100  }
101 
114  public function setVisibility( array $params ) {
115  $status = Status::newGood();
116 
117  $bitPars = $params['value'];
118  $comment = $params['comment'];
119  $perItemStatus = $params['perItemStatus'] ?? false;
120 
121  // CAS-style checks are done on the _deleted fields so the select
122  // does not need to use FOR UPDATE nor be in the atomic section
123  $dbw = wfGetDB( DB_MASTER );
124  $this->res = $this->doQuery( $dbw );
125 
126  $status->merge( $this->acquireItemLocks() );
127  if ( !$status->isGood() ) {
128  return $status;
129  }
130 
131  $dbw->startAtomic( __METHOD__ );
132  $dbw->onTransactionResolution(
133  function () {
134  // Release locks on commit or error
135  $this->releaseItemLocks();
136  },
137  __METHOD__
138  );
139 
140  $missing = array_flip( $this->ids );
141  $this->clearFileOps();
142  $idsForLog = [];
143  $authorActors = [];
144 
145  if ( $perItemStatus ) {
146  $status->itemStatuses = [];
147  }
148 
149  // For multi-item deletions, set the old/new bitfields in log_params such that "hid X"
150  // shows in logs if field X was hidden from ANY item and likewise for "unhid Y". Note the
151  // form does not let the same field get hidden and unhidden in different items at once.
152  $virtualOldBits = 0;
153  $virtualNewBits = 0;
154  $logType = 'delete';
155 
156  // Will be filled with id => [old, new bits] information and
157  // passed to doPostCommitUpdates().
158  $visibilityChangeMap = [];
159 
161  foreach ( $this as $item ) {
162  unset( $missing[$item->getId()] );
163 
164  if ( $perItemStatus ) {
165  $itemStatus = Status::newGood();
166  $status->itemStatuses[$item->getId()] = $itemStatus;
167  } else {
168  $itemStatus = $status;
169  }
170 
171  $oldBits = $item->getBits();
172  // Build the actual new rev_deleted bitfield
173  $newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits );
174 
175  if ( $oldBits == $newBits ) {
176  $itemStatus->warning(
177  'revdelete-no-change', $item->formatDate(), $item->formatTime() );
178  $status->failCount++;
179  continue;
180  } elseif ( $oldBits == 0 && $newBits != 0 ) {
181  $opType = 'hide';
182  } elseif ( $oldBits != 0 && $newBits == 0 ) {
183  $opType = 'show';
184  } else {
185  $opType = 'modify';
186  }
187 
188  if ( $item->isHideCurrentOp( $newBits ) ) {
189  // Cannot hide current version text
190  $itemStatus->error(
191  'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
192  $status->failCount++;
193  continue;
194  } elseif ( !$item->canView() ) {
195  // Cannot access this revision
196  $msg = ( $opType == 'show' ) ?
197  'revdelete-show-no-access' : 'revdelete-modify-no-access';
198  $itemStatus->error( $msg, $item->formatDate(), $item->formatTime() );
199  $status->failCount++;
200  continue;
201  // Cannot just "hide from Sysops" without hiding any fields
202  } elseif ( $newBits == RevisionRecord::DELETED_RESTRICTED ) {
203  $itemStatus->warning(
204  'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
205  $status->failCount++;
206  continue;
207  }
208 
209  // Update the revision
210  $ok = $item->setBits( $newBits );
211 
212  if ( $ok ) {
213  $idsForLog[] = $item->getId();
214  // If any item field was suppressed or unsuppressed
215  if ( ( $oldBits | $newBits ) & $this->getSuppressBit() ) {
216  $logType = 'suppress';
217  }
218  // Track which fields where (un)hidden for each item
219  $addedBits = ( $oldBits ^ $newBits ) & $newBits;
220  $removedBits = ( $oldBits ^ $newBits ) & $oldBits;
221  $virtualNewBits |= $addedBits;
222  $virtualOldBits |= $removedBits;
223 
224  $status->successCount++;
225  $authorActors[] = $item->getAuthorActor();
226 
227  // Save the old and new bits in $visibilityChangeMap for
228  // later use.
229  $visibilityChangeMap[$item->getId()] = [
230  'oldBits' => $oldBits,
231  'newBits' => $newBits,
232  ];
233  } else {
234  $itemStatus->error(
235  'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
236  $status->failCount++;
237  }
238  }
239 
240  // Handle missing revisions
241  foreach ( $missing as $id => $unused ) {
242  if ( $perItemStatus ) {
243  $status->itemStatuses[$id] = Status::newFatal( 'revdelete-modify-missing', $id );
244  } else {
245  $status->error( 'revdelete-modify-missing', $id );
246  }
247  $status->failCount++;
248  }
249 
250  if ( $status->successCount == 0 ) {
251  $dbw->endAtomic( __METHOD__ );
252  return $status;
253  }
254 
255  // Save success count
256  $successCount = $status->successCount;
257 
258  // Move files, if there are any
259  $status->merge( $this->doPreCommitUpdates() );
260  if ( !$status->isOK() ) {
261  // Fatal error, such as no configured archive directory or I/O failures
262  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
263  $lbFactory->rollbackMasterChanges( __METHOD__ );
264  return $status;
265  }
266 
267  // Log it
268  $authorFields = [];
269  $authorFields['authorActors'] = $authorActors;
270  $this->updateLog(
271  $logType,
272  [
273  'title' => $this->title,
274  'count' => $successCount,
275  'newBits' => $virtualNewBits,
276  'oldBits' => $virtualOldBits,
277  'comment' => $comment,
278  'ids' => $idsForLog,
279  'tags' => $params['tags'] ?? [],
280  ] + $authorFields
281  );
282 
283  // Clear caches after commit
285  function () use ( $visibilityChangeMap ) {
286  $this->doPostCommitUpdates( $visibilityChangeMap );
287  },
289  $dbw
290  );
291 
292  $dbw->endAtomic( __METHOD__ );
293 
294  return $status;
295  }
296 
297  final protected function acquireItemLocks() {
298  $status = Status::newGood();
300  foreach ( $this as $item ) {
301  $status->merge( $item->lock() );
302  }
303 
304  return $status;
305  }
306 
307  final protected function releaseItemLocks() {
308  $status = Status::newGood();
310  foreach ( $this as $item ) {
311  $status->merge( $item->unlock() );
312  }
313 
314  return $status;
315  }
316 
321  function reloadFromMaster() {
322  $dbw = wfGetDB( DB_MASTER );
323  $this->res = $this->doQuery( $dbw );
324  }
325 
339  private function updateLog( $logType, $params ) {
340  // Get the URL param's corresponding DB field
341  $field = RevisionDeleter::getRelationType( $this->getType() );
342  if ( !$field ) {
343  throw new MWException( "Bad log URL param type!" );
344  }
345  // Add params for affected page and ids
346  $logParams = $this->getLogParams( $params );
347  // Actually add the deletion log entry
348  $logEntry = new ManualLogEntry( $logType, $this->getLogAction() );
349  $logEntry->setTarget( $params['title'] );
350  $logEntry->setComment( $params['comment'] );
351  $logEntry->setParameters( $logParams );
352  $logEntry->setPerformer( $this->getUser() );
353  // Allow for easy searching of deletion log items for revision/log items
354  $relations = [
355  $field => $params['ids'],
356  ];
357  if ( isset( $params['authorActors'] ) ) {
358  $relations += [
359  'target_author_actor' => $params['authorActors'],
360  ];
361  }
362  $logEntry->setRelations( $relations );
363  // Apply change tags to the log entry
364  $logEntry->addTags( $params['tags'] );
365  $logId = $logEntry->insert();
366  $logEntry->publish( $logId );
367  }
368 
373  public function getLogAction() {
374  return 'revision';
375  }
376 
382  public function getLogParams( $params ) {
383  return [
384  '4::type' => $this->getType(),
385  '5::ids' => $params['ids'],
386  '6::ofield' => $params['oldBits'],
387  '7::nfield' => $params['newBits'],
388  ];
389  }
390 
395  public function clearFileOps() {
396  }
397 
403  public function doPreCommitUpdates() {
404  return Status::newGood();
405  }
406 
413  public function doPostCommitUpdates( array $visibilityChangeMap ) {
414  return Status::newGood();
415  }
416 
420  abstract public function getSuppressBit();
421 }
clearFileOps()
Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates() STUB...
Definition: RevDelList.php:395
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
static getRelationType( $typeName)
Get DB field name for URL param...
getLogParams( $params)
Get log parameter array.
Definition: RevDelList.php:382
getType()
Get the internal type name of this list.
static suggestTarget( $target, array $ids)
Suggest a target for the revision deletion Optionally override this function.
Definition: RevDelList.php:80
updateLog( $logType, $params)
Record a log entry on the action.
Definition: RevDelList.php:339
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
IContextSource $context
const DB_MASTER
Definition: defines.php:26
List for revision table items for a single page.
areAnySuppressed()
Indicate whether any item in this list is suppressed.
Definition: RevDelList.php:89
releaseItemLocks()
Definition: RevDelList.php:307
getSuppressBit()
Get the integer value of the flag used for suppression.
setVisibility(array $params)
Set the visibility for the revisions in this list.
Definition: RevDelList.php:114
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
reloadFromMaster()
Reload the list data from the master DB.
Definition: RevDelList.php:321
getLogAction()
Get the log action for this list type.
Definition: RevDelList.php:373
__construct(IContextSource $context, Title $title, array $ids)
Definition: RevDelList.php:37
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
static getRevdelConstant()
Get the revision deletion constant for this list type Override this function.
Definition: RevDelList.php:68
static extractBitfield(array $bitPars, $oldfield)
Put together a rev_deleted bitfield.
doPostCommitUpdates(array $visibilityChangeMap)
A hook for setVisibility(): do any necessary updates post-commit.
Definition: RevDelList.php:413
static getRestriction()
Get the user right required for this list type Override this function.
Definition: RevDelList.php:58
doPreCommitUpdates()
A hook for setVisibility(): do batch updates pre-commit.
Definition: RevDelList.php:403
doQuery( $db)
Do the DB query to iterate through the objects.
acquireItemLocks()
Definition: RevDelList.php:297
static getRelationType()
Get the DB field name associated with the ID list.
Definition: RevDelList.php:48