MediaWiki fundraising/REL1_35
RevDelList.php
Go to the documentation of this file.
1<?php
24
36abstract class RevDelList extends RevisionListBase {
37 public function __construct( IContextSource $context, Title $title, array $ids ) {
38 parent::__construct( $context, $title );
39 $this->ids = $ids;
40 }
41
48 public static function getRelationType() {
49 return null;
50 }
51
58 public static function getRestriction() {
59 return null;
60 }
61
68 public static function getRevdelConstant() {
69 return null;
70 }
71
80 public static function suggestTarget( $target, array $ids ) {
81 return $target;
82 }
83
89 public function areAnySuppressed() {
90 $bit = $this->getSuppressBit();
91
93 foreach ( $this as $item ) {
94 if ( $item->getBits() & $bit ) {
95 return true;
96 }
97 }
98
99 return false;
100 }
101
114 public function setVisibility( array $params ) {
115 $status = Status::newGood();
116
117 $bitPars = $params['value'];
118 $comment = $params['comment'];
119 $perItemStatus = $params['perItemStatus'] ?? false;
120
121 // CAS-style checks are done on the _deleted fields so the select
122 // does not need to use FOR UPDATE nor be in the atomic section
123 $dbw = wfGetDB( DB_MASTER );
124 $this->res = $this->doQuery( $dbw );
125
126 $status->merge( $this->acquireItemLocks() );
127 if ( !$status->isGood() ) {
128 return $status;
129 }
130
131 $dbw->startAtomic( __METHOD__ );
132 $dbw->onTransactionResolution(
133 function () {
134 // Release locks on commit or error
135 $this->releaseItemLocks();
136 },
137 __METHOD__
138 );
139
140 $missing = array_flip( $this->ids );
141 $this->clearFileOps();
142 $idsForLog = [];
143 $authorActors = [];
144
145 if ( $perItemStatus ) {
146 $status->itemStatuses = [];
147 }
148
149 // For multi-item deletions, set the old/new bitfields in log_params such that "hid X"
150 // shows in logs if field X was hidden from ANY item and likewise for "unhid Y". Note the
151 // form does not let the same field get hidden and unhidden in different items at once.
152 $virtualOldBits = 0;
153 $virtualNewBits = 0;
154 $logType = 'delete';
155
156 // Will be filled with id => [old, new bits] information and
157 // passed to doPostCommitUpdates().
158 $visibilityChangeMap = [];
159
161 foreach ( $this as $item ) {
162 unset( $missing[$item->getId()] );
163
164 if ( $perItemStatus ) {
165 $itemStatus = Status::newGood();
166 $status->itemStatuses[$item->getId()] = $itemStatus;
167 } else {
168 $itemStatus = $status;
169 }
170
171 $oldBits = $item->getBits();
172 // Build the actual new rev_deleted bitfield
173 $newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits );
174
175 if ( $oldBits == $newBits ) {
176 $itemStatus->warning(
177 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
178 $status->failCount++;
179 continue;
180 } elseif ( $oldBits == 0 && $newBits != 0 ) {
181 $opType = 'hide';
182 } elseif ( $oldBits != 0 && $newBits == 0 ) {
183 $opType = 'show';
184 } else {
185 $opType = 'modify';
186 }
187
188 if ( $item->isHideCurrentOp( $newBits ) ) {
189 // Cannot hide current version text
190 $itemStatus->error(
191 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
192 $status->failCount++;
193 continue;
194 } elseif ( !$item->canView() ) {
195 // Cannot access this revision
196 $msg = ( $opType == 'show' ) ?
197 'revdelete-show-no-access' : 'revdelete-modify-no-access';
198 $itemStatus->error( $msg, $item->formatDate(), $item->formatTime() );
199 $status->failCount++;
200 continue;
201 // Cannot just "hide from Sysops" without hiding any fields
202 } elseif ( $newBits == RevisionRecord::DELETED_RESTRICTED ) {
203 $itemStatus->warning(
204 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
205 $status->failCount++;
206 continue;
207 }
208
209 // Update the revision
210 $ok = $item->setBits( $newBits );
211
212 if ( $ok ) {
213 $idsForLog[] = $item->getId();
214 // If any item field was suppressed or unsuppressed
215 if ( ( $oldBits | $newBits ) & $this->getSuppressBit() ) {
216 $logType = 'suppress';
217 }
218 // Track which fields where (un)hidden for each item
219 $addedBits = ( $oldBits ^ $newBits ) & $newBits;
220 $removedBits = ( $oldBits ^ $newBits ) & $oldBits;
221 $virtualNewBits |= $addedBits;
222 $virtualOldBits |= $removedBits;
223
224 $status->successCount++;
225 $authorActors[] = $item->getAuthorActor();
226
227 // Save the old and new bits in $visibilityChangeMap for
228 // later use.
229 $visibilityChangeMap[$item->getId()] = [
230 'oldBits' => $oldBits,
231 'newBits' => $newBits,
232 ];
233 } else {
234 $itemStatus->error(
235 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
236 $status->failCount++;
237 }
238 }
239
240 // Handle missing revisions
241 foreach ( $missing as $id => $unused ) {
242 if ( $perItemStatus ) {
243 $status->itemStatuses[$id] = Status::newFatal( 'revdelete-modify-missing', $id );
244 } else {
245 $status->error( 'revdelete-modify-missing', $id );
246 }
247 $status->failCount++;
248 }
249
250 if ( $status->successCount == 0 ) {
251 $dbw->endAtomic( __METHOD__ );
252 return $status;
253 }
254
255 // Save success count
256 $successCount = $status->successCount;
257
258 // Move files, if there are any
259 $status->merge( $this->doPreCommitUpdates() );
260 if ( !$status->isOK() ) {
261 // Fatal error, such as no configured archive directory or I/O failures
262 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
263 $lbFactory->rollbackMasterChanges( __METHOD__ );
264 return $status;
265 }
266
267 // Log it
268 $authorFields = [];
269 $authorFields['authorActors'] = $authorActors;
270 $this->updateLog(
271 $logType,
272 [
273 'title' => $this->title,
274 'count' => $successCount,
275 'newBits' => $virtualNewBits,
276 'oldBits' => $virtualOldBits,
277 'comment' => $comment,
278 'ids' => $idsForLog,
279 'tags' => $params['tags'] ?? [],
280 ] + $authorFields
281 );
282
283 // Clear caches after commit
284 DeferredUpdates::addCallableUpdate(
285 function () use ( $visibilityChangeMap ) {
286 $this->doPostCommitUpdates( $visibilityChangeMap );
287 },
288 DeferredUpdates::PRESEND,
289 $dbw
290 );
291
292 $dbw->endAtomic( __METHOD__ );
293
294 return $status;
295 }
296
297 final protected function acquireItemLocks() {
298 $status = Status::newGood();
300 foreach ( $this as $item ) {
301 $status->merge( $item->lock() );
302 }
303
304 return $status;
305 }
306
307 final protected function releaseItemLocks() {
308 $status = Status::newGood();
310 foreach ( $this as $item ) {
311 $status->merge( $item->unlock() );
312 }
313
314 return $status;
315 }
316
321 public function reloadFromMaster() {
322 $dbw = wfGetDB( DB_MASTER );
323 $this->res = $this->doQuery( $dbw );
324 }
325
339 private function updateLog( $logType, $params ) {
340 // Get the URL param's corresponding DB field
341 $field = RevisionDeleter::getRelationType( $this->getType() );
342 if ( !$field ) {
343 throw new MWException( "Bad log URL param type!" );
344 }
345 // Add params for affected page and ids
346 $logParams = $this->getLogParams( $params );
347 // Actually add the deletion log entry
348 $logEntry = new ManualLogEntry( $logType, $this->getLogAction() );
349 $logEntry->setTarget( $params['title'] );
350 $logEntry->setComment( $params['comment'] );
351 $logEntry->setParameters( $logParams );
352 $logEntry->setPerformer( $this->getUser() );
353 // Allow for easy searching of deletion log items for revision/log items
354 $relations = [
355 $field => $params['ids'],
356 ];
357 if ( isset( $params['authorActors'] ) ) {
358 $relations += [
359 'target_author_actor' => $params['authorActors'],
360 ];
361 }
362 $logEntry->setRelations( $relations );
363 // Apply change tags to the log entry
364 $logEntry->addTags( $params['tags'] );
365 $logId = $logEntry->insert();
366 $logEntry->publish( $logId );
367 }
368
373 public function getLogAction() {
374 return 'revision';
375 }
376
382 public function getLogParams( $params ) {
383 return [
384 '4::type' => $this->getType(),
385 '5::ids' => $params['ids'],
386 '6::ofield' => $params['oldBits'],
387 '7::nfield' => $params['newBits'],
388 ];
389 }
390
395 public function clearFileOps() {
396 }
397
403 public function doPreCommitUpdates() {
404 return Status::newGood();
405 }
406
413 public function doPostCommitUpdates( array $visibilityChangeMap ) {
414 return Status::newGood();
415 }
416
420 abstract public function getSuppressBit();
421}
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getUser()
Stable to override.
IContextSource $context
MediaWiki exception.
Class for creating new log entries and inserting them into the database.
MediaWikiServices is the service locator for the application scope of MediaWiki.
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.
__construct(IContextSource $context, Title $title, array $ids)
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.
updateLog( $logType, $params)
Record a log entry on the action.
reloadFromMaster()
Reload the list data from the master DB.
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.
Represents a title within MediaWiki.
Definition Title.php:42
Interface for objects which can provide a MediaWiki context on request.
const DB_MASTER
Definition defines.php:29