MediaWiki REL1_39
RevDelList.php
Go to the documentation of this file.
1<?php
25
37abstract class RevDelList extends RevisionListBase {
38
40 private $lbFactory;
41
48 public function __construct(
49 IContextSource $context,
51 array $ids,
52 LBFactory $lbFactory
53 ) {
54 parent::__construct( $context, $page );
55
56 // ids is a protected variable in RevisionListBase
57 $this->ids = $ids;
58 $this->lbFactory = $lbFactory;
59 }
60
67 public static function getRelationType() {
68 return null;
69 }
70
77 public static function getRestriction() {
78 return null;
79 }
80
87 public static function getRevdelConstant() {
88 return null;
89 }
90
99 public static function suggestTarget( $target, array $ids ) {
100 return $target;
101 }
102
108 public function areAnySuppressed() {
109 $bit = $this->getSuppressBit();
110
112 foreach ( $this as $item ) {
113 if ( $item->getBits() & $bit ) {
114 return true;
115 }
116 }
117
118 return false;
119 }
120
133 public function setVisibility( array $params ) {
134 $status = Status::newGood();
135
136 $bitPars = $params['value'];
137 $comment = $params['comment'];
138 $perItemStatus = $params['perItemStatus'] ?? false;
139
140 // T387638 - Always ensure ->value['itemStatuses'] is set if requested
141 if ( $perItemStatus ) {
142 $status->value['itemStatuses'] = [];
143 }
144
145 // CAS-style checks are done on the _deleted fields so the select
146 // does not need to use FOR UPDATE nor be in the atomic section
147 $dbw = $this->lbFactory->getMainLB()->getConnectionRef( DB_PRIMARY );
148 $this->res = $this->doQuery( $dbw );
149
150 $status->merge( $this->acquireItemLocks() );
151 if ( !$status->isGood() ) {
152 return $status;
153 }
154
155 $dbw->startAtomic( __METHOD__, $dbw::ATOMIC_CANCELABLE );
156 $dbw->onTransactionResolution(
157 function () {
158 // Release locks on commit or error
159 $this->releaseItemLocks();
160 },
161 __METHOD__
162 );
163
164 $missing = array_fill_keys( $this->ids, true );
165 $this->clearFileOps();
166 $idsForLog = [];
167 $authorActors = [];
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->getMainLB()->getConnectionRef( DB_PRIMARY );
343 $this->res = $this->doQuery( $dbw );
344 }
345
349 public function reloadFromMaster() {
350 wfDeprecated( __METHOD__, '1.37' );
351 $this->reloadFromPrimary();
352 }
353
367 private function updateLog( $logType, $params ) {
368 // Get the URL param's corresponding DB field
369 $field = RevisionDeleter::getRelationType( $this->getType() );
370 if ( !$field ) {
371 throw new MWException( "Bad log URL param type!" );
372 }
373 // Add params for affected page and ids
374 $logParams = $this->getLogParams( $params );
375 // Actually add the deletion log entry
376 $logEntry = new ManualLogEntry( $logType, $this->getLogAction() );
377 $logEntry->setTarget( $params['page'] );
378 $logEntry->setComment( $params['comment'] );
379 $logEntry->setParameters( $logParams );
380 $logEntry->setPerformer( $this->getUser() );
381 // Allow for easy searching of deletion log items for revision/log items
382 $relations = [
383 $field => $params['ids'],
384 ];
385 if ( isset( $params['authorActors'] ) ) {
386 $relations += [
387 'target_author_actor' => $params['authorActors'],
388 ];
389 }
390 $logEntry->setRelations( $relations );
391 // Apply change tags to the log entry
392 $logEntry->addTags( $params['tags'] );
393 $logId = $logEntry->insert();
394 $logEntry->publish( $logId );
395 }
396
401 public function getLogAction() {
402 return 'revision';
403 }
404
410 public function getLogParams( $params ) {
411 return [
412 '4::type' => $this->getType(),
413 '5::ids' => $params['ids'],
414 '6::ofield' => $params['oldBits'],
415 '7::nfield' => $params['newBits'],
416 ];
417 }
418
423 public function clearFileOps() {
424 }
425
431 public function doPreCommitUpdates() {
432 return Status::newGood();
433 }
434
441 public function doPostCommitUpdates( array $visibilityChangeMap ) {
442 return Status::newGood();
443 }
444
448 abstract public function getSuppressBit();
449}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
MediaWiki exception.
Class for creating new log entries and inserting them into the database.
Page revision base class.
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.
const DB_PRIMARY
Definition defines.php:28