MediaWiki REL1_40
RevDelList.php
Go to the documentation of this file.
1<?php
26
38abstract class RevDelList extends RevisionListBase {
39
41 private $lbFactory;
42
49 public function __construct(
50 IContextSource $context,
52 array $ids,
53 LBFactory $lbFactory
54 ) {
55 parent::__construct( $context, $page );
56
57 // ids is a protected variable in RevisionListBase
58 $this->ids = $ids;
59 $this->lbFactory = $lbFactory;
60 }
61
68 public static function getRelationType() {
69 return null;
70 }
71
78 public static function getRestriction() {
79 return null;
80 }
81
88 public static function getRevdelConstant() {
89 return null;
90 }
91
100 public static function suggestTarget( $target, array $ids ) {
101 return $target;
102 }
103
109 public function areAnySuppressed() {
110 $bit = $this->getSuppressBit();
111
113 foreach ( $this as $item ) {
114 if ( $item->getBits() & $bit ) {
115 return true;
116 }
117 }
118
119 return false;
120 }
121
134 public function setVisibility( array $params ) {
135 $status = Status::newGood();
136
137 $bitPars = $params['value'];
138 $comment = $params['comment'];
139 $perItemStatus = $params['perItemStatus'] ?? false;
140
141 // CAS-style checks are done on the _deleted fields so the select
142 // does not need to use FOR UPDATE nor be in the atomic section
143 $dbw = $this->lbFactory->getPrimaryDatabase();
144 $this->res = $this->doQuery( $dbw );
145
146 $status->merge( $this->acquireItemLocks() );
147 if ( !$status->isGood() ) {
148 return $status;
149 }
150
151 $dbw->startAtomic( __METHOD__, $dbw::ATOMIC_CANCELABLE );
152 $dbw->onTransactionResolution(
153 function () {
154 // Release locks on commit or error
155 $this->releaseItemLocks();
156 },
157 __METHOD__
158 );
159
160 $missing = array_fill_keys( $this->ids, true );
161 $this->clearFileOps();
162 $idsForLog = [];
163 $authorActors = [];
164
165 if ( $perItemStatus ) {
166 $status->value['itemStatuses'] = [];
167 }
168
169 // For multi-item deletions, set the old/new bitfields in log_params such that "hid X"
170 // shows in logs if field X was hidden from ANY item and likewise for "unhid Y". Note the
171 // form does not let the same field get hidden and unhidden in different items at once.
172 $virtualOldBits = 0;
173 $virtualNewBits = 0;
174 $logType = 'delete';
175
176 // Will be filled with id => [old, new bits] information and
177 // passed to doPostCommitUpdates().
178 $visibilityChangeMap = [];
179
181 foreach ( $this as $item ) {
182 unset( $missing[$item->getId()] );
183
184 if ( $perItemStatus ) {
185 $itemStatus = Status::newGood();
186 $status->value['itemStatuses'][$item->getId()] = $itemStatus;
187 } else {
188 $itemStatus = $status;
189 }
190
191 $oldBits = $item->getBits();
192 // Build the actual new rev_deleted bitfield
193 $newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits );
194
195 if ( $oldBits == $newBits ) {
196 $itemStatus->warning(
197 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
198 $status->failCount++;
199 continue;
200 } elseif ( $oldBits == 0 && $newBits != 0 ) {
201 $opType = 'hide';
202 } elseif ( $oldBits != 0 && $newBits == 0 ) {
203 $opType = 'show';
204 } else {
205 $opType = 'modify';
206 }
207
208 if ( $item->isHideCurrentOp( $newBits ) ) {
209 // Cannot hide current version text
210 $itemStatus->error(
211 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
212 $status->failCount++;
213 continue;
214 } elseif ( !$item->canView() ) {
215 // Cannot access this revision
216 $msg = ( $opType == 'show' ) ?
217 'revdelete-show-no-access' : 'revdelete-modify-no-access';
218 $itemStatus->error( $msg, $item->formatDate(), $item->formatTime() );
219 $status->failCount++;
220 continue;
221 // Cannot just "hide from Sysops" without hiding any fields
222 } elseif ( $newBits == RevisionRecord::DELETED_RESTRICTED ) {
223 $itemStatus->warning(
224 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
225 $status->failCount++;
226 continue;
227 }
228
229 // Update the revision
230 $ok = $item->setBits( $newBits );
231
232 if ( $ok ) {
233 $idsForLog[] = $item->getId();
234 // If any item field was suppressed or unsuppressed
235 if ( ( $oldBits | $newBits ) & $this->getSuppressBit() ) {
236 $logType = 'suppress';
237 }
238 // Track which fields where (un)hidden for each item
239 $addedBits = ( $oldBits ^ $newBits ) & $newBits;
240 $removedBits = ( $oldBits ^ $newBits ) & $oldBits;
241 $virtualNewBits |= $addedBits;
242 $virtualOldBits |= $removedBits;
243
244 $status->successCount++;
245 $authorActors[] = $item->getAuthorActor();
246
247 // Save the old and new bits in $visibilityChangeMap for
248 // later use.
249 $visibilityChangeMap[$item->getId()] = [
250 'oldBits' => $oldBits,
251 'newBits' => $newBits,
252 ];
253 } else {
254 $itemStatus->error(
255 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
256 $status->failCount++;
257 }
258 }
259
260 // Handle missing revisions
261 foreach ( $missing as $id => $unused ) {
262 if ( $perItemStatus ) {
263 $status->value['itemStatuses'][$id] = Status::newFatal( 'revdelete-modify-missing', $id );
264 } else {
265 $status->error( 'revdelete-modify-missing', $id );
266 }
267 $status->failCount++;
268 }
269
270 if ( $status->successCount == 0 ) {
271 $dbw->endAtomic( __METHOD__ );
272 return $status;
273 }
274
275 // Save success count
276 $successCount = $status->successCount;
277
278 // Move files, if there are any
279 $status->merge( $this->doPreCommitUpdates() );
280 if ( !$status->isOK() ) {
281 // Fatal error, such as no configured archive directory or I/O failures
282 $dbw->cancelAtomic( __METHOD__ );
283 return $status;
284 }
285
286 // Log it
287 $authorFields = [];
288 $authorFields['authorActors'] = $authorActors;
289 $this->updateLog(
290 $logType,
291 [
292 'page' => $this->page,
293 'count' => $successCount,
294 'newBits' => $virtualNewBits,
295 'oldBits' => $virtualOldBits,
296 'comment' => $comment,
297 'ids' => $idsForLog,
298 'tags' => $params['tags'] ?? [],
299 ] + $authorFields
300 );
301
302 // Clear caches after commit
303 DeferredUpdates::addCallableUpdate(
304 function () use ( $visibilityChangeMap ) {
305 $this->doPostCommitUpdates( $visibilityChangeMap );
306 },
307 DeferredUpdates::PRESEND,
308 $dbw
309 );
310
311 $dbw->endAtomic( __METHOD__ );
312
313 return $status;
314 }
315
316 final protected function acquireItemLocks() {
317 $status = Status::newGood();
319 foreach ( $this as $item ) {
320 $status->merge( $item->lock() );
321 }
322
323 return $status;
324 }
325
326 final protected function releaseItemLocks() {
327 $status = Status::newGood();
329 foreach ( $this as $item ) {
330 $status->merge( $item->unlock() );
331 }
332
333 return $status;
334 }
335
341 public function reloadFromPrimary() {
342 $dbw = $this->lbFactory->getPrimaryDatabase();
343 $this->res = $this->doQuery( $dbw );
344 }
345
359 private function updateLog( $logType, $params ) {
360 // Get the URL param's corresponding DB field
361 $field = RevisionDeleter::getRelationType( $this->getType() );
362 if ( !$field ) {
363 throw new MWException( "Bad log URL param type!" );
364 }
365 // Add params for affected page and ids
366 $logParams = $this->getLogParams( $params );
367 // Actually add the deletion log entry
368 $logEntry = new ManualLogEntry( $logType, $this->getLogAction() );
369 $logEntry->setTarget( $params['page'] );
370 $logEntry->setComment( $params['comment'] );
371 $logEntry->setParameters( $logParams );
372 $logEntry->setPerformer( $this->getUser() );
373 // Allow for easy searching of deletion log items for revision/log items
374 $relations = [
375 $field => $params['ids'],
376 ];
377 if ( isset( $params['authorActors'] ) ) {
378 $relations += [
379 'target_author_actor' => $params['authorActors'],
380 ];
381 }
382 $logEntry->setRelations( $relations );
383 // Apply change tags to the log entry
384 $logEntry->addTags( $params['tags'] );
385 $logId = $logEntry->insert();
386 $logEntry->publish( $logId );
387 }
388
393 public function getLogAction() {
394 return 'revision';
395 }
396
402 public function getLogParams( $params ) {
403 return [
404 '4::type' => $this->getType(),
405 '5::ids' => $params['ids'],
406 '6::ofield' => $params['oldBits'],
407 '7::nfield' => $params['newBits'],
408 ];
409 }
410
415 public function clearFileOps() {
416 }
417
423 public function doPreCommitUpdates() {
424 return Status::newGood();
425 }
426
433 public function doPostCommitUpdates( array $visibilityChangeMap ) {
434 return Status::newGood();
435 }
436
440 abstract public function getSuppressBit();
441}
MediaWiki exception.
Class for creating new log entries and inserting them into the database.
Page revision base class.
Represents a title within MediaWiki.
Definition Title.php:82
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.
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.
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.
getSuppressBit()
Get the integer value of the flag used for suppression.
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)
static getRelationType( $typeName)
Get DB field name for URL param... Future code for other things may also track other types of revisio...
static extractBitfield(array $bitPars, $oldfield)
Put together a rev_deleted bitfield.
List for revision table items for a single page.
getType()
Get the internal type name of this list.
doQuery( $db)
Do the DB query to iterate through the objects.
Interface for objects which can provide a MediaWiki context on request.
Interface for objects (potentially) representing an editable wiki page.