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