r77677 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r77676‎ | r77677 | r77678 >
Date:19:56, 3 December 2010
Author:reedy
Status:ok (Comments)
Tags:
Comment:
Move includes/RevisionDelete.php to includes/revisiondelete/RevisionDelete.php
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/RevisionDelete.php (deleted) (history)
  • /trunk/phase3/includes/revisiondelete/RevisionDelete.php (added) (history)

Diff [purge]

Index: trunk/phase3/includes/RevisionDelete.php
@@ -1,1413 +0,0 @@
2 -<?php
3 -/**
4 - * Revision/log/file deletion backend
5 - *
6 - * @file
7 - */
8 -
9 -/**
10 - * Temporary b/c interface, collection of static functions.
11 - * @ingroup SpecialPage
12 - */
13 -class RevisionDeleter {
14 - /**
15 - * Checks for a change in the bitfield for a certain option and updates the
16 - * provided array accordingly.
17 - *
18 - * @param $desc String: description to add to the array if the option was
19 - * enabled / disabled.
20 - * @param $field Integer: the bitmask describing the single option.
21 - * @param $diff Integer: the xor of the old and new bitfields.
22 - * @param $new Integer: the new bitfield
23 - * @param $arr Array: the array to update.
24 - */
25 - protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
26 - if( $diff & $field ) {
27 - $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
28 - }
29 - }
30 -
31 - /**
32 - * Gets an array of message keys describing the changes made to the visibility
33 - * of the revision. If the resulting array is $arr, then $arr[0] will contain an
34 - * array of strings describing the items that were hidden, $arr[2] will contain
35 - * an array of strings describing the items that were unhidden, and $arr[3] will
36 - * contain an array with a single string, which can be one of "applied
37 - * restrictions to sysops", "removed restrictions from sysops", or null.
38 - *
39 - * @param $n Integer: the new bitfield.
40 - * @param $o Integer: the old bitfield.
41 - * @return An array as described above.
42 - */
43 - protected static function getChanges( $n, $o ) {
44 - $diff = $n ^ $o;
45 - $ret = array( 0 => array(), 1 => array(), 2 => array() );
46 - // Build bitfield changes in language
47 - self::checkItem( 'revdelete-content',
48 - Revision::DELETED_TEXT, $diff, $n, $ret );
49 - self::checkItem( 'revdelete-summary',
50 - Revision::DELETED_COMMENT, $diff, $n, $ret );
51 - self::checkItem( 'revdelete-uname',
52 - Revision::DELETED_USER, $diff, $n, $ret );
53 - // Restriction application to sysops
54 - if( $diff & Revision::DELETED_RESTRICTED ) {
55 - if( $n & Revision::DELETED_RESTRICTED )
56 - $ret[2][] = 'revdelete-restricted';
57 - else
58 - $ret[2][] = 'revdelete-unrestricted';
59 - }
60 - return $ret;
61 - }
62 -
63 - /**
64 - * Gets a log message to describe the given revision visibility change. This
65 - * message will be of the form "[hid {content, edit summary, username}];
66 - * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
67 - *
68 - * @param $count Integer: The number of effected revisions.
69 - * @param $nbitfield Integer: The new bitfield for the revision.
70 - * @param $obitfield Integer: The old bitfield for the revision.
71 - * @param $isForLog Boolean
72 - * @param $forContent Boolean
73 - */
74 - public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false, $forContent = false ) {
75 - global $wgLang, $wgContLang;
76 -
77 - $lang = $forContent ? $wgContLang : $wgLang;
78 - $msgFunc = $forContent ? "wfMsgForContent" : "wfMsg";
79 -
80 - $changes = self::getChanges( $nbitfield, $obitfield );
81 - array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
82 -
83 - $changesText = array();
84 -
85 - if( count( $changes[0] ) ) {
86 - $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
87 - }
88 - if( count( $changes[1] ) ) {
89 - $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
90 - }
91 -
92 - $s = $lang->semicolonList( $changesText );
93 - if( count( $changes[2] ) ) {
94 - $s .= $s ? ' (' . $changes[2][0] . ')' : ' ' . $changes[2][0];
95 - }
96 -
97 - $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
98 - return wfMsgExt( $msg, $forContent ? array( 'parsemag', 'content' ) : array( 'parsemag' ), $s, $lang->formatNum($count) );
99 - }
100 -
101 - private static function expandMessageArray(& $msg, $key, $forContent) {
102 - if ( is_array ($msg) ) {
103 - array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
104 - } else {
105 - if ( $forContent ) {
106 - $msg = wfMsgForContent($msg);
107 - } else {
108 - $msg = wfMsg($msg);
109 - }
110 - }
111 - }
112 -
113 - // Get DB field name for URL param...
114 - // Future code for other things may also track
115 - // other types of revision-specific changes.
116 - // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
117 - public static function getRelationType( $typeName ) {
118 - if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
119 - $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
120 - }
121 - if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
122 - $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
123 - $list = new $class( null, null, null );
124 - return $list->getIdField();
125 - } else {
126 - return null;
127 - }
128 - }
129 -
130 - // Checks if a revision still exists in the revision table.
131 - // If it doesn't, returns the corresponding ar_timestamp field
132 - // so that this key can be used instead.
133 - public static function checkRevisionExistence( $title, $revid ) {
134 - $dbr = wfGetDB( DB_SLAVE );
135 - $exists = $dbr->selectField( 'revision', '1',
136 - array( 'rev_id' => $revid ), __METHOD__ );
137 -
138 - if ( $exists ) {
139 - return true;
140 - }
141 -
142 - $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
143 - array( 'ar_namespace' => $title->getNamespace(),
144 - 'ar_title' => $title->getDBkey(),
145 - 'ar_rev_id' => $revid ), __METHOD__ );
146 -
147 - return $timestamp;
148 - }
149 -
150 - // Creates utility links for log entries.
151 - public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
152 - global $wgLang;
153 -
154 - if( count($paramArray) >= 2 ) {
155 - // Different revision types use different URL params...
156 - $originalKey = $key = $paramArray[0];
157 - // $paramArray[1] is a CSV of the IDs
158 - $Ids = explode( ',', $paramArray[1] );
159 -
160 - $revert = array();
161 -
162 - // For if undeleted revisions are found amidst deleted ones.
163 - $undeletedRevisions = array();
164 -
165 - // This is not going to work if some revs are deleted and some
166 - // aren't.
167 - if ($key == 'revision') {
168 - foreach( $Ids as $k => $id ) {
169 - $existResult =
170 - self::checkRevisionExistence( $title, $id );
171 -
172 - if ($existResult !== true) {
173 - $key = 'archive';
174 - $Ids[$k] = $existResult;
175 - } else {
176 - // Undeleted revision amidst deleted ones
177 - unset($Ids[$k]);
178 - $undeletedRevisions[] = $id;
179 - }
180 - }
181 -
182 - if ( $key == $originalKey ) {
183 - $Ids = $undeletedRevisions;
184 - $undeletedRevisions = array();
185 - }
186 - }
187 -
188 - // Diff link for single rev deletions
189 - if( count($Ids) == 1 && !count($undeletedRevisions) ) {
190 - // Live revision diffs...
191 - if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
192 - $revert[] = $skin->link(
193 - $title,
194 - $messages['diff'],
195 - array(),
196 - array(
197 - 'diff' => intval( $Ids[0] ),
198 - 'unhide' => 1
199 - ),
200 - array( 'known', 'noclasses' )
201 - );
202 - // Deleted revision diffs...
203 - } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
204 - $revert[] = $skin->link(
205 - SpecialPage::getTitleFor( 'Undelete' ),
206 - $messages['diff'],
207 - array(),
208 - array(
209 - 'target' => $title->getPrefixedDBKey(),
210 - 'diff' => 'prev',
211 - 'timestamp' => $Ids[0]
212 - ),
213 - array( 'known', 'noclasses' )
214 - );
215 - }
216 - }
217 -
218 - // View/modify link...
219 - if ( count($undeletedRevisions) ) {
220 - // FIXME THIS IS A HORRIBLE HORRIBLE HACK AND SHOULD DIE
221 - // It's not possible to pass a list of both deleted and
222 - // undeleted revisions to SpecialRevisionDelete, so we're
223 - // stuck with two links. See bug 23363.
224 - $restoreLinks = array();
225 -
226 - $restoreLinks[] = $skin->link(
227 - SpecialPage::getTitleFor( 'Revisiondelete' ),
228 - $messages['revdel-restore-visible'],
229 - array(),
230 - array(
231 - 'target' => $title->getPrefixedText(),
232 - 'type' => $originalKey,
233 - 'ids' => implode(',', $undeletedRevisions),
234 - ),
235 - array( 'known', 'noclasses' )
236 - );
237 -
238 - $restoreLinks[] = $skin->link(
239 - SpecialPage::getTitleFor( 'Revisiondelete' ),
240 - $messages['revdel-restore-deleted'],
241 - array(),
242 - array(
243 - 'target' => $title->getPrefixedText(),
244 - 'type' => $key,
245 - 'ids' => implode(',', $Ids),
246 - ),
247 - array( 'known', 'noclasses' )
248 - );
249 -
250 - $revert[] = $messages['revdel-restore'] . ' [' .
251 - $wgLang->pipeList( $restoreLinks ) . ']';
252 - } else {
253 - $revert[] = $skin->link(
254 - SpecialPage::getTitleFor( 'Revisiondelete' ),
255 - $messages['revdel-restore'],
256 - array(),
257 - array(
258 - 'target' => $title->getPrefixedText(),
259 - 'type' => $key,
260 - 'ids' => implode(',', $Ids),
261 - ),
262 - array( 'known', 'noclasses' )
263 - );
264 - }
265 -
266 - // Pipe links
267 - return wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
268 - }
269 - return '';
270 - }
271 -}
272 -
273 -/**
274 - * Abstract base class for a list of deletable items
275 - */
276 -abstract class RevDel_List {
277 - var $special, $title, $ids, $res, $current;
278 - var $type = null; // override this
279 - var $idField = null; // override this
280 - var $dateField = false; // override this
281 - var $authorIdField = false; // override this
282 - var $authorNameField = false; // override this
283 -
284 - /**
285 - * @param $special The parent SpecialPage
286 - * @param $title The target title
287 - * @param $ids Array of IDs
288 - */
289 - public function __construct( $special, $title, $ids ) {
290 - $this->special = $special;
291 - $this->title = $title;
292 - $this->ids = $ids;
293 - }
294 -
295 - /**
296 - * Get the internal type name of this list. Equal to the table name.
297 - */
298 - public function getType() {
299 - return $this->type;
300 - }
301 -
302 - /**
303 - * Get the DB field name associated with the ID list
304 - */
305 - public function getIdField() {
306 - return $this->idField;
307 - }
308 -
309 - /**
310 - * Get the DB field name storing timestamps
311 - */
312 - public function getTimestampField() {
313 - return $this->dateField;
314 - }
315 -
316 - /**
317 - * Get the DB field name storing user ids
318 - */
319 - public function getAuthorIdField() {
320 - return $this->authorIdField;
321 - }
322 -
323 - /**
324 - * Get the DB field name storing user names
325 - */
326 - public function getAuthorNameField() {
327 - return $this->authorNameField;
328 - }
329 - /**
330 - * Set the visibility for the revisions in this list. Logging and
331 - * transactions are done here.
332 - *
333 - * @param $params Associative array of parameters. Members are:
334 - * value: The integer value to set the visibility to
335 - * comment: The log comment.
336 - * @return Status
337 - */
338 - public function setVisibility( $params ) {
339 - $bitPars = $params['value'];
340 - $comment = $params['comment'];
341 -
342 - $this->res = false;
343 - $dbw = wfGetDB( DB_MASTER );
344 - $this->doQuery( $dbw );
345 - $dbw->begin();
346 - $status = Status::newGood();
347 - $missing = array_flip( $this->ids );
348 - $this->clearFileOps();
349 - $idsForLog = array();
350 - $authorIds = $authorIPs = array();
351 -
352 - for ( $this->reset(); $this->current(); $this->next() ) {
353 - $item = $this->current();
354 - unset( $missing[ $item->getId() ] );
355 -
356 - $oldBits = $item->getBits();
357 - // Build the actual new rev_deleted bitfield
358 - $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
359 -
360 - if ( $oldBits == $newBits ) {
361 - $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
362 - $status->failCount++;
363 - continue;
364 - } elseif ( $oldBits == 0 && $newBits != 0 ) {
365 - $opType = 'hide';
366 - } elseif ( $oldBits != 0 && $newBits == 0 ) {
367 - $opType = 'show';
368 - } else {
369 - $opType = 'modify';
370 - }
371 -
372 - if ( $item->isHideCurrentOp( $newBits ) ) {
373 - // Cannot hide current version text
374 - $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
375 - $status->failCount++;
376 - continue;
377 - }
378 - if ( !$item->canView() ) {
379 - // Cannot access this revision
380 - $msg = ($opType == 'show') ?
381 - 'revdelete-show-no-access' : 'revdelete-modify-no-access';
382 - $status->error( $msg, $item->formatDate(), $item->formatTime() );
383 - $status->failCount++;
384 - continue;
385 - }
386 - // Cannot just "hide from Sysops" without hiding any fields
387 - if( $newBits == Revision::DELETED_RESTRICTED ) {
388 - $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
389 - $status->failCount++;
390 - continue;
391 - }
392 -
393 - // Update the revision
394 - $ok = $item->setBits( $newBits );
395 -
396 - if ( $ok ) {
397 - $idsForLog[] = $item->getId();
398 - $status->successCount++;
399 - if( $item->getAuthorId() > 0 ) {
400 - $authorIds[] = $item->getAuthorId();
401 - } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
402 - $authorIPs[] = $item->getAuthorName();
403 - }
404 - } else {
405 - $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
406 - $status->failCount++;
407 - }
408 - }
409 -
410 - // Handle missing revisions
411 - foreach ( $missing as $id => $unused ) {
412 - $status->error( 'revdelete-modify-missing', $id );
413 - $status->failCount++;
414 - }
415 -
416 - if ( $status->successCount == 0 ) {
417 - $status->ok = false;
418 - $dbw->rollback();
419 - return $status;
420 - }
421 -
422 - // Save success count
423 - $successCount = $status->successCount;
424 -
425 - // Move files, if there are any
426 - $status->merge( $this->doPreCommitUpdates() );
427 - if ( !$status->isOK() ) {
428 - // Fatal error, such as no configured archive directory
429 - $dbw->rollback();
430 - return $status;
431 - }
432 -
433 - // Log it
434 - $this->updateLog( array(
435 - 'title' => $this->title,
436 - 'count' => $successCount,
437 - 'newBits' => $newBits,
438 - 'oldBits' => $oldBits,
439 - 'comment' => $comment,
440 - 'ids' => $idsForLog,
441 - 'authorIds' => $authorIds,
442 - 'authorIPs' => $authorIPs
443 - ) );
444 - $dbw->commit();
445 -
446 - // Clear caches
447 - $status->merge( $this->doPostCommitUpdates() );
448 - return $status;
449 - }
450 -
451 - /**
452 - * Reload the list data from the master DB. This can be done after setVisibility()
453 - * to allow $item->getHTML() to show the new data.
454 - */
455 - function reloadFromMaster() {
456 - $dbw = wfGetDB( DB_MASTER );
457 - $this->res = $this->doQuery( $dbw );
458 - }
459 -
460 - /**
461 - * Record a log entry on the action
462 - * @param $params Associative array of parameters:
463 - * newBits: The new value of the *_deleted bitfield
464 - * oldBits: The old value of the *_deleted bitfield.
465 - * title: The target title
466 - * ids: The ID list
467 - * comment: The log comment
468 - * authorsIds: The array of the user IDs of the offenders
469 - * authorsIPs: The array of the IP/anon user offenders
470 - */
471 - protected function updateLog( $params ) {
472 - // Get the URL param's corresponding DB field
473 - $field = RevisionDeleter::getRelationType( $this->getType() );
474 - if( !$field ) {
475 - throw new MWException( "Bad log URL param type!" );
476 - }
477 - // Put things hidden from sysops in the oversight log
478 - if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
479 - $logType = 'suppress';
480 - } else {
481 - $logType = 'delete';
482 - }
483 - // Add params for effected page and ids
484 - $logParams = $this->getLogParams( $params );
485 - // Actually add the deletion log entry
486 - $log = new LogPage( $logType );
487 - $logid = $log->addEntry( $this->getLogAction(), $params['title'],
488 - $params['comment'], $logParams );
489 - // Allow for easy searching of deletion log items for revision/log items
490 - $log->addRelations( $field, $params['ids'], $logid );
491 - $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
492 - $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
493 - }
494 -
495 - /**
496 - * Get the log action for this list type
497 - */
498 - public function getLogAction() {
499 - return 'revision';
500 - }
501 -
502 - /**
503 - * Get log parameter array.
504 - * @param $params Associative array of log parameters, same as updateLog()
505 - * @return array
506 - */
507 - public function getLogParams( $params ) {
508 - return array(
509 - $this->getType(),
510 - implode( ',', $params['ids'] ),
511 - "ofield={$params['oldBits']}",
512 - "nfield={$params['newBits']}"
513 - );
514 - }
515 -
516 - /**
517 - * Initialise the current iteration pointer
518 - */
519 - protected function initCurrent() {
520 - $row = $this->res->current();
521 - if ( $row ) {
522 - $this->current = $this->newItem( $row );
523 - } else {
524 - $this->current = false;
525 - }
526 - }
527 -
528 - /**
529 - * Start iteration. This must be called before current() or next().
530 - * @return First list item
531 - */
532 - public function reset() {
533 - if ( !$this->res ) {
534 - $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
535 - } else {
536 - $this->res->rewind();
537 - }
538 - $this->initCurrent();
539 - return $this->current;
540 - }
541 -
542 - /**
543 - * Get the current list item, or false if we are at the end
544 - */
545 - public function current() {
546 - return $this->current;
547 - }
548 -
549 - /**
550 - * Move the iteration pointer to the next list item, and return it.
551 - */
552 - public function next() {
553 - $this->res->next();
554 - $this->initCurrent();
555 - return $this->current;
556 - }
557 -
558 - /**
559 - * Get the number of items in the list.
560 - */
561 - public function length() {
562 - if( !$this->res ) {
563 - return 0;
564 - } else {
565 - return $this->res->numRows();
566 - }
567 - }
568 -
569 - /**
570 - * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
571 - * STUB
572 - */
573 - public function clearFileOps() {
574 - }
575 -
576 - /**
577 - * A hook for setVisibility(): do batch updates pre-commit.
578 - * STUB
579 - * @return Status
580 - */
581 - public function doPreCommitUpdates() {
582 - return Status::newGood();
583 - }
584 -
585 - /**
586 - * A hook for setVisibility(): do any necessary updates post-commit.
587 - * STUB
588 - * @return Status
589 - */
590 - public function doPostCommitUpdates() {
591 - return Status::newGood();
592 - }
593 -
594 - /**
595 - * Create an item object from a DB result row
596 - * @param $row stdclass
597 - */
598 - abstract public function newItem( $row );
599 -
600 - /**
601 - * Do the DB query to iterate through the objects.
602 - * @param $db Database object to use for the query
603 - */
604 - abstract public function doQuery( $db );
605 -
606 - /**
607 - * Get the integer value of the flag used for suppression
608 - */
609 - abstract public function getSuppressBit();
610 -}
611 -
612 -/**
613 - * Abstract base class for deletable items
614 - */
615 -abstract class RevDel_Item {
616 - /** The parent SpecialPage */
617 - var $special;
618 -
619 - /** The parent RevDel_List */
620 - var $list;
621 -
622 - /** The DB result row */
623 - var $row;
624 -
625 - /**
626 - * @param $list RevDel_List
627 - * @param $row DB result row
628 - */
629 - public function __construct( $list, $row ) {
630 - $this->special = $list->special;
631 - $this->list = $list;
632 - $this->row = $row;
633 - }
634 -
635 - /**
636 - * Get the ID, as it would appear in the ids URL parameter
637 - */
638 - public function getId() {
639 - $field = $this->list->getIdField();
640 - return $this->row->$field;
641 - }
642 -
643 - /**
644 - * Get the date, formatted with $wgLang
645 - */
646 - public function formatDate() {
647 - global $wgLang;
648 - return $wgLang->date( $this->getTimestamp() );
649 - }
650 -
651 - /**
652 - * Get the time, formatted with $wgLang
653 - */
654 - public function formatTime() {
655 - global $wgLang;
656 - return $wgLang->time( $this->getTimestamp() );
657 - }
658 -
659 - /**
660 - * Get the timestamp in MW 14-char form
661 - */
662 - public function getTimestamp() {
663 - $field = $this->list->getTimestampField();
664 - return wfTimestamp( TS_MW, $this->row->$field );
665 - }
666 -
667 - /**
668 - * Get the author user ID
669 - */
670 - public function getAuthorId() {
671 - $field = $this->list->getAuthorIdField();
672 - return intval( $this->row->$field );
673 - }
674 -
675 - /**
676 - * Get the author user name
677 - */
678 - public function getAuthorName() {
679 - $field = $this->list->getAuthorNameField();
680 - return strval( $this->row->$field );
681 - }
682 -
683 - /**
684 - * Returns true if the item is "current", and the operation to set the given
685 - * bits can't be executed for that reason
686 - * STUB
687 - */
688 - public function isHideCurrentOp( $newBits ) {
689 - return false;
690 - }
691 -
692 - /**
693 - * Returns true if the current user can view the item
694 - */
695 - abstract public function canView();
696 -
697 - /**
698 - * Returns true if the current user can view the item text/file
699 - */
700 - abstract public function canViewContent();
701 -
702 - /**
703 - * Get the current deletion bitfield value
704 - */
705 - abstract public function getBits();
706 -
707 - /**
708 - * Get the HTML of the list item. Should be include <li></li> tags.
709 - * This is used to show the list in HTML form, by the special page.
710 - */
711 - abstract public function getHTML();
712 -
713 - /**
714 - * Set the visibility of the item. This should do any necessary DB queries.
715 - *
716 - * The DB update query should have a condition which forces it to only update
717 - * if the value in the DB matches the value fetched earlier with the SELECT.
718 - * If the update fails because it did not match, the function should return
719 - * false. This prevents concurrency problems.
720 - *
721 - * @return boolean success
722 - */
723 - abstract public function setBits( $newBits );
724 -}
725 -
726 -/**
727 - * List for revision table items
728 - */
729 -class RevDel_RevisionList extends RevDel_List {
730 - var $currentRevId;
731 - var $type = 'revision';
732 - var $idField = 'rev_id';
733 - var $dateField = 'rev_timestamp';
734 - var $authorIdField = 'rev_user';
735 - var $authorNameField = 'rev_user_text';
736 -
737 - public function doQuery( $db ) {
738 - $ids = array_map( 'intval', $this->ids );
739 - return $db->select( array('revision','page'), '*',
740 - array(
741 - 'rev_page' => $this->title->getArticleID(),
742 - 'rev_id' => $ids,
743 - 'rev_page = page_id'
744 - ),
745 - __METHOD__,
746 - array( 'ORDER BY' => 'rev_id DESC' )
747 - );
748 - }
749 -
750 - public function newItem( $row ) {
751 - return new RevDel_RevisionItem( $this, $row );
752 - }
753 -
754 - public function getCurrent() {
755 - if ( is_null( $this->currentRevId ) ) {
756 - $dbw = wfGetDB( DB_MASTER );
757 - $this->currentRevId = $dbw->selectField(
758 - 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
759 - }
760 - return $this->currentRevId;
761 - }
762 -
763 - public function getSuppressBit() {
764 - return Revision::DELETED_RESTRICTED;
765 - }
766 -
767 - public function doPreCommitUpdates() {
768 - $this->title->invalidateCache();
769 - return Status::newGood();
770 - }
771 -
772 - public function doPostCommitUpdates() {
773 - $this->title->purgeSquid();
774 - // Extensions that require referencing previous revisions may need this
775 - wfRunHooks( 'ArticleRevisionVisibilitySet', array( &$this->title ) );
776 - return Status::newGood();
777 - }
778 -}
779 -
780 -/**
781 - * Item class for a revision table row
782 - */
783 -class RevDel_RevisionItem extends RevDel_Item {
784 - var $revision;
785 -
786 - public function __construct( $list, $row ) {
787 - parent::__construct( $list, $row );
788 - $this->revision = new Revision( $row );
789 - }
790 -
791 - public function canView() {
792 - return $this->revision->userCan( Revision::DELETED_RESTRICTED );
793 - }
794 -
795 - public function canViewContent() {
796 - return $this->revision->userCan( Revision::DELETED_TEXT );
797 - }
798 -
799 - public function getBits() {
800 - return $this->revision->mDeleted;
801 - }
802 -
803 - public function setBits( $bits ) {
804 - $dbw = wfGetDB( DB_MASTER );
805 - // Update revision table
806 - $dbw->update( 'revision',
807 - array( 'rev_deleted' => $bits ),
808 - array(
809 - 'rev_id' => $this->revision->getId(),
810 - 'rev_page' => $this->revision->getPage(),
811 - 'rev_deleted' => $this->getBits()
812 - ),
813 - __METHOD__
814 - );
815 - if ( !$dbw->affectedRows() ) {
816 - // Concurrent fail!
817 - return false;
818 - }
819 - // Update recentchanges table
820 - $dbw->update( 'recentchanges',
821 - array(
822 - 'rc_deleted' => $bits,
823 - 'rc_patrolled' => 1
824 - ),
825 - array(
826 - 'rc_this_oldid' => $this->revision->getId(), // condition
827 - // non-unique timestamp index
828 - 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
829 - ),
830 - __METHOD__
831 - );
832 - return true;
833 - }
834 -
835 - public function isDeleted() {
836 - return $this->revision->isDeleted( Revision::DELETED_TEXT );
837 - }
838 -
839 - public function isHideCurrentOp( $newBits ) {
840 - return ( $newBits & Revision::DELETED_TEXT )
841 - && $this->list->getCurrent() == $this->getId();
842 - }
843 -
844 - /**
845 - * Get the HTML link to the revision text.
846 - * Overridden by RevDel_ArchiveItem.
847 - */
848 - protected function getRevisionLink() {
849 - global $wgLang;
850 - $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
851 - if ( $this->isDeleted() && !$this->canViewContent() ) {
852 - return $date;
853 - }
854 - return $this->special->skin->link(
855 - $this->list->title,
856 - $date,
857 - array(),
858 - array(
859 - 'oldid' => $this->revision->getId(),
860 - 'unhide' => 1
861 - )
862 - );
863 - }
864 -
865 - /**
866 - * Get the HTML link to the diff.
867 - * Overridden by RevDel_ArchiveItem
868 - */
869 - protected function getDiffLink() {
870 - if ( $this->isDeleted() && !$this->canViewContent() ) {
871 - return wfMsgHtml('diff');
872 - } else {
873 - return
874 - $this->special->skin->link(
875 - $this->list->title,
876 - wfMsgHtml('diff'),
877 - array(),
878 - array(
879 - 'diff' => $this->revision->getId(),
880 - 'oldid' => 'prev',
881 - 'unhide' => 1
882 - ),
883 - array(
884 - 'known',
885 - 'noclasses'
886 - )
887 - );
888 - }
889 - }
890 -
891 - public function getHTML() {
892 - $difflink = $this->getDiffLink();
893 - $revlink = $this->getRevisionLink();
894 - $userlink = $this->special->skin->revUserLink( $this->revision );
895 - $comment = $this->special->skin->revComment( $this->revision );
896 - if ( $this->isDeleted() ) {
897 - $revlink = "<span class=\"history-deleted\">$revlink</span>";
898 - }
899 - return "<li>($difflink) $revlink $userlink $comment</li>";
900 - }
901 -}
902 -
903 -/**
904 - * List for archive table items, i.e. revisions deleted via action=delete
905 - */
906 -class RevDel_ArchiveList extends RevDel_RevisionList {
907 - var $type = 'archive';
908 - var $idField = 'ar_timestamp';
909 - var $dateField = 'ar_timestamp';
910 - var $authorIdField = 'ar_user';
911 - var $authorNameField = 'ar_user_text';
912 -
913 - public function doQuery( $db ) {
914 - $timestamps = array();
915 - foreach ( $this->ids as $id ) {
916 - $timestamps[] = $db->timestamp( $id );
917 - }
918 - return $db->select( 'archive', '*',
919 - array(
920 - 'ar_namespace' => $this->title->getNamespace(),
921 - 'ar_title' => $this->title->getDBkey(),
922 - 'ar_timestamp' => $timestamps
923 - ),
924 - __METHOD__,
925 - array( 'ORDER BY' => 'ar_timestamp DESC' )
926 - );
927 - }
928 -
929 - public function newItem( $row ) {
930 - return new RevDel_ArchiveItem( $this, $row );
931 - }
932 -
933 - public function doPreCommitUpdates() {
934 - return Status::newGood();
935 - }
936 -
937 - public function doPostCommitUpdates() {
938 - return Status::newGood();
939 - }
940 -}
941 -
942 -/**
943 - * Item class for a archive table row
944 - */
945 -class RevDel_ArchiveItem extends RevDel_RevisionItem {
946 - public function __construct( $list, $row ) {
947 - parent::__construct( $list, $row );
948 - $this->revision = Revision::newFromArchiveRow( $row,
949 - array( 'page' => $this->list->title->getArticleId() ) );
950 - }
951 -
952 - public function getId() {
953 - # Convert DB timestamp to MW timestamp
954 - return $this->revision->getTimestamp();
955 - }
956 -
957 - public function setBits( $bits ) {
958 - $dbw = wfGetDB( DB_MASTER );
959 - $dbw->update( 'archive',
960 - array( 'ar_deleted' => $bits ),
961 - array( 'ar_namespace' => $this->list->title->getNamespace(),
962 - 'ar_title' => $this->list->title->getDBkey(),
963 - // use timestamp for index
964 - 'ar_timestamp' => $this->row->ar_timestamp,
965 - 'ar_rev_id' => $this->row->ar_rev_id,
966 - 'ar_deleted' => $this->getBits()
967 - ),
968 - __METHOD__ );
969 - return (bool)$dbw->affectedRows();
970 - }
971 -
972 - protected function getRevisionLink() {
973 - global $wgLang;
974 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
975 - $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
976 - if ( $this->isDeleted() && !$this->canViewContent() ) {
977 - return $date;
978 - }
979 - return $this->special->skin->link( $undelete, $date, array(),
980 - array(
981 - 'target' => $this->list->title->getPrefixedText(),
982 - 'timestamp' => $this->revision->getTimestamp()
983 - ) );
984 - }
985 -
986 - protected function getDiffLink() {
987 - if ( $this->isDeleted() && !$this->canViewContent() ) {
988 - return wfMsgHtml( 'diff' );
989 - }
990 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
991 - return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
992 - array(
993 - 'target' => $this->list->title->getPrefixedText(),
994 - 'diff' => 'prev',
995 - 'timestamp' => $this->revision->getTimestamp()
996 - ) );
997 - }
998 -}
999 -
1000 -/**
1001 - * List for oldimage table items
1002 - */
1003 -class RevDel_FileList extends RevDel_List {
1004 - var $type = 'oldimage';
1005 - var $idField = 'oi_archive_name';
1006 - var $dateField = 'oi_timestamp';
1007 - var $authorIdField = 'oi_user';
1008 - var $authorNameField = 'oi_user_text';
1009 - var $storeBatch, $deleteBatch, $cleanupBatch;
1010 -
1011 - public function doQuery( $db ) {
1012 - $archiveNames = array();
1013 - foreach( $this->ids as $timestamp ) {
1014 - $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
1015 - }
1016 - return $db->select( 'oldimage', '*',
1017 - array(
1018 - 'oi_name' => $this->title->getDBkey(),
1019 - 'oi_archive_name' => $archiveNames
1020 - ),
1021 - __METHOD__,
1022 - array( 'ORDER BY' => 'oi_timestamp DESC' )
1023 - );
1024 - }
1025 -
1026 - public function newItem( $row ) {
1027 - return new RevDel_FileItem( $this, $row );
1028 - }
1029 -
1030 - public function clearFileOps() {
1031 - $this->deleteBatch = array();
1032 - $this->storeBatch = array();
1033 - $this->cleanupBatch = array();
1034 - }
1035 -
1036 - public function doPreCommitUpdates() {
1037 - $status = Status::newGood();
1038 - $repo = RepoGroup::singleton()->getLocalRepo();
1039 - if ( $this->storeBatch ) {
1040 - $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
1041 - }
1042 - if ( !$status->isOK() ) {
1043 - return $status;
1044 - }
1045 - if ( $this->deleteBatch ) {
1046 - $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
1047 - }
1048 - if ( !$status->isOK() ) {
1049 - // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
1050 - // modified (but destined for rollback) causes data loss
1051 - return $status;
1052 - }
1053 - if ( $this->cleanupBatch ) {
1054 - $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
1055 - }
1056 - return $status;
1057 - }
1058 -
1059 - public function doPostCommitUpdates() {
1060 - $file = wfLocalFile( $this->title );
1061 - $file->purgeCache();
1062 - $file->purgeDescription();
1063 - return Status::newGood();
1064 - }
1065 -
1066 - public function getSuppressBit() {
1067 - return File::DELETED_RESTRICTED;
1068 - }
1069 -}
1070 -
1071 -/**
1072 - * Item class for an oldimage table row
1073 - */
1074 -class RevDel_FileItem extends RevDel_Item {
1075 - var $file;
1076 -
1077 - public function __construct( $list, $row ) {
1078 - parent::__construct( $list, $row );
1079 - $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
1080 - }
1081 -
1082 - public function getId() {
1083 - $parts = explode( '!', $this->row->oi_archive_name );
1084 - return $parts[0];
1085 - }
1086 -
1087 - public function canView() {
1088 - return $this->file->userCan( File::DELETED_RESTRICTED );
1089 - }
1090 -
1091 - public function canViewContent() {
1092 - return $this->file->userCan( File::DELETED_FILE );
1093 - }
1094 -
1095 - public function getBits() {
1096 - return $this->file->getVisibility();
1097 - }
1098 -
1099 - public function setBits( $bits ) {
1100 - # Queue the file op
1101 - # FIXME: move to LocalFile.php
1102 - if ( $this->isDeleted() ) {
1103 - if ( $bits & File::DELETED_FILE ) {
1104 - # Still deleted
1105 - } else {
1106 - # Newly undeleted
1107 - $key = $this->file->getStorageKey();
1108 - $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
1109 - $this->list->storeBatch[] = array(
1110 - $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
1111 - 'public',
1112 - $this->file->getRel()
1113 - );
1114 - $this->list->cleanupBatch[] = $key;
1115 - }
1116 - } elseif ( $bits & File::DELETED_FILE ) {
1117 - # Newly deleted
1118 - $key = $this->file->getStorageKey();
1119 - $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
1120 - $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
1121 - }
1122 -
1123 - # Do the database operations
1124 - $dbw = wfGetDB( DB_MASTER );
1125 - $dbw->update( 'oldimage',
1126 - array( 'oi_deleted' => $bits ),
1127 - array(
1128 - 'oi_name' => $this->row->oi_name,
1129 - 'oi_timestamp' => $this->row->oi_timestamp,
1130 - 'oi_deleted' => $this->getBits()
1131 - ),
1132 - __METHOD__
1133 - );
1134 - return (bool)$dbw->affectedRows();
1135 - }
1136 -
1137 - public function isDeleted() {
1138 - return $this->file->isDeleted( File::DELETED_FILE );
1139 - }
1140 -
1141 - /**
1142 - * Get the link to the file.
1143 - * Overridden by RevDel_ArchivedFileItem.
1144 - */
1145 - protected function getLink() {
1146 - global $wgLang, $wgUser;
1147 - $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
1148 - if ( $this->isDeleted() ) {
1149 - # Hidden files...
1150 - if ( !$this->canViewContent() ) {
1151 - $link = $date;
1152 - } else {
1153 - $link = $this->special->skin->link(
1154 - $this->special->getTitle(),
1155 - $date, array(),
1156 - array(
1157 - 'target' => $this->list->title->getPrefixedText(),
1158 - 'file' => $this->file->getArchiveName(),
1159 - 'token' => $wgUser->editToken( $this->file->getArchiveName() )
1160 - )
1161 - );
1162 - }
1163 - return '<span class="history-deleted">' . $link . '</span>';
1164 - } else {
1165 - # Regular files...
1166 - return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
1167 - }
1168 - }
1169 - /**
1170 - * Generate a user tool link cluster if the current user is allowed to view it
1171 - * @return string HTML
1172 - */
1173 - protected function getUserTools() {
1174 - if( $this->file->userCan( Revision::DELETED_USER ) ) {
1175 - $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
1176 - $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
1177 - } else {
1178 - $link = wfMsgHtml( 'rev-deleted-user' );
1179 - }
1180 - if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
1181 - return '<span class="history-deleted">' . $link . '</span>';
1182 - }
1183 - return $link;
1184 - }
1185 -
1186 - /**
1187 - * Wrap and format the file's comment block, if the current
1188 - * user is allowed to view it.
1189 - *
1190 - * @return string HTML
1191 - */
1192 - protected function getComment() {
1193 - if( $this->file->userCan( File::DELETED_COMMENT ) ) {
1194 - $block = $this->special->skin->commentBlock( $this->file->description );
1195 - } else {
1196 - $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
1197 - }
1198 - if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
1199 - return "<span class=\"history-deleted\">$block</span>";
1200 - }
1201 - return $block;
1202 - }
1203 -
1204 - public function getHTML() {
1205 - global $wgLang;
1206 - $data =
1207 - wfMsg(
1208 - 'widthheight',
1209 - $wgLang->formatNum( $this->file->getWidth() ),
1210 - $wgLang->formatNum( $this->file->getHeight() )
1211 - ) .
1212 - ' (' .
1213 - wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
1214 - ')';
1215 -
1216 - return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
1217 - $data . ' ' . $this->getComment(). '</li>';
1218 - }
1219 -}
1220 -
1221 -/**
1222 - * List for filearchive table items
1223 - */
1224 -class RevDel_ArchivedFileList extends RevDel_FileList {
1225 - var $type = 'filearchive';
1226 - var $idField = 'fa_id';
1227 - var $dateField = 'fa_timestamp';
1228 - var $authorIdField = 'fa_user';
1229 - var $authorNameField = 'fa_user_text';
1230 -
1231 - public function doQuery( $db ) {
1232 - $ids = array_map( 'intval', $this->ids );
1233 - return $db->select( 'filearchive', '*',
1234 - array(
1235 - 'fa_name' => $this->title->getDBkey(),
1236 - 'fa_id' => $ids
1237 - ),
1238 - __METHOD__,
1239 - array( 'ORDER BY' => 'fa_id DESC' )
1240 - );
1241 - }
1242 -
1243 - public function newItem( $row ) {
1244 - return new RevDel_ArchivedFileItem( $this, $row );
1245 - }
1246 -}
1247 -
1248 -/**
1249 - * Item class for a filearchive table row
1250 - */
1251 -class RevDel_ArchivedFileItem extends RevDel_FileItem {
1252 - public function __construct( $list, $row ) {
1253 - parent::__construct( $list, $row );
1254 - $this->file = ArchivedFile::newFromRow( $row );
1255 - }
1256 -
1257 - public function getId() {
1258 - return $this->row->fa_id;
1259 - }
1260 -
1261 - public function setBits( $bits ) {
1262 - $dbw = wfGetDB( DB_MASTER );
1263 - $dbw->update( 'filearchive',
1264 - array( 'fa_deleted' => $bits ),
1265 - array(
1266 - 'fa_id' => $this->row->fa_id,
1267 - 'fa_deleted' => $this->getBits(),
1268 - ),
1269 - __METHOD__
1270 - );
1271 - return (bool)$dbw->affectedRows();
1272 - }
1273 -
1274 - protected function getLink() {
1275 - global $wgLang, $wgUser;
1276 - $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
1277 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
1278 - $key = $this->file->getKey();
1279 - # Hidden files...
1280 - if( !$this->canViewContent() ) {
1281 - $link = $date;
1282 - } else {
1283 - $link = $this->special->skin->link( $undelete, $date, array(),
1284 - array(
1285 - 'target' => $this->list->title->getPrefixedText(),
1286 - 'file' => $key,
1287 - 'token' => $wgUser->editToken( $key )
1288 - )
1289 - );
1290 - }
1291 - if( $this->isDeleted() ) {
1292 - $link = '<span class="history-deleted">' . $link . '</span>';
1293 - }
1294 - return $link;
1295 - }
1296 -}
1297 -
1298 -/**
1299 - * List for logging table items
1300 - */
1301 -class RevDel_LogList extends RevDel_List {
1302 - var $type = 'logging';
1303 - var $idField = 'log_id';
1304 - var $dateField = 'log_timestamp';
1305 - var $authorIdField = 'log_user';
1306 - var $authorNameField = 'log_user_text';
1307 -
1308 - public function doQuery( $db ) {
1309 - $ids = array_map( 'intval', $this->ids );
1310 - return $db->select( 'logging', '*',
1311 - array( 'log_id' => $ids ),
1312 - __METHOD__,
1313 - array( 'ORDER BY' => 'log_id DESC' )
1314 - );
1315 - }
1316 -
1317 - public function newItem( $row ) {
1318 - return new RevDel_LogItem( $this, $row );
1319 - }
1320 -
1321 - public function getSuppressBit() {
1322 - return Revision::DELETED_RESTRICTED;
1323 - }
1324 -
1325 - public function getLogAction() {
1326 - return 'event';
1327 - }
1328 -
1329 - public function getLogParams( $params ) {
1330 - return array(
1331 - implode( ',', $params['ids'] ),
1332 - "ofield={$params['oldBits']}",
1333 - "nfield={$params['newBits']}"
1334 - );
1335 - }
1336 -}
1337 -
1338 -/**
1339 - * Item class for a logging table row
1340 - */
1341 -class RevDel_LogItem extends RevDel_Item {
1342 - public function canView() {
1343 - return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
1344 - }
1345 -
1346 - public function canViewContent() {
1347 - return true; // none
1348 - }
1349 -
1350 - public function getBits() {
1351 - return $this->row->log_deleted;
1352 - }
1353 -
1354 - public function setBits( $bits ) {
1355 - $dbw = wfGetDB( DB_MASTER );
1356 - $dbw->update( 'recentchanges',
1357 - array(
1358 - 'rc_deleted' => $bits,
1359 - 'rc_patrolled' => 1
1360 - ),
1361 - array(
1362 - 'rc_logid' => $this->row->log_id,
1363 - 'rc_timestamp' => $this->row->log_timestamp // index
1364 - ),
1365 - __METHOD__
1366 - );
1367 - $dbw->update( 'logging',
1368 - array( 'log_deleted' => $bits ),
1369 - array(
1370 - 'log_id' => $this->row->log_id,
1371 - 'log_deleted' => $this->getBits()
1372 - ),
1373 - __METHOD__
1374 - );
1375 - return (bool)$dbw->affectedRows();
1376 - }
1377 -
1378 - public function getHTML() {
1379 - global $wgLang;
1380 -
1381 - $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
1382 - $paramArray = LogPage::extractParams( $this->row->log_params );
1383 - $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
1384 -
1385 - // Log link for this page
1386 - $loglink = $this->special->skin->link(
1387 - SpecialPage::getTitleFor( 'Log' ),
1388 - wfMsgHtml( 'log' ),
1389 - array(),
1390 - array( 'page' => $title->getPrefixedText() )
1391 - );
1392 - // Action text
1393 - if( !$this->canView() ) {
1394 - $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
1395 - } else {
1396 - $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
1397 - $this->special->skin, $paramArray, true, true );
1398 - if( $this->row->log_deleted & LogPage::DELETED_ACTION )
1399 - $action = '<span class="history-deleted">' . $action . '</span>';
1400 - }
1401 - // User links
1402 - $userLink = $this->special->skin->userLink( $this->row->log_user,
1403 - User::WhoIs( $this->row->log_user ) );
1404 - if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
1405 - $userLink = '<span class="history-deleted">' . $userLink . '</span>';
1406 - }
1407 - // Comment
1408 - $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
1409 - if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
1410 - $comment = '<span class="history-deleted">' . $comment . '</span>';
1411 - }
1412 - return "<li>($loglink) $date $userLink $action $comment</li>";
1413 - }
1414 -}
Index: trunk/phase3/includes/revisiondelete/RevisionDelete.php
@@ -0,0 +1,1413 @@
 2+<?php
 3+/**
 4+ * Revision/log/file deletion backend
 5+ *
 6+ * @file
 7+ */
 8+
 9+/**
 10+ * Temporary b/c interface, collection of static functions.
 11+ * @ingroup SpecialPage
 12+ */
 13+class RevisionDeleter {
 14+ /**
 15+ * Checks for a change in the bitfield for a certain option and updates the
 16+ * provided array accordingly.
 17+ *
 18+ * @param $desc String: description to add to the array if the option was
 19+ * enabled / disabled.
 20+ * @param $field Integer: the bitmask describing the single option.
 21+ * @param $diff Integer: the xor of the old and new bitfields.
 22+ * @param $new Integer: the new bitfield
 23+ * @param $arr Array: the array to update.
 24+ */
 25+ protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
 26+ if( $diff & $field ) {
 27+ $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
 28+ }
 29+ }
 30+
 31+ /**
 32+ * Gets an array of message keys describing the changes made to the visibility
 33+ * of the revision. If the resulting array is $arr, then $arr[0] will contain an
 34+ * array of strings describing the items that were hidden, $arr[2] will contain
 35+ * an array of strings describing the items that were unhidden, and $arr[3] will
 36+ * contain an array with a single string, which can be one of "applied
 37+ * restrictions to sysops", "removed restrictions from sysops", or null.
 38+ *
 39+ * @param $n Integer: the new bitfield.
 40+ * @param $o Integer: the old bitfield.
 41+ * @return An array as described above.
 42+ */
 43+ protected static function getChanges( $n, $o ) {
 44+ $diff = $n ^ $o;
 45+ $ret = array( 0 => array(), 1 => array(), 2 => array() );
 46+ // Build bitfield changes in language
 47+ self::checkItem( 'revdelete-content',
 48+ Revision::DELETED_TEXT, $diff, $n, $ret );
 49+ self::checkItem( 'revdelete-summary',
 50+ Revision::DELETED_COMMENT, $diff, $n, $ret );
 51+ self::checkItem( 'revdelete-uname',
 52+ Revision::DELETED_USER, $diff, $n, $ret );
 53+ // Restriction application to sysops
 54+ if( $diff & Revision::DELETED_RESTRICTED ) {
 55+ if( $n & Revision::DELETED_RESTRICTED )
 56+ $ret[2][] = 'revdelete-restricted';
 57+ else
 58+ $ret[2][] = 'revdelete-unrestricted';
 59+ }
 60+ return $ret;
 61+ }
 62+
 63+ /**
 64+ * Gets a log message to describe the given revision visibility change. This
 65+ * message will be of the form "[hid {content, edit summary, username}];
 66+ * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
 67+ *
 68+ * @param $count Integer: The number of effected revisions.
 69+ * @param $nbitfield Integer: The new bitfield for the revision.
 70+ * @param $obitfield Integer: The old bitfield for the revision.
 71+ * @param $isForLog Boolean
 72+ * @param $forContent Boolean
 73+ */
 74+ public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false, $forContent = false ) {
 75+ global $wgLang, $wgContLang;
 76+
 77+ $lang = $forContent ? $wgContLang : $wgLang;
 78+ $msgFunc = $forContent ? "wfMsgForContent" : "wfMsg";
 79+
 80+ $changes = self::getChanges( $nbitfield, $obitfield );
 81+ array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
 82+
 83+ $changesText = array();
 84+
 85+ if( count( $changes[0] ) ) {
 86+ $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
 87+ }
 88+ if( count( $changes[1] ) ) {
 89+ $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
 90+ }
 91+
 92+ $s = $lang->semicolonList( $changesText );
 93+ if( count( $changes[2] ) ) {
 94+ $s .= $s ? ' (' . $changes[2][0] . ')' : ' ' . $changes[2][0];
 95+ }
 96+
 97+ $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
 98+ return wfMsgExt( $msg, $forContent ? array( 'parsemag', 'content' ) : array( 'parsemag' ), $s, $lang->formatNum($count) );
 99+ }
 100+
 101+ private static function expandMessageArray(& $msg, $key, $forContent) {
 102+ if ( is_array ($msg) ) {
 103+ array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
 104+ } else {
 105+ if ( $forContent ) {
 106+ $msg = wfMsgForContent($msg);
 107+ } else {
 108+ $msg = wfMsg($msg);
 109+ }
 110+ }
 111+ }
 112+
 113+ // Get DB field name for URL param...
 114+ // Future code for other things may also track
 115+ // other types of revision-specific changes.
 116+ // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
 117+ public static function getRelationType( $typeName ) {
 118+ if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
 119+ $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
 120+ }
 121+ if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
 122+ $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
 123+ $list = new $class( null, null, null );
 124+ return $list->getIdField();
 125+ } else {
 126+ return null;
 127+ }
 128+ }
 129+
 130+ // Checks if a revision still exists in the revision table.
 131+ // If it doesn't, returns the corresponding ar_timestamp field
 132+ // so that this key can be used instead.
 133+ public static function checkRevisionExistence( $title, $revid ) {
 134+ $dbr = wfGetDB( DB_SLAVE );
 135+ $exists = $dbr->selectField( 'revision', '1',
 136+ array( 'rev_id' => $revid ), __METHOD__ );
 137+
 138+ if ( $exists ) {
 139+ return true;
 140+ }
 141+
 142+ $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
 143+ array( 'ar_namespace' => $title->getNamespace(),
 144+ 'ar_title' => $title->getDBkey(),
 145+ 'ar_rev_id' => $revid ), __METHOD__ );
 146+
 147+ return $timestamp;
 148+ }
 149+
 150+ // Creates utility links for log entries.
 151+ public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
 152+ global $wgLang;
 153+
 154+ if( count($paramArray) >= 2 ) {
 155+ // Different revision types use different URL params...
 156+ $originalKey = $key = $paramArray[0];
 157+ // $paramArray[1] is a CSV of the IDs
 158+ $Ids = explode( ',', $paramArray[1] );
 159+
 160+ $revert = array();
 161+
 162+ // For if undeleted revisions are found amidst deleted ones.
 163+ $undeletedRevisions = array();
 164+
 165+ // This is not going to work if some revs are deleted and some
 166+ // aren't.
 167+ if ($key == 'revision') {
 168+ foreach( $Ids as $k => $id ) {
 169+ $existResult =
 170+ self::checkRevisionExistence( $title, $id );
 171+
 172+ if ($existResult !== true) {
 173+ $key = 'archive';
 174+ $Ids[$k] = $existResult;
 175+ } else {
 176+ // Undeleted revision amidst deleted ones
 177+ unset($Ids[$k]);
 178+ $undeletedRevisions[] = $id;
 179+ }
 180+ }
 181+
 182+ if ( $key == $originalKey ) {
 183+ $Ids = $undeletedRevisions;
 184+ $undeletedRevisions = array();
 185+ }
 186+ }
 187+
 188+ // Diff link for single rev deletions
 189+ if( count($Ids) == 1 && !count($undeletedRevisions) ) {
 190+ // Live revision diffs...
 191+ if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
 192+ $revert[] = $skin->link(
 193+ $title,
 194+ $messages['diff'],
 195+ array(),
 196+ array(
 197+ 'diff' => intval( $Ids[0] ),
 198+ 'unhide' => 1
 199+ ),
 200+ array( 'known', 'noclasses' )
 201+ );
 202+ // Deleted revision diffs...
 203+ } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
 204+ $revert[] = $skin->link(
 205+ SpecialPage::getTitleFor( 'Undelete' ),
 206+ $messages['diff'],
 207+ array(),
 208+ array(
 209+ 'target' => $title->getPrefixedDBKey(),
 210+ 'diff' => 'prev',
 211+ 'timestamp' => $Ids[0]
 212+ ),
 213+ array( 'known', 'noclasses' )
 214+ );
 215+ }
 216+ }
 217+
 218+ // View/modify link...
 219+ if ( count($undeletedRevisions) ) {
 220+ // FIXME THIS IS A HORRIBLE HORRIBLE HACK AND SHOULD DIE
 221+ // It's not possible to pass a list of both deleted and
 222+ // undeleted revisions to SpecialRevisionDelete, so we're
 223+ // stuck with two links. See bug 23363.
 224+ $restoreLinks = array();
 225+
 226+ $restoreLinks[] = $skin->link(
 227+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 228+ $messages['revdel-restore-visible'],
 229+ array(),
 230+ array(
 231+ 'target' => $title->getPrefixedText(),
 232+ 'type' => $originalKey,
 233+ 'ids' => implode(',', $undeletedRevisions),
 234+ ),
 235+ array( 'known', 'noclasses' )
 236+ );
 237+
 238+ $restoreLinks[] = $skin->link(
 239+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 240+ $messages['revdel-restore-deleted'],
 241+ array(),
 242+ array(
 243+ 'target' => $title->getPrefixedText(),
 244+ 'type' => $key,
 245+ 'ids' => implode(',', $Ids),
 246+ ),
 247+ array( 'known', 'noclasses' )
 248+ );
 249+
 250+ $revert[] = $messages['revdel-restore'] . ' [' .
 251+ $wgLang->pipeList( $restoreLinks ) . ']';
 252+ } else {
 253+ $revert[] = $skin->link(
 254+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 255+ $messages['revdel-restore'],
 256+ array(),
 257+ array(
 258+ 'target' => $title->getPrefixedText(),
 259+ 'type' => $key,
 260+ 'ids' => implode(',', $Ids),
 261+ ),
 262+ array( 'known', 'noclasses' )
 263+ );
 264+ }
 265+
 266+ // Pipe links
 267+ return wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
 268+ }
 269+ return '';
 270+ }
 271+}
 272+
 273+/**
 274+ * Abstract base class for a list of deletable items
 275+ */
 276+abstract class RevDel_List {
 277+ var $special, $title, $ids, $res, $current;
 278+ var $type = null; // override this
 279+ var $idField = null; // override this
 280+ var $dateField = false; // override this
 281+ var $authorIdField = false; // override this
 282+ var $authorNameField = false; // override this
 283+
 284+ /**
 285+ * @param $special The parent SpecialPage
 286+ * @param $title The target title
 287+ * @param $ids Array of IDs
 288+ */
 289+ public function __construct( $special, $title, $ids ) {
 290+ $this->special = $special;
 291+ $this->title = $title;
 292+ $this->ids = $ids;
 293+ }
 294+
 295+ /**
 296+ * Get the internal type name of this list. Equal to the table name.
 297+ */
 298+ public function getType() {
 299+ return $this->type;
 300+ }
 301+
 302+ /**
 303+ * Get the DB field name associated with the ID list
 304+ */
 305+ public function getIdField() {
 306+ return $this->idField;
 307+ }
 308+
 309+ /**
 310+ * Get the DB field name storing timestamps
 311+ */
 312+ public function getTimestampField() {
 313+ return $this->dateField;
 314+ }
 315+
 316+ /**
 317+ * Get the DB field name storing user ids
 318+ */
 319+ public function getAuthorIdField() {
 320+ return $this->authorIdField;
 321+ }
 322+
 323+ /**
 324+ * Get the DB field name storing user names
 325+ */
 326+ public function getAuthorNameField() {
 327+ return $this->authorNameField;
 328+ }
 329+ /**
 330+ * Set the visibility for the revisions in this list. Logging and
 331+ * transactions are done here.
 332+ *
 333+ * @param $params Associative array of parameters. Members are:
 334+ * value: The integer value to set the visibility to
 335+ * comment: The log comment.
 336+ * @return Status
 337+ */
 338+ public function setVisibility( $params ) {
 339+ $bitPars = $params['value'];
 340+ $comment = $params['comment'];
 341+
 342+ $this->res = false;
 343+ $dbw = wfGetDB( DB_MASTER );
 344+ $this->doQuery( $dbw );
 345+ $dbw->begin();
 346+ $status = Status::newGood();
 347+ $missing = array_flip( $this->ids );
 348+ $this->clearFileOps();
 349+ $idsForLog = array();
 350+ $authorIds = $authorIPs = array();
 351+
 352+ for ( $this->reset(); $this->current(); $this->next() ) {
 353+ $item = $this->current();
 354+ unset( $missing[ $item->getId() ] );
 355+
 356+ $oldBits = $item->getBits();
 357+ // Build the actual new rev_deleted bitfield
 358+ $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
 359+
 360+ if ( $oldBits == $newBits ) {
 361+ $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
 362+ $status->failCount++;
 363+ continue;
 364+ } elseif ( $oldBits == 0 && $newBits != 0 ) {
 365+ $opType = 'hide';
 366+ } elseif ( $oldBits != 0 && $newBits == 0 ) {
 367+ $opType = 'show';
 368+ } else {
 369+ $opType = 'modify';
 370+ }
 371+
 372+ if ( $item->isHideCurrentOp( $newBits ) ) {
 373+ // Cannot hide current version text
 374+ $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
 375+ $status->failCount++;
 376+ continue;
 377+ }
 378+ if ( !$item->canView() ) {
 379+ // Cannot access this revision
 380+ $msg = ($opType == 'show') ?
 381+ 'revdelete-show-no-access' : 'revdelete-modify-no-access';
 382+ $status->error( $msg, $item->formatDate(), $item->formatTime() );
 383+ $status->failCount++;
 384+ continue;
 385+ }
 386+ // Cannot just "hide from Sysops" without hiding any fields
 387+ if( $newBits == Revision::DELETED_RESTRICTED ) {
 388+ $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
 389+ $status->failCount++;
 390+ continue;
 391+ }
 392+
 393+ // Update the revision
 394+ $ok = $item->setBits( $newBits );
 395+
 396+ if ( $ok ) {
 397+ $idsForLog[] = $item->getId();
 398+ $status->successCount++;
 399+ if( $item->getAuthorId() > 0 ) {
 400+ $authorIds[] = $item->getAuthorId();
 401+ } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
 402+ $authorIPs[] = $item->getAuthorName();
 403+ }
 404+ } else {
 405+ $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
 406+ $status->failCount++;
 407+ }
 408+ }
 409+
 410+ // Handle missing revisions
 411+ foreach ( $missing as $id => $unused ) {
 412+ $status->error( 'revdelete-modify-missing', $id );
 413+ $status->failCount++;
 414+ }
 415+
 416+ if ( $status->successCount == 0 ) {
 417+ $status->ok = false;
 418+ $dbw->rollback();
 419+ return $status;
 420+ }
 421+
 422+ // Save success count
 423+ $successCount = $status->successCount;
 424+
 425+ // Move files, if there are any
 426+ $status->merge( $this->doPreCommitUpdates() );
 427+ if ( !$status->isOK() ) {
 428+ // Fatal error, such as no configured archive directory
 429+ $dbw->rollback();
 430+ return $status;
 431+ }
 432+
 433+ // Log it
 434+ $this->updateLog( array(
 435+ 'title' => $this->title,
 436+ 'count' => $successCount,
 437+ 'newBits' => $newBits,
 438+ 'oldBits' => $oldBits,
 439+ 'comment' => $comment,
 440+ 'ids' => $idsForLog,
 441+ 'authorIds' => $authorIds,
 442+ 'authorIPs' => $authorIPs
 443+ ) );
 444+ $dbw->commit();
 445+
 446+ // Clear caches
 447+ $status->merge( $this->doPostCommitUpdates() );
 448+ return $status;
 449+ }
 450+
 451+ /**
 452+ * Reload the list data from the master DB. This can be done after setVisibility()
 453+ * to allow $item->getHTML() to show the new data.
 454+ */
 455+ function reloadFromMaster() {
 456+ $dbw = wfGetDB( DB_MASTER );
 457+ $this->res = $this->doQuery( $dbw );
 458+ }
 459+
 460+ /**
 461+ * Record a log entry on the action
 462+ * @param $params Associative array of parameters:
 463+ * newBits: The new value of the *_deleted bitfield
 464+ * oldBits: The old value of the *_deleted bitfield.
 465+ * title: The target title
 466+ * ids: The ID list
 467+ * comment: The log comment
 468+ * authorsIds: The array of the user IDs of the offenders
 469+ * authorsIPs: The array of the IP/anon user offenders
 470+ */
 471+ protected function updateLog( $params ) {
 472+ // Get the URL param's corresponding DB field
 473+ $field = RevisionDeleter::getRelationType( $this->getType() );
 474+ if( !$field ) {
 475+ throw new MWException( "Bad log URL param type!" );
 476+ }
 477+ // Put things hidden from sysops in the oversight log
 478+ if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
 479+ $logType = 'suppress';
 480+ } else {
 481+ $logType = 'delete';
 482+ }
 483+ // Add params for effected page and ids
 484+ $logParams = $this->getLogParams( $params );
 485+ // Actually add the deletion log entry
 486+ $log = new LogPage( $logType );
 487+ $logid = $log->addEntry( $this->getLogAction(), $params['title'],
 488+ $params['comment'], $logParams );
 489+ // Allow for easy searching of deletion log items for revision/log items
 490+ $log->addRelations( $field, $params['ids'], $logid );
 491+ $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
 492+ $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
 493+ }
 494+
 495+ /**
 496+ * Get the log action for this list type
 497+ */
 498+ public function getLogAction() {
 499+ return 'revision';
 500+ }
 501+
 502+ /**
 503+ * Get log parameter array.
 504+ * @param $params Associative array of log parameters, same as updateLog()
 505+ * @return array
 506+ */
 507+ public function getLogParams( $params ) {
 508+ return array(
 509+ $this->getType(),
 510+ implode( ',', $params['ids'] ),
 511+ "ofield={$params['oldBits']}",
 512+ "nfield={$params['newBits']}"
 513+ );
 514+ }
 515+
 516+ /**
 517+ * Initialise the current iteration pointer
 518+ */
 519+ protected function initCurrent() {
 520+ $row = $this->res->current();
 521+ if ( $row ) {
 522+ $this->current = $this->newItem( $row );
 523+ } else {
 524+ $this->current = false;
 525+ }
 526+ }
 527+
 528+ /**
 529+ * Start iteration. This must be called before current() or next().
 530+ * @return First list item
 531+ */
 532+ public function reset() {
 533+ if ( !$this->res ) {
 534+ $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
 535+ } else {
 536+ $this->res->rewind();
 537+ }
 538+ $this->initCurrent();
 539+ return $this->current;
 540+ }
 541+
 542+ /**
 543+ * Get the current list item, or false if we are at the end
 544+ */
 545+ public function current() {
 546+ return $this->current;
 547+ }
 548+
 549+ /**
 550+ * Move the iteration pointer to the next list item, and return it.
 551+ */
 552+ public function next() {
 553+ $this->res->next();
 554+ $this->initCurrent();
 555+ return $this->current;
 556+ }
 557+
 558+ /**
 559+ * Get the number of items in the list.
 560+ */
 561+ public function length() {
 562+ if( !$this->res ) {
 563+ return 0;
 564+ } else {
 565+ return $this->res->numRows();
 566+ }
 567+ }
 568+
 569+ /**
 570+ * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
 571+ * STUB
 572+ */
 573+ public function clearFileOps() {
 574+ }
 575+
 576+ /**
 577+ * A hook for setVisibility(): do batch updates pre-commit.
 578+ * STUB
 579+ * @return Status
 580+ */
 581+ public function doPreCommitUpdates() {
 582+ return Status::newGood();
 583+ }
 584+
 585+ /**
 586+ * A hook for setVisibility(): do any necessary updates post-commit.
 587+ * STUB
 588+ * @return Status
 589+ */
 590+ public function doPostCommitUpdates() {
 591+ return Status::newGood();
 592+ }
 593+
 594+ /**
 595+ * Create an item object from a DB result row
 596+ * @param $row stdclass
 597+ */
 598+ abstract public function newItem( $row );
 599+
 600+ /**
 601+ * Do the DB query to iterate through the objects.
 602+ * @param $db Database object to use for the query
 603+ */
 604+ abstract public function doQuery( $db );
 605+
 606+ /**
 607+ * Get the integer value of the flag used for suppression
 608+ */
 609+ abstract public function getSuppressBit();
 610+}
 611+
 612+/**
 613+ * Abstract base class for deletable items
 614+ */
 615+abstract class RevDel_Item {
 616+ /** The parent SpecialPage */
 617+ var $special;
 618+
 619+ /** The parent RevDel_List */
 620+ var $list;
 621+
 622+ /** The DB result row */
 623+ var $row;
 624+
 625+ /**
 626+ * @param $list RevDel_List
 627+ * @param $row DB result row
 628+ */
 629+ public function __construct( $list, $row ) {
 630+ $this->special = $list->special;
 631+ $this->list = $list;
 632+ $this->row = $row;
 633+ }
 634+
 635+ /**
 636+ * Get the ID, as it would appear in the ids URL parameter
 637+ */
 638+ public function getId() {
 639+ $field = $this->list->getIdField();
 640+ return $this->row->$field;
 641+ }
 642+
 643+ /**
 644+ * Get the date, formatted with $wgLang
 645+ */
 646+ public function formatDate() {
 647+ global $wgLang;
 648+ return $wgLang->date( $this->getTimestamp() );
 649+ }
 650+
 651+ /**
 652+ * Get the time, formatted with $wgLang
 653+ */
 654+ public function formatTime() {
 655+ global $wgLang;
 656+ return $wgLang->time( $this->getTimestamp() );
 657+ }
 658+
 659+ /**
 660+ * Get the timestamp in MW 14-char form
 661+ */
 662+ public function getTimestamp() {
 663+ $field = $this->list->getTimestampField();
 664+ return wfTimestamp( TS_MW, $this->row->$field );
 665+ }
 666+
 667+ /**
 668+ * Get the author user ID
 669+ */
 670+ public function getAuthorId() {
 671+ $field = $this->list->getAuthorIdField();
 672+ return intval( $this->row->$field );
 673+ }
 674+
 675+ /**
 676+ * Get the author user name
 677+ */
 678+ public function getAuthorName() {
 679+ $field = $this->list->getAuthorNameField();
 680+ return strval( $this->row->$field );
 681+ }
 682+
 683+ /**
 684+ * Returns true if the item is "current", and the operation to set the given
 685+ * bits can't be executed for that reason
 686+ * STUB
 687+ */
 688+ public function isHideCurrentOp( $newBits ) {
 689+ return false;
 690+ }
 691+
 692+ /**
 693+ * Returns true if the current user can view the item
 694+ */
 695+ abstract public function canView();
 696+
 697+ /**
 698+ * Returns true if the current user can view the item text/file
 699+ */
 700+ abstract public function canViewContent();
 701+
 702+ /**
 703+ * Get the current deletion bitfield value
 704+ */
 705+ abstract public function getBits();
 706+
 707+ /**
 708+ * Get the HTML of the list item. Should be include <li></li> tags.
 709+ * This is used to show the list in HTML form, by the special page.
 710+ */
 711+ abstract public function getHTML();
 712+
 713+ /**
 714+ * Set the visibility of the item. This should do any necessary DB queries.
 715+ *
 716+ * The DB update query should have a condition which forces it to only update
 717+ * if the value in the DB matches the value fetched earlier with the SELECT.
 718+ * If the update fails because it did not match, the function should return
 719+ * false. This prevents concurrency problems.
 720+ *
 721+ * @return boolean success
 722+ */
 723+ abstract public function setBits( $newBits );
 724+}
 725+
 726+/**
 727+ * List for revision table items
 728+ */
 729+class RevDel_RevisionList extends RevDel_List {
 730+ var $currentRevId;
 731+ var $type = 'revision';
 732+ var $idField = 'rev_id';
 733+ var $dateField = 'rev_timestamp';
 734+ var $authorIdField = 'rev_user';
 735+ var $authorNameField = 'rev_user_text';
 736+
 737+ public function doQuery( $db ) {
 738+ $ids = array_map( 'intval', $this->ids );
 739+ return $db->select( array('revision','page'), '*',
 740+ array(
 741+ 'rev_page' => $this->title->getArticleID(),
 742+ 'rev_id' => $ids,
 743+ 'rev_page = page_id'
 744+ ),
 745+ __METHOD__,
 746+ array( 'ORDER BY' => 'rev_id DESC' )
 747+ );
 748+ }
 749+
 750+ public function newItem( $row ) {
 751+ return new RevDel_RevisionItem( $this, $row );
 752+ }
 753+
 754+ public function getCurrent() {
 755+ if ( is_null( $this->currentRevId ) ) {
 756+ $dbw = wfGetDB( DB_MASTER );
 757+ $this->currentRevId = $dbw->selectField(
 758+ 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
 759+ }
 760+ return $this->currentRevId;
 761+ }
 762+
 763+ public function getSuppressBit() {
 764+ return Revision::DELETED_RESTRICTED;
 765+ }
 766+
 767+ public function doPreCommitUpdates() {
 768+ $this->title->invalidateCache();
 769+ return Status::newGood();
 770+ }
 771+
 772+ public function doPostCommitUpdates() {
 773+ $this->title->purgeSquid();
 774+ // Extensions that require referencing previous revisions may need this
 775+ wfRunHooks( 'ArticleRevisionVisibilitySet', array( &$this->title ) );
 776+ return Status::newGood();
 777+ }
 778+}
 779+
 780+/**
 781+ * Item class for a revision table row
 782+ */
 783+class RevDel_RevisionItem extends RevDel_Item {
 784+ var $revision;
 785+
 786+ public function __construct( $list, $row ) {
 787+ parent::__construct( $list, $row );
 788+ $this->revision = new Revision( $row );
 789+ }
 790+
 791+ public function canView() {
 792+ return $this->revision->userCan( Revision::DELETED_RESTRICTED );
 793+ }
 794+
 795+ public function canViewContent() {
 796+ return $this->revision->userCan( Revision::DELETED_TEXT );
 797+ }
 798+
 799+ public function getBits() {
 800+ return $this->revision->mDeleted;
 801+ }
 802+
 803+ public function setBits( $bits ) {
 804+ $dbw = wfGetDB( DB_MASTER );
 805+ // Update revision table
 806+ $dbw->update( 'revision',
 807+ array( 'rev_deleted' => $bits ),
 808+ array(
 809+ 'rev_id' => $this->revision->getId(),
 810+ 'rev_page' => $this->revision->getPage(),
 811+ 'rev_deleted' => $this->getBits()
 812+ ),
 813+ __METHOD__
 814+ );
 815+ if ( !$dbw->affectedRows() ) {
 816+ // Concurrent fail!
 817+ return false;
 818+ }
 819+ // Update recentchanges table
 820+ $dbw->update( 'recentchanges',
 821+ array(
 822+ 'rc_deleted' => $bits,
 823+ 'rc_patrolled' => 1
 824+ ),
 825+ array(
 826+ 'rc_this_oldid' => $this->revision->getId(), // condition
 827+ // non-unique timestamp index
 828+ 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
 829+ ),
 830+ __METHOD__
 831+ );
 832+ return true;
 833+ }
 834+
 835+ public function isDeleted() {
 836+ return $this->revision->isDeleted( Revision::DELETED_TEXT );
 837+ }
 838+
 839+ public function isHideCurrentOp( $newBits ) {
 840+ return ( $newBits & Revision::DELETED_TEXT )
 841+ && $this->list->getCurrent() == $this->getId();
 842+ }
 843+
 844+ /**
 845+ * Get the HTML link to the revision text.
 846+ * Overridden by RevDel_ArchiveItem.
 847+ */
 848+ protected function getRevisionLink() {
 849+ global $wgLang;
 850+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
 851+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 852+ return $date;
 853+ }
 854+ return $this->special->skin->link(
 855+ $this->list->title,
 856+ $date,
 857+ array(),
 858+ array(
 859+ 'oldid' => $this->revision->getId(),
 860+ 'unhide' => 1
 861+ )
 862+ );
 863+ }
 864+
 865+ /**
 866+ * Get the HTML link to the diff.
 867+ * Overridden by RevDel_ArchiveItem
 868+ */
 869+ protected function getDiffLink() {
 870+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 871+ return wfMsgHtml('diff');
 872+ } else {
 873+ return
 874+ $this->special->skin->link(
 875+ $this->list->title,
 876+ wfMsgHtml('diff'),
 877+ array(),
 878+ array(
 879+ 'diff' => $this->revision->getId(),
 880+ 'oldid' => 'prev',
 881+ 'unhide' => 1
 882+ ),
 883+ array(
 884+ 'known',
 885+ 'noclasses'
 886+ )
 887+ );
 888+ }
 889+ }
 890+
 891+ public function getHTML() {
 892+ $difflink = $this->getDiffLink();
 893+ $revlink = $this->getRevisionLink();
 894+ $userlink = $this->special->skin->revUserLink( $this->revision );
 895+ $comment = $this->special->skin->revComment( $this->revision );
 896+ if ( $this->isDeleted() ) {
 897+ $revlink = "<span class=\"history-deleted\">$revlink</span>";
 898+ }
 899+ return "<li>($difflink) $revlink $userlink $comment</li>";
 900+ }
 901+}
 902+
 903+/**
 904+ * List for archive table items, i.e. revisions deleted via action=delete
 905+ */
 906+class RevDel_ArchiveList extends RevDel_RevisionList {
 907+ var $type = 'archive';
 908+ var $idField = 'ar_timestamp';
 909+ var $dateField = 'ar_timestamp';
 910+ var $authorIdField = 'ar_user';
 911+ var $authorNameField = 'ar_user_text';
 912+
 913+ public function doQuery( $db ) {
 914+ $timestamps = array();
 915+ foreach ( $this->ids as $id ) {
 916+ $timestamps[] = $db->timestamp( $id );
 917+ }
 918+ return $db->select( 'archive', '*',
 919+ array(
 920+ 'ar_namespace' => $this->title->getNamespace(),
 921+ 'ar_title' => $this->title->getDBkey(),
 922+ 'ar_timestamp' => $timestamps
 923+ ),
 924+ __METHOD__,
 925+ array( 'ORDER BY' => 'ar_timestamp DESC' )
 926+ );
 927+ }
 928+
 929+ public function newItem( $row ) {
 930+ return new RevDel_ArchiveItem( $this, $row );
 931+ }
 932+
 933+ public function doPreCommitUpdates() {
 934+ return Status::newGood();
 935+ }
 936+
 937+ public function doPostCommitUpdates() {
 938+ return Status::newGood();
 939+ }
 940+}
 941+
 942+/**
 943+ * Item class for a archive table row
 944+ */
 945+class RevDel_ArchiveItem extends RevDel_RevisionItem {
 946+ public function __construct( $list, $row ) {
 947+ parent::__construct( $list, $row );
 948+ $this->revision = Revision::newFromArchiveRow( $row,
 949+ array( 'page' => $this->list->title->getArticleId() ) );
 950+ }
 951+
 952+ public function getId() {
 953+ # Convert DB timestamp to MW timestamp
 954+ return $this->revision->getTimestamp();
 955+ }
 956+
 957+ public function setBits( $bits ) {
 958+ $dbw = wfGetDB( DB_MASTER );
 959+ $dbw->update( 'archive',
 960+ array( 'ar_deleted' => $bits ),
 961+ array( 'ar_namespace' => $this->list->title->getNamespace(),
 962+ 'ar_title' => $this->list->title->getDBkey(),
 963+ // use timestamp for index
 964+ 'ar_timestamp' => $this->row->ar_timestamp,
 965+ 'ar_rev_id' => $this->row->ar_rev_id,
 966+ 'ar_deleted' => $this->getBits()
 967+ ),
 968+ __METHOD__ );
 969+ return (bool)$dbw->affectedRows();
 970+ }
 971+
 972+ protected function getRevisionLink() {
 973+ global $wgLang;
 974+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 975+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
 976+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 977+ return $date;
 978+ }
 979+ return $this->special->skin->link( $undelete, $date, array(),
 980+ array(
 981+ 'target' => $this->list->title->getPrefixedText(),
 982+ 'timestamp' => $this->revision->getTimestamp()
 983+ ) );
 984+ }
 985+
 986+ protected function getDiffLink() {
 987+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 988+ return wfMsgHtml( 'diff' );
 989+ }
 990+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 991+ return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
 992+ array(
 993+ 'target' => $this->list->title->getPrefixedText(),
 994+ 'diff' => 'prev',
 995+ 'timestamp' => $this->revision->getTimestamp()
 996+ ) );
 997+ }
 998+}
 999+
 1000+/**
 1001+ * List for oldimage table items
 1002+ */
 1003+class RevDel_FileList extends RevDel_List {
 1004+ var $type = 'oldimage';
 1005+ var $idField = 'oi_archive_name';
 1006+ var $dateField = 'oi_timestamp';
 1007+ var $authorIdField = 'oi_user';
 1008+ var $authorNameField = 'oi_user_text';
 1009+ var $storeBatch, $deleteBatch, $cleanupBatch;
 1010+
 1011+ public function doQuery( $db ) {
 1012+ $archiveNames = array();
 1013+ foreach( $this->ids as $timestamp ) {
 1014+ $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
 1015+ }
 1016+ return $db->select( 'oldimage', '*',
 1017+ array(
 1018+ 'oi_name' => $this->title->getDBkey(),
 1019+ 'oi_archive_name' => $archiveNames
 1020+ ),
 1021+ __METHOD__,
 1022+ array( 'ORDER BY' => 'oi_timestamp DESC' )
 1023+ );
 1024+ }
 1025+
 1026+ public function newItem( $row ) {
 1027+ return new RevDel_FileItem( $this, $row );
 1028+ }
 1029+
 1030+ public function clearFileOps() {
 1031+ $this->deleteBatch = array();
 1032+ $this->storeBatch = array();
 1033+ $this->cleanupBatch = array();
 1034+ }
 1035+
 1036+ public function doPreCommitUpdates() {
 1037+ $status = Status::newGood();
 1038+ $repo = RepoGroup::singleton()->getLocalRepo();
 1039+ if ( $this->storeBatch ) {
 1040+ $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
 1041+ }
 1042+ if ( !$status->isOK() ) {
 1043+ return $status;
 1044+ }
 1045+ if ( $this->deleteBatch ) {
 1046+ $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
 1047+ }
 1048+ if ( !$status->isOK() ) {
 1049+ // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
 1050+ // modified (but destined for rollback) causes data loss
 1051+ return $status;
 1052+ }
 1053+ if ( $this->cleanupBatch ) {
 1054+ $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
 1055+ }
 1056+ return $status;
 1057+ }
 1058+
 1059+ public function doPostCommitUpdates() {
 1060+ $file = wfLocalFile( $this->title );
 1061+ $file->purgeCache();
 1062+ $file->purgeDescription();
 1063+ return Status::newGood();
 1064+ }
 1065+
 1066+ public function getSuppressBit() {
 1067+ return File::DELETED_RESTRICTED;
 1068+ }
 1069+}
 1070+
 1071+/**
 1072+ * Item class for an oldimage table row
 1073+ */
 1074+class RevDel_FileItem extends RevDel_Item {
 1075+ var $file;
 1076+
 1077+ public function __construct( $list, $row ) {
 1078+ parent::__construct( $list, $row );
 1079+ $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
 1080+ }
 1081+
 1082+ public function getId() {
 1083+ $parts = explode( '!', $this->row->oi_archive_name );
 1084+ return $parts[0];
 1085+ }
 1086+
 1087+ public function canView() {
 1088+ return $this->file->userCan( File::DELETED_RESTRICTED );
 1089+ }
 1090+
 1091+ public function canViewContent() {
 1092+ return $this->file->userCan( File::DELETED_FILE );
 1093+ }
 1094+
 1095+ public function getBits() {
 1096+ return $this->file->getVisibility();
 1097+ }
 1098+
 1099+ public function setBits( $bits ) {
 1100+ # Queue the file op
 1101+ # FIXME: move to LocalFile.php
 1102+ if ( $this->isDeleted() ) {
 1103+ if ( $bits & File::DELETED_FILE ) {
 1104+ # Still deleted
 1105+ } else {
 1106+ # Newly undeleted
 1107+ $key = $this->file->getStorageKey();
 1108+ $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
 1109+ $this->list->storeBatch[] = array(
 1110+ $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
 1111+ 'public',
 1112+ $this->file->getRel()
 1113+ );
 1114+ $this->list->cleanupBatch[] = $key;
 1115+ }
 1116+ } elseif ( $bits & File::DELETED_FILE ) {
 1117+ # Newly deleted
 1118+ $key = $this->file->getStorageKey();
 1119+ $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
 1120+ $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
 1121+ }
 1122+
 1123+ # Do the database operations
 1124+ $dbw = wfGetDB( DB_MASTER );
 1125+ $dbw->update( 'oldimage',
 1126+ array( 'oi_deleted' => $bits ),
 1127+ array(
 1128+ 'oi_name' => $this->row->oi_name,
 1129+ 'oi_timestamp' => $this->row->oi_timestamp,
 1130+ 'oi_deleted' => $this->getBits()
 1131+ ),
 1132+ __METHOD__
 1133+ );
 1134+ return (bool)$dbw->affectedRows();
 1135+ }
 1136+
 1137+ public function isDeleted() {
 1138+ return $this->file->isDeleted( File::DELETED_FILE );
 1139+ }
 1140+
 1141+ /**
 1142+ * Get the link to the file.
 1143+ * Overridden by RevDel_ArchivedFileItem.
 1144+ */
 1145+ protected function getLink() {
 1146+ global $wgLang, $wgUser;
 1147+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
 1148+ if ( $this->isDeleted() ) {
 1149+ # Hidden files...
 1150+ if ( !$this->canViewContent() ) {
 1151+ $link = $date;
 1152+ } else {
 1153+ $link = $this->special->skin->link(
 1154+ $this->special->getTitle(),
 1155+ $date, array(),
 1156+ array(
 1157+ 'target' => $this->list->title->getPrefixedText(),
 1158+ 'file' => $this->file->getArchiveName(),
 1159+ 'token' => $wgUser->editToken( $this->file->getArchiveName() )
 1160+ )
 1161+ );
 1162+ }
 1163+ return '<span class="history-deleted">' . $link . '</span>';
 1164+ } else {
 1165+ # Regular files...
 1166+ return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
 1167+ }
 1168+ }
 1169+ /**
 1170+ * Generate a user tool link cluster if the current user is allowed to view it
 1171+ * @return string HTML
 1172+ */
 1173+ protected function getUserTools() {
 1174+ if( $this->file->userCan( Revision::DELETED_USER ) ) {
 1175+ $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
 1176+ $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
 1177+ } else {
 1178+ $link = wfMsgHtml( 'rev-deleted-user' );
 1179+ }
 1180+ if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
 1181+ return '<span class="history-deleted">' . $link . '</span>';
 1182+ }
 1183+ return $link;
 1184+ }
 1185+
 1186+ /**
 1187+ * Wrap and format the file's comment block, if the current
 1188+ * user is allowed to view it.
 1189+ *
 1190+ * @return string HTML
 1191+ */
 1192+ protected function getComment() {
 1193+ if( $this->file->userCan( File::DELETED_COMMENT ) ) {
 1194+ $block = $this->special->skin->commentBlock( $this->file->description );
 1195+ } else {
 1196+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
 1197+ }
 1198+ if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
 1199+ return "<span class=\"history-deleted\">$block</span>";
 1200+ }
 1201+ return $block;
 1202+ }
 1203+
 1204+ public function getHTML() {
 1205+ global $wgLang;
 1206+ $data =
 1207+ wfMsg(
 1208+ 'widthheight',
 1209+ $wgLang->formatNum( $this->file->getWidth() ),
 1210+ $wgLang->formatNum( $this->file->getHeight() )
 1211+ ) .
 1212+ ' (' .
 1213+ wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
 1214+ ')';
 1215+
 1216+ return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
 1217+ $data . ' ' . $this->getComment(). '</li>';
 1218+ }
 1219+}
 1220+
 1221+/**
 1222+ * List for filearchive table items
 1223+ */
 1224+class RevDel_ArchivedFileList extends RevDel_FileList {
 1225+ var $type = 'filearchive';
 1226+ var $idField = 'fa_id';
 1227+ var $dateField = 'fa_timestamp';
 1228+ var $authorIdField = 'fa_user';
 1229+ var $authorNameField = 'fa_user_text';
 1230+
 1231+ public function doQuery( $db ) {
 1232+ $ids = array_map( 'intval', $this->ids );
 1233+ return $db->select( 'filearchive', '*',
 1234+ array(
 1235+ 'fa_name' => $this->title->getDBkey(),
 1236+ 'fa_id' => $ids
 1237+ ),
 1238+ __METHOD__,
 1239+ array( 'ORDER BY' => 'fa_id DESC' )
 1240+ );
 1241+ }
 1242+
 1243+ public function newItem( $row ) {
 1244+ return new RevDel_ArchivedFileItem( $this, $row );
 1245+ }
 1246+}
 1247+
 1248+/**
 1249+ * Item class for a filearchive table row
 1250+ */
 1251+class RevDel_ArchivedFileItem extends RevDel_FileItem {
 1252+ public function __construct( $list, $row ) {
 1253+ parent::__construct( $list, $row );
 1254+ $this->file = ArchivedFile::newFromRow( $row );
 1255+ }
 1256+
 1257+ public function getId() {
 1258+ return $this->row->fa_id;
 1259+ }
 1260+
 1261+ public function setBits( $bits ) {
 1262+ $dbw = wfGetDB( DB_MASTER );
 1263+ $dbw->update( 'filearchive',
 1264+ array( 'fa_deleted' => $bits ),
 1265+ array(
 1266+ 'fa_id' => $this->row->fa_id,
 1267+ 'fa_deleted' => $this->getBits(),
 1268+ ),
 1269+ __METHOD__
 1270+ );
 1271+ return (bool)$dbw->affectedRows();
 1272+ }
 1273+
 1274+ protected function getLink() {
 1275+ global $wgLang, $wgUser;
 1276+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
 1277+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 1278+ $key = $this->file->getKey();
 1279+ # Hidden files...
 1280+ if( !$this->canViewContent() ) {
 1281+ $link = $date;
 1282+ } else {
 1283+ $link = $this->special->skin->link( $undelete, $date, array(),
 1284+ array(
 1285+ 'target' => $this->list->title->getPrefixedText(),
 1286+ 'file' => $key,
 1287+ 'token' => $wgUser->editToken( $key )
 1288+ )
 1289+ );
 1290+ }
 1291+ if( $this->isDeleted() ) {
 1292+ $link = '<span class="history-deleted">' . $link . '</span>';
 1293+ }
 1294+ return $link;
 1295+ }
 1296+}
 1297+
 1298+/**
 1299+ * List for logging table items
 1300+ */
 1301+class RevDel_LogList extends RevDel_List {
 1302+ var $type = 'logging';
 1303+ var $idField = 'log_id';
 1304+ var $dateField = 'log_timestamp';
 1305+ var $authorIdField = 'log_user';
 1306+ var $authorNameField = 'log_user_text';
 1307+
 1308+ public function doQuery( $db ) {
 1309+ $ids = array_map( 'intval', $this->ids );
 1310+ return $db->select( 'logging', '*',
 1311+ array( 'log_id' => $ids ),
 1312+ __METHOD__,
 1313+ array( 'ORDER BY' => 'log_id DESC' )
 1314+ );
 1315+ }
 1316+
 1317+ public function newItem( $row ) {
 1318+ return new RevDel_LogItem( $this, $row );
 1319+ }
 1320+
 1321+ public function getSuppressBit() {
 1322+ return Revision::DELETED_RESTRICTED;
 1323+ }
 1324+
 1325+ public function getLogAction() {
 1326+ return 'event';
 1327+ }
 1328+
 1329+ public function getLogParams( $params ) {
 1330+ return array(
 1331+ implode( ',', $params['ids'] ),
 1332+ "ofield={$params['oldBits']}",
 1333+ "nfield={$params['newBits']}"
 1334+ );
 1335+ }
 1336+}
 1337+
 1338+/**
 1339+ * Item class for a logging table row
 1340+ */
 1341+class RevDel_LogItem extends RevDel_Item {
 1342+ public function canView() {
 1343+ return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
 1344+ }
 1345+
 1346+ public function canViewContent() {
 1347+ return true; // none
 1348+ }
 1349+
 1350+ public function getBits() {
 1351+ return $this->row->log_deleted;
 1352+ }
 1353+
 1354+ public function setBits( $bits ) {
 1355+ $dbw = wfGetDB( DB_MASTER );
 1356+ $dbw->update( 'recentchanges',
 1357+ array(
 1358+ 'rc_deleted' => $bits,
 1359+ 'rc_patrolled' => 1
 1360+ ),
 1361+ array(
 1362+ 'rc_logid' => $this->row->log_id,
 1363+ 'rc_timestamp' => $this->row->log_timestamp // index
 1364+ ),
 1365+ __METHOD__
 1366+ );
 1367+ $dbw->update( 'logging',
 1368+ array( 'log_deleted' => $bits ),
 1369+ array(
 1370+ 'log_id' => $this->row->log_id,
 1371+ 'log_deleted' => $this->getBits()
 1372+ ),
 1373+ __METHOD__
 1374+ );
 1375+ return (bool)$dbw->affectedRows();
 1376+ }
 1377+
 1378+ public function getHTML() {
 1379+ global $wgLang;
 1380+
 1381+ $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
 1382+ $paramArray = LogPage::extractParams( $this->row->log_params );
 1383+ $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
 1384+
 1385+ // Log link for this page
 1386+ $loglink = $this->special->skin->link(
 1387+ SpecialPage::getTitleFor( 'Log' ),
 1388+ wfMsgHtml( 'log' ),
 1389+ array(),
 1390+ array( 'page' => $title->getPrefixedText() )
 1391+ );
 1392+ // Action text
 1393+ if( !$this->canView() ) {
 1394+ $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
 1395+ } else {
 1396+ $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
 1397+ $this->special->skin, $paramArray, true, true );
 1398+ if( $this->row->log_deleted & LogPage::DELETED_ACTION )
 1399+ $action = '<span class="history-deleted">' . $action . '</span>';
 1400+ }
 1401+ // User links
 1402+ $userLink = $this->special->skin->userLink( $this->row->log_user,
 1403+ User::WhoIs( $this->row->log_user ) );
 1404+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
 1405+ $userLink = '<span class="history-deleted">' . $userLink . '</span>';
 1406+ }
 1407+ // Comment
 1408+ $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
 1409+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
 1410+ $comment = '<span class="history-deleted">' . $comment . '</span>';
 1411+ }
 1412+ return "<li>($loglink) $date $userLink $action $comment</li>";
 1413+ }
 1414+}
Property changes on: trunk/phase3/includes/revisiondelete/RevisionDelete.php
___________________________________________________________________
Added: svn:eol-style
11415 + native
Added: svn:keywords
21416 + Author Date Id Revision
Index: trunk/phase3/includes/AutoLoader.php
@@ -210,7 +210,7 @@
211211 'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
212212 'ReverseChronologicalPager' => 'includes/Pager.php',
213213 'Revision' => 'includes/Revision.php',
214 - 'RevisionDelete' => 'includes/RevisionDelete.php',
 214+ 'RevisionDelete' => 'includes/revisiondelete/RevisionDelete.php',
215215 'RSSFeed' => 'includes/Feed.php',
216216 'Sanitizer' => 'includes/Sanitizer.php',
217217 'SiteConfiguration' => 'includes/SiteConfiguration.php',

Follow-up revisions

RevisionCommit summaryAuthorDate
r77678Followup r77677, fix rest of autoloader entriesreedy20:25, 3 December 2010
r77679Fixup 1 more autoloader from r77677...reedy20:30, 3 December 2010

Comments

#Comment by Catrope (talk | contribs)   23:56, 3 December 2010

If this is to be useful, RevisionDelete.php should be split out with one class per file or something.

Status & tagging log