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