MediaWiki master
RevDelList.php
Go to the documentation of this file.
1<?php
29
41abstract class RevDelList extends RevisionListBase {
42
44 protected const SUPPRESS_BIT = RevisionRecord::DELETED_RESTRICTED;
45
47 private $lbFactory;
48
55 public function __construct(
56 IContextSource $context,
57 PageIdentity $page,
58 array $ids,
59 LBFactory $lbFactory
60 ) {
61 parent::__construct( $context, $page );
62
63 // ids is a protected variable in RevisionListBase
64 $this->ids = $ids;
65 $this->lbFactory = $lbFactory;
66 }
67
74 public static function getRelationType() {
75 return null;
76 }
77
84 public static function getRestriction() {
85 return null;
86 }
87
94 public static function getRevdelConstant() {
95 return null;
96 }
97
106 public static function suggestTarget( $target, array $ids ) {
107 return $target;
108 }
109
115 public function areAnySuppressed() {
117 foreach ( $this as $item ) {
118 if ( $item->getBits() & self::SUPPRESS_BIT ) {
119 return true;
120 }
121 }
122
123 return false;
124 }
125
138 public function setVisibility( array $params ) {
139 $status = Status::newGood();
140
141 $bitPars = $params['value'];
142 $comment = $params['comment'];
143 $perItemStatus = $params['perItemStatus'] ?? false;
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->getPrimaryDatabase();
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 if ( $perItemStatus ) {
170 $status->value['itemStatuses'] = [];
171 }
172
173 // For multi-item deletions, set the old/new bitfields in log_params such that "hid X"
174 // shows in logs if field X was hidden from ANY item and likewise for "unhid Y". Note the
175 // form does not let the same field get hidden and unhidden in different items at once.
176 $virtualOldBits = 0;
177 $virtualNewBits = 0;
178 $logType = 'delete';
179
180 // Will be filled with id => [old, new bits] information and
181 // passed to doPostCommitUpdates().
182 $visibilityChangeMap = [];
183
185 foreach ( $this as $item ) {
186 unset( $missing[$item->getId()] );
187
188 if ( $perItemStatus ) {
189 $itemStatus = Status::newGood();
190 $status->value['itemStatuses'][$item->getId()] = $itemStatus;
191 } else {
192 $itemStatus = $status;
193 }
194
195 $oldBits = $item->getBits();
196 // Build the actual new rev_deleted bitfield
197 $newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits );
198
199 if ( $oldBits == $newBits ) {
200 $itemStatus->warning(
201 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
202 $status->failCount++;
203 continue;
204 } elseif ( $oldBits == 0 && $newBits != 0 ) {
205 $opType = 'hide';
206 } elseif ( $oldBits != 0 && $newBits == 0 ) {
207 $opType = 'show';
208 } else {
209 $opType = 'modify';
210 }
211
212 if ( $item->isHideCurrentOp( $newBits ) ) {
213 // Cannot hide current version text
214 $itemStatus->error(
215 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
216 $status->failCount++;
217 continue;
218 } elseif ( !$item->canView() ) {
219 // Cannot access this revision
220 $msg = ( $opType == 'show' ) ?
221 'revdelete-show-no-access' : 'revdelete-modify-no-access';
222 $itemStatus->error( $msg, $item->formatDate(), $item->formatTime() );
223 $status->failCount++;
224 continue;
225 // Cannot just "hide from Sysops" without hiding any fields
226 } elseif ( $newBits == self::SUPPRESS_BIT ) {
227 $itemStatus->warning(
228 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
229 $status->failCount++;
230 continue;
231 }
232
233 // Update the revision
234 $ok = $item->setBits( $newBits );
235
236 if ( $ok ) {
237 $idsForLog[] = $item->getId();
238 // If any item field was suppressed or unsuppressed
239 if ( ( $oldBits | $newBits ) & self::SUPPRESS_BIT ) {
240 $logType = 'suppress';
241 }
242 // Track which fields where (un)hidden for each item
243 $addedBits = ( $oldBits ^ $newBits ) & $newBits;
244 $removedBits = ( $oldBits ^ $newBits ) & $oldBits;
245 $virtualNewBits |= $addedBits;
246 $virtualOldBits |= $removedBits;
247
248 $status->successCount++;
249 $authorActors[] = $item->getAuthorActor();
250
251 // Save the old and new bits in $visibilityChangeMap for
252 // later use.
253 $visibilityChangeMap[$item->getId()] = [
254 'oldBits' => $oldBits,
255 'newBits' => $newBits,
256 ];
257 } else {
258 $itemStatus->error(
259 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
260 $status->failCount++;
261 }
262 }
263
264 // Handle missing revisions
265 foreach ( $missing as $id => $unused ) {
266 if ( $perItemStatus ) {
267 $status->value['itemStatuses'][$id] = Status::newFatal( 'revdelete-modify-missing', $id );
268 } else {
269 $status->error( 'revdelete-modify-missing', $id );
270 }
271 $status->failCount++;
272 }
273
274 if ( $status->successCount == 0 ) {
275 $dbw->endAtomic( __METHOD__ );
276 return $status;
277 }
278
279 // Save success count
280 $successCount = $status->successCount;
281
282 // Move files, if there are any
283 $status->merge( $this->doPreCommitUpdates() );
284 if ( !$status->isOK() ) {
285 // Fatal error, such as no configured archive directory or I/O failures
286 $dbw->cancelAtomic( __METHOD__ );
287 return $status;
288 }
289
290 // Log it
291 $authorFields = [];
292 $authorFields['authorActors'] = $authorActors;
293 $this->updateLog(
294 $logType,
295 [
296 'page' => $this->page,
297 'count' => $successCount,
298 'newBits' => $virtualNewBits,
299 'oldBits' => $virtualOldBits,
300 'comment' => $comment,
301 'ids' => $idsForLog,
302 'tags' => $params['tags'] ?? [],
303 ] + $authorFields
304 );
305
306 // Clear caches after commit
307 DeferredUpdates::addCallableUpdate(
308 function () use ( $visibilityChangeMap ) {
309 $this->doPostCommitUpdates( $visibilityChangeMap );
310 },
311 DeferredUpdates::PRESEND,
312 $dbw
313 );
314
315 $dbw->endAtomic( __METHOD__ );
316
317 return $status;
318 }
319
320 final protected function acquireItemLocks() {
321 $status = Status::newGood();
323 foreach ( $this as $item ) {
324 $status->merge( $item->lock() );
325 }
326
327 return $status;
328 }
329
330 final protected function releaseItemLocks() {
331 $status = Status::newGood();
333 foreach ( $this as $item ) {
334 $status->merge( $item->unlock() );
335 }
336
337 return $status;
338 }
339
345 public function reloadFromPrimary() {
346 $dbw = $this->lbFactory->getPrimaryDatabase();
347 $this->res = $this->doQuery( $dbw );
348 }
349
362 private function updateLog( $logType, $params ) {
363 // Get the URL param's corresponding DB field
364 $field = RevisionDeleter::getRelationType( $this->getType() );
365 if ( !$field ) {
366 throw new UnexpectedValueException( "Bad log URL param type!" );
367 }
368 // Add params for affected page and ids
369 $logParams = $this->getLogParams( $params );
370 // Actually add the deletion log entry
371 $logEntry = new ManualLogEntry( $logType, $this->getLogAction() );
372 $logEntry->setTarget( $params['page'] );
373 $logEntry->setComment( $params['comment'] );
374 $logEntry->setParameters( $logParams );
375 $logEntry->setPerformer( $this->getUser() );
376 // Allow for easy searching of deletion log items for revision/log items
377 $relations = [
378 $field => $params['ids'],
379 ];
380 if ( isset( $params['authorActors'] ) ) {
381 $relations += [
382 'target_author_actor' => $params['authorActors'],
383 ];
384 }
385 $logEntry->setRelations( $relations );
386 // Apply change tags to the log entry
387 $logEntry->addTags( $params['tags'] );
388 $logId = $logEntry->insert();
389 $logEntry->publish( $logId );
390 }
391
396 public function getLogAction() {
397 return 'revision';
398 }
399
405 public function getLogParams( $params ) {
406 return [
407 '4::type' => $this->getType(),
408 '5::ids' => $params['ids'],
409 '6::ofield' => $params['oldBits'],
410 '7::nfield' => $params['newBits'],
411 ];
412 }
413
418 public function clearFileOps() {
419 }
420
426 public function doPreCommitUpdates() {
427 return Status::newGood();
428 }
429
436 public function doPostCommitUpdates( array $visibilityChangeMap ) {
437 return Status::newGood();
438 }
439
440}
getUser()
array $params
The job parameters.
Class for creating new log entries and inserting them into the database.
Defer callable updates to run later in the PHP process.
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.
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)
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.