r66857 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r66856‎ | r66857 | r66858 >
Date:23:28, 24 May 2010
Author:reedy
Status:ok
Tags:
Comment:
Revert r66856, due to the complete rubbish commit summary (dupe of another revision)
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/RevisionDelete.php (deleted) (history)
  • /trunk/phase3/includes/specials/SpecialRevisiondelete.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/RevisionDelete.php
@@ -1,1407 +0,0 @@
2 -<?php
3 -/**
4 - * Temporary b/c interface, collection of static functions.
5 - * @ingroup SpecialPage
6 - */
7 -class RevisionDeleter {
8 - /**
9 - * Checks for a change in the bitfield for a certain option and updates the
10 - * provided array accordingly.
11 - *
12 - * @param $desc String: description to add to the array if the option was
13 - * enabled / disabled.
14 - * @param $field Integer: the bitmask describing the single option.
15 - * @param $diff Integer: the xor of the old and new bitfields.
16 - * @param $new Integer: the new bitfield
17 - * @param $arr Array: the array to update.
18 - */
19 - protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
20 - if( $diff & $field ) {
21 - $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
22 - }
23 - }
24 -
25 - /**
26 - * Gets an array of message keys describing the changes made to the visibility
27 - * of the revision. If the resulting array is $arr, then $arr[0] will contain an
28 - * array of strings describing the items that were hidden, $arr[2] will contain
29 - * an array of strings describing the items that were unhidden, and $arr[3] will
30 - * contain an array with a single string, which can be one of "applied
31 - * restrictions to sysops", "removed restrictions from sysops", or null.
32 - *
33 - * @param $n Integer: the new bitfield.
34 - * @param $o Integer: the old bitfield.
35 - * @return An array as described above.
36 - */
37 - protected static function getChanges( $n, $o ) {
38 - $diff = $n ^ $o;
39 - $ret = array( 0 => array(), 1 => array(), 2 => array() );
40 - // Build bitfield changes in language
41 - self::checkItem( 'revdelete-content',
42 - Revision::DELETED_TEXT, $diff, $n, $ret );
43 - self::checkItem( 'revdelete-summary',
44 - Revision::DELETED_COMMENT, $diff, $n, $ret );
45 - self::checkItem( 'revdelete-uname',
46 - Revision::DELETED_USER, $diff, $n, $ret );
47 - // Restriction application to sysops
48 - if( $diff & Revision::DELETED_RESTRICTED ) {
49 - if( $n & Revision::DELETED_RESTRICTED )
50 - $ret[2][] = 'revdelete-restricted';
51 - else
52 - $ret[2][] = 'revdelete-unrestricted';
53 - }
54 - return $ret;
55 - }
56 -
57 - /**
58 - * Gets a log message to describe the given revision visibility change. This
59 - * message will be of the form "[hid {content, edit summary, username}];
60 - * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
61 - *
62 - * @param $count Integer: The number of effected revisions.
63 - * @param $nbitfield Integer: The new bitfield for the revision.
64 - * @param $obitfield Integer: The old bitfield for the revision.
65 - * @param $isForLog Boolean
66 - * @param $forContent Boolean
67 - */
68 - public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false, $forContent = false ) {
69 - global $wgLang, $wgContLang;
70 -
71 - $lang = $forContent ? $wgContLang : $wgLang;
72 - $msgFunc = $forContent ? "wfMsgForContent" : "wfMsg";
73 -
74 - $s = '';
75 - $changes = self::getChanges( $nbitfield, $obitfield );
76 - array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
77 -
78 - $changesText = array();
79 -
80 - if( count( $changes[0] ) ) {
81 - $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
82 - }
83 - if( count( $changes[1] ) ) {
84 - $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
85 - }
86 -
87 - $s = $lang->semicolonList( $changesText );
88 - if( count( $changes[2] ) ) {
89 - $s .= $s ? ' (' . $changes[2][0] . ')' : ' ' . $changes[2][0];
90 - }
91 -
92 - $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
93 - return wfMsgExt( $msg, $forContent ? array( 'parsemag', 'content' ) : array( 'parsemag' ), $s, $lang->formatNum($count) );
94 - }
95 -
96 - private static function expandMessageArray(& $msg, $key, $forContent) {
97 - if ( is_array ($msg) ) {
98 - array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
99 - } else {
100 - if ( $forContent ) {
101 - $msg = wfMsgForContent($msg);
102 - } else {
103 - $msg = wfMsg($msg);
104 - }
105 - }
106 - }
107 -
108 - // Get DB field name for URL param...
109 - // Future code for other things may also track
110 - // other types of revision-specific changes.
111 - // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
112 - public static function getRelationType( $typeName ) {
113 - if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
114 - $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
115 - }
116 - if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
117 - $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
118 - $list = new $class( null, null, null );
119 - return $list->getIdField();
120 - } else {
121 - return null;
122 - }
123 - }
124 -
125 - // Checks if a revision still exists in the revision table.
126 - // If it doesn't, returns the corresponding ar_timestamp field
127 - // so that this key can be used instead.
128 - public static function checkRevisionExistence( $title, $revid ) {
129 - $dbr = wfGetDB( DB_SLAVE );
130 - $exists = $dbr->selectField( 'revision', '1',
131 - array( 'rev_id' => $revid ), __METHOD__ );
132 -
133 - if ( $exists ) {
134 - return true;
135 - }
136 -
137 - $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
138 - array( 'ar_namespace' => $title->getNamespace(),
139 - 'ar_title' => $title->getDBkey(),
140 - 'ar_rev_id' => $revid ), __METHOD__ );
141 -
142 - return $timestamp;
143 - }
144 -
145 - // Creates utility links for log entries.
146 - public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
147 - global $wgLang;
148 -
149 - if( count($paramArray) >= 2 ) {
150 - // Different revision types use different URL params...
151 - $originalKey = $key = $paramArray[0];
152 - // $paramArray[1] is a CSV of the IDs
153 - $Ids = explode( ',', $paramArray[1] );
154 - $query = $paramArray[1];
155 - $revert = array();
156 -
157 - // For if undeleted revisions are found amidst deleted ones.
158 - $undeletedRevisions = array();
159 -
160 - // This is not going to work if some revs are deleted and some
161 - // aren't.
162 - if ($key == 'revision') {
163 - foreach( $Ids as $k => $id ) {
164 - $existResult =
165 - self::checkRevisionExistence( $title, $id );
166 -
167 - if ($existResult !== true) {
168 - $key = 'archive';
169 - $Ids[$k] = $existResult;
170 - } elseif ($key != $originalKey) {
171 - // Undeleted revision amidst deleted ones
172 - unset($Ids[$k]);
173 - $undeletedRevisions[] = $id;
174 - }
175 - }
176 - }
177 -
178 - // Diff link for single rev deletions
179 - if( count($Ids) == 1 && !count($undeletedRevisions) ) {
180 - // Live revision diffs...
181 - if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
182 - $revert[] = $skin->link(
183 - $title,
184 - $messages['diff'],
185 - array(),
186 - array(
187 - 'diff' => intval( $Ids[0] ),
188 - 'unhide' => 1
189 - ),
190 - array( 'known', 'noclasses' )
191 - );
192 - // Deleted revision diffs...
193 - } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
194 - $revert[] = $skin->link(
195 - SpecialPage::getTitleFor( 'Undelete' ),
196 - $messages['diff'],
197 - array(),
198 - array(
199 - 'target' => $title->getPrefixedDBKey(),
200 - 'diff' => 'prev',
201 - 'timestamp' => $Ids[0]
202 - ),
203 - array( 'known', 'noclasses' )
204 - );
205 - }
206 - }
207 -
208 - // View/modify link...
209 - if ( count($undeletedRevisions) ) {
210 - // FIXME THIS IS A HORRIBLE HORRIBLE HACK AND SHOULD DIE
211 - // It's not possible to pass a list of both deleted and
212 - // undeleted revisions to SpecialRevisionDelete, so we're
213 - // stuck with two links. See bug 23363.
214 - $restoreLinks = array();
215 -
216 - $restoreLinks[] = $skin->link(
217 - SpecialPage::getTitleFor( 'Revisiondelete' ),
218 - $messages['revdel-restore-visible'],
219 - array(),
220 - array(
221 - 'target' => $title->getPrefixedText(),
222 - 'type' => $originalKey,
223 - 'ids' => implode(',', $undeletedRevisions),
224 - ),
225 - array( 'known', 'noclasses' )
226 - );
227 -
228 - $restoreLinks[] = $skin->link(
229 - SpecialPage::getTitleFor( 'Revisiondelete' ),
230 - $messages['revdel-restore-deleted'],
231 - array(),
232 - array(
233 - 'target' => $title->getPrefixedText(),
234 - 'type' => $key,
235 - 'ids' => implode(',', $Ids),
236 - ),
237 - array( 'known', 'noclasses' )
238 - );
239 -
240 - $revert[] = $messages['revdel-restore'] . ' [' .
241 - $wgLang->pipeList( $restoreLinks ) . ']';
242 - } else {
243 - $revert[] = $skin->link(
244 - SpecialPage::getTitleFor( 'Revisiondelete' ),
245 - $messages['revdel-restore'],
246 - array(),
247 - array(
248 - 'target' => $title->getPrefixedText(),
249 - 'type' => $key,
250 - 'ids' => implode(',', $Ids),
251 - ),
252 - array( 'known', 'noclasses' )
253 - );
254 - }
255 -
256 - // Pipe links
257 - $revert = wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
258 - }
259 - return $revert;
260 - }
261 -}
262 -
263 -/**
264 - * Abstract base class for a list of deletable items
265 - */
266 -abstract class RevDel_List {
267 - var $special, $title, $ids, $res, $current;
268 - var $type = null; // override this
269 - var $idField = null; // override this
270 - var $dateField = false; // override this
271 - var $authorIdField = false; // override this
272 - var $authorNameField = false; // override this
273 -
274 - /**
275 - * @param $special The parent SpecialPage
276 - * @param $title The target title
277 - * @param $ids Array of IDs
278 - */
279 - public function __construct( $special, $title, $ids ) {
280 - $this->special = $special;
281 - $this->title = $title;
282 - $this->ids = $ids;
283 - }
284 -
285 - /**
286 - * Get the internal type name of this list. Equal to the table name.
287 - */
288 - public function getType() {
289 - return $this->type;
290 - }
291 -
292 - /**
293 - * Get the DB field name associated with the ID list
294 - */
295 - public function getIdField() {
296 - return $this->idField;
297 - }
298 -
299 - /**
300 - * Get the DB field name storing timestamps
301 - */
302 - public function getTimestampField() {
303 - return $this->dateField;
304 - }
305 -
306 - /**
307 - * Get the DB field name storing user ids
308 - */
309 - public function getAuthorIdField() {
310 - return $this->authorIdField;
311 - }
312 -
313 - /**
314 - * Get the DB field name storing user names
315 - */
316 - public function getAuthorNameField() {
317 - return $this->authorNameField;
318 - }
319 - /**
320 - * Set the visibility for the revisions in this list. Logging and
321 - * transactions are done here.
322 - *
323 - * @param $params Associative array of parameters. Members are:
324 - * value: The integer value to set the visibility to
325 - * comment: The log comment.
326 - * @return Status
327 - */
328 - public function setVisibility( $params ) {
329 - $bitPars = $params['value'];
330 - $comment = $params['comment'];
331 -
332 - $this->res = false;
333 - $dbw = wfGetDB( DB_MASTER );
334 - $this->doQuery( $dbw );
335 - $dbw->begin();
336 - $status = Status::newGood();
337 - $missing = array_flip( $this->ids );
338 - $this->clearFileOps();
339 - $idsForLog = array();
340 - $authorIds = $authorIPs = array();
341 -
342 - for ( $this->reset(); $this->current(); $this->next() ) {
343 - $item = $this->current();
344 - unset( $missing[ $item->getId() ] );
345 -
346 - $oldBits = $item->getBits();
347 - // Build the actual new rev_deleted bitfield
348 - $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
349 -
350 - if ( $oldBits == $newBits ) {
351 - $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
352 - $status->failCount++;
353 - continue;
354 - } elseif ( $oldBits == 0 && $newBits != 0 ) {
355 - $opType = 'hide';
356 - } elseif ( $oldBits != 0 && $newBits == 0 ) {
357 - $opType = 'show';
358 - } else {
359 - $opType = 'modify';
360 - }
361 -
362 - if ( $item->isHideCurrentOp( $newBits ) ) {
363 - // Cannot hide current version text
364 - $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
365 - $status->failCount++;
366 - continue;
367 - }
368 - if ( !$item->canView() ) {
369 - // Cannot access this revision
370 - $msg = ($opType == 'show') ?
371 - 'revdelete-show-no-access' : 'revdelete-modify-no-access';
372 - $status->error( $msg, $item->formatDate(), $item->formatTime() );
373 - $status->failCount++;
374 - continue;
375 - }
376 - // Cannot just "hide from Sysops" without hiding any fields
377 - if( $newBits == Revision::DELETED_RESTRICTED ) {
378 - $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
379 - $status->failCount++;
380 - continue;
381 - }
382 -
383 - // Update the revision
384 - $ok = $item->setBits( $newBits );
385 -
386 - if ( $ok ) {
387 - $idsForLog[] = $item->getId();
388 - $status->successCount++;
389 - if( $item->getAuthorId() > 0 ) {
390 - $authorIds[] = $item->getAuthorId();
391 - } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
392 - $authorIPs[] = $item->getAuthorName();
393 - }
394 - } else {
395 - $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
396 - $status->failCount++;
397 - }
398 - }
399 -
400 - // Handle missing revisions
401 - foreach ( $missing as $id => $unused ) {
402 - $status->error( 'revdelete-modify-missing', $id );
403 - $status->failCount++;
404 - }
405 -
406 - if ( $status->successCount == 0 ) {
407 - $status->ok = false;
408 - $dbw->rollback();
409 - return $status;
410 - }
411 -
412 - // Save success count
413 - $successCount = $status->successCount;
414 -
415 - // Move files, if there are any
416 - $status->merge( $this->doPreCommitUpdates() );
417 - if ( !$status->isOK() ) {
418 - // Fatal error, such as no configured archive directory
419 - $dbw->rollback();
420 - return $status;
421 - }
422 -
423 - // Log it
424 - $this->updateLog( array(
425 - 'title' => $this->title,
426 - 'count' => $successCount,
427 - 'newBits' => $newBits,
428 - 'oldBits' => $oldBits,
429 - 'comment' => $comment,
430 - 'ids' => $idsForLog,
431 - 'authorIds' => $authorIds,
432 - 'authorIPs' => $authorIPs
433 - ) );
434 - $dbw->commit();
435 -
436 - // Clear caches
437 - $status->merge( $this->doPostCommitUpdates() );
438 - return $status;
439 - }
440 -
441 - /**
442 - * Reload the list data from the master DB. This can be done after setVisibility()
443 - * to allow $item->getHTML() to show the new data.
444 - */
445 - function reloadFromMaster() {
446 - $dbw = wfGetDB( DB_MASTER );
447 - $this->res = $this->doQuery( $dbw );
448 - }
449 -
450 - /**
451 - * Record a log entry on the action
452 - * @param $params Associative array of parameters:
453 - * newBits: The new value of the *_deleted bitfield
454 - * oldBits: The old value of the *_deleted bitfield.
455 - * title: The target title
456 - * ids: The ID list
457 - * comment: The log comment
458 - * authorsIds: The array of the user IDs of the offenders
459 - * authorsIPs: The array of the IP/anon user offenders
460 - */
461 - protected function updateLog( $params ) {
462 - // Get the URL param's corresponding DB field
463 - $field = RevisionDeleter::getRelationType( $this->getType() );
464 - if( !$field ) {
465 - throw new MWException( "Bad log URL param type!" );
466 - }
467 - // Put things hidden from sysops in the oversight log
468 - if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
469 - $logType = 'suppress';
470 - } else {
471 - $logType = 'delete';
472 - }
473 - // Add params for effected page and ids
474 - $logParams = $this->getLogParams( $params );
475 - // Actually add the deletion log entry
476 - $log = new LogPage( $logType );
477 - $logid = $log->addEntry( $this->getLogAction(), $params['title'],
478 - $params['comment'], $logParams );
479 - // Allow for easy searching of deletion log items for revision/log items
480 - $log->addRelations( $field, $params['ids'], $logid );
481 - $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
482 - $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
483 - }
484 -
485 - /**
486 - * Get the log action for this list type
487 - */
488 - public function getLogAction() {
489 - return 'revision';
490 - }
491 -
492 - /**
493 - * Get log parameter array.
494 - * @param $params Associative array of log parameters, same as updateLog()
495 - * @return array
496 - */
497 - public function getLogParams( $params ) {
498 - return array(
499 - $this->getType(),
500 - implode( ',', $params['ids'] ),
501 - "ofield={$params['oldBits']}",
502 - "nfield={$params['newBits']}"
503 - );
504 - }
505 -
506 - /**
507 - * Initialise the current iteration pointer
508 - */
509 - protected function initCurrent() {
510 - $row = $this->res->current();
511 - if ( $row ) {
512 - $this->current = $this->newItem( $row );
513 - } else {
514 - $this->current = false;
515 - }
516 - }
517 -
518 - /**
519 - * Start iteration. This must be called before current() or next().
520 - * @return First list item
521 - */
522 - public function reset() {
523 - if ( !$this->res ) {
524 - $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
525 - } else {
526 - $this->res->rewind();
527 - }
528 - $this->initCurrent();
529 - return $this->current;
530 - }
531 -
532 - /**
533 - * Get the current list item, or false if we are at the end
534 - */
535 - public function current() {
536 - return $this->current;
537 - }
538 -
539 - /**
540 - * Move the iteration pointer to the next list item, and return it.
541 - */
542 - public function next() {
543 - $this->res->next();
544 - $this->initCurrent();
545 - return $this->current;
546 - }
547 -
548 - /**
549 - * Get the number of items in the list.
550 - */
551 - public function length() {
552 - if( !$this->res ) {
553 - return 0;
554 - } else {
555 - return $this->res->numRows();
556 - }
557 - }
558 -
559 - /**
560 - * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
561 - * STUB
562 - */
563 - public function clearFileOps() {
564 - }
565 -
566 - /**
567 - * A hook for setVisibility(): do batch updates pre-commit.
568 - * STUB
569 - * @return Status
570 - */
571 - public function doPreCommitUpdates() {
572 - return Status::newGood();
573 - }
574 -
575 - /**
576 - * A hook for setVisibility(): do any necessary updates post-commit.
577 - * STUB
578 - * @return Status
579 - */
580 - public function doPostCommitUpdates() {
581 - return Status::newGood();
582 - }
583 -
584 - /**
585 - * Create an item object from a DB result row
586 - * @param $row stdclass
587 - */
588 - abstract public function newItem( $row );
589 -
590 - /**
591 - * Do the DB query to iterate through the objects.
592 - * @param $db Database object to use for the query
593 - */
594 - abstract public function doQuery( $db );
595 -
596 - /**
597 - * Get the integer value of the flag used for suppression
598 - */
599 - abstract public function getSuppressBit();
600 -}
601 -
602 -/**
603 - * Abstract base class for deletable items
604 - */
605 -abstract class RevDel_Item {
606 - /** The parent SpecialPage */
607 - var $special;
608 -
609 - /** The parent RevDel_List */
610 - var $list;
611 -
612 - /** The DB result row */
613 - var $row;
614 -
615 - /**
616 - * @param $list RevDel_List
617 - * @param $row DB result row
618 - */
619 - public function __construct( $list, $row ) {
620 - $this->special = $list->special;
621 - $this->list = $list;
622 - $this->row = $row;
623 - }
624 -
625 - /**
626 - * Get the ID, as it would appear in the ids URL parameter
627 - */
628 - public function getId() {
629 - $field = $this->list->getIdField();
630 - return $this->row->$field;
631 - }
632 -
633 - /**
634 - * Get the date, formatted with $wgLang
635 - */
636 - public function formatDate() {
637 - global $wgLang;
638 - return $wgLang->date( $this->getTimestamp() );
639 - }
640 -
641 - /**
642 - * Get the time, formatted with $wgLang
643 - */
644 - public function formatTime() {
645 - global $wgLang;
646 - return $wgLang->time( $this->getTimestamp() );
647 - }
648 -
649 - /**
650 - * Get the timestamp in MW 14-char form
651 - */
652 - public function getTimestamp() {
653 - $field = $this->list->getTimestampField();
654 - return wfTimestamp( TS_MW, $this->row->$field );
655 - }
656 -
657 - /**
658 - * Get the author user ID
659 - */
660 - public function getAuthorId() {
661 - $field = $this->list->getAuthorIdField();
662 - return intval( $this->row->$field );
663 - }
664 -
665 - /**
666 - * Get the author user name
667 - */
668 - public function getAuthorName() {
669 - $field = $this->list->getAuthorNameField();
670 - return strval( $this->row->$field );
671 - }
672 -
673 - /**
674 - * Returns true if the item is "current", and the operation to set the given
675 - * bits can't be executed for that reason
676 - * STUB
677 - */
678 - public function isHideCurrentOp( $newBits ) {
679 - return false;
680 - }
681 -
682 - /**
683 - * Returns true if the current user can view the item
684 - */
685 - abstract public function canView();
686 -
687 - /**
688 - * Returns true if the current user can view the item text/file
689 - */
690 - abstract public function canViewContent();
691 -
692 - /**
693 - * Get the current deletion bitfield value
694 - */
695 - abstract public function getBits();
696 -
697 - /**
698 - * Get the HTML of the list item. Should be include <li></li> tags.
699 - * This is used to show the list in HTML form, by the special page.
700 - */
701 - abstract public function getHTML();
702 -
703 - /**
704 - * Set the visibility of the item. This should do any necessary DB queries.
705 - *
706 - * The DB update query should have a condition which forces it to only update
707 - * if the value in the DB matches the value fetched earlier with the SELECT.
708 - * If the update fails because it did not match, the function should return
709 - * false. This prevents concurrency problems.
710 - *
711 - * @return boolean success
712 - */
713 - abstract public function setBits( $newBits );
714 -}
715 -
716 -/**
717 - * List for revision table items
718 - */
719 -class RevDel_RevisionList extends RevDel_List {
720 - var $currentRevId;
721 - var $type = 'revision';
722 - var $idField = 'rev_id';
723 - var $dateField = 'rev_timestamp';
724 - var $authorIdField = 'rev_user';
725 - var $authorNameField = 'rev_user_text';
726 -
727 - public function doQuery( $db ) {
728 - $ids = array_map( 'intval', $this->ids );
729 - return $db->select( array('revision','page'), '*',
730 - array(
731 - 'rev_page' => $this->title->getArticleID(),
732 - 'rev_id' => $ids,
733 - 'rev_page = page_id'
734 - ),
735 - __METHOD__,
736 - array( 'ORDER BY' => 'rev_id DESC' )
737 - );
738 - }
739 -
740 - public function newItem( $row ) {
741 - return new RevDel_RevisionItem( $this, $row );
742 - }
743 -
744 - public function getCurrent() {
745 - if ( is_null( $this->currentRevId ) ) {
746 - $dbw = wfGetDB( DB_MASTER );
747 - $this->currentRevId = $dbw->selectField(
748 - 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
749 - }
750 - return $this->currentRevId;
751 - }
752 -
753 - public function getSuppressBit() {
754 - return Revision::DELETED_RESTRICTED;
755 - }
756 -
757 - public function doPreCommitUpdates() {
758 - $this->title->invalidateCache();
759 - return Status::newGood();
760 - }
761 -
762 - public function doPostCommitUpdates() {
763 - $this->title->purgeSquid();
764 - // Extensions that require referencing previous revisions may need this
765 - wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title ) );
766 - return Status::newGood();
767 - }
768 -}
769 -
770 -/**
771 - * Item class for a revision table row
772 - */
773 -class RevDel_RevisionItem extends RevDel_Item {
774 - var $revision;
775 -
776 - public function __construct( $list, $row ) {
777 - parent::__construct( $list, $row );
778 - $this->revision = new Revision( $row );
779 - }
780 -
781 - public function canView() {
782 - return $this->revision->userCan( Revision::DELETED_RESTRICTED );
783 - }
784 -
785 - public function canViewContent() {
786 - return $this->revision->userCan( Revision::DELETED_TEXT );
787 - }
788 -
789 - public function getBits() {
790 - return $this->revision->mDeleted;
791 - }
792 -
793 - public function setBits( $bits ) {
794 - $dbw = wfGetDB( DB_MASTER );
795 - // Update revision table
796 - $dbw->update( 'revision',
797 - array( 'rev_deleted' => $bits ),
798 - array(
799 - 'rev_id' => $this->revision->getId(),
800 - 'rev_page' => $this->revision->getPage(),
801 - 'rev_deleted' => $this->getBits()
802 - ),
803 - __METHOD__
804 - );
805 - if ( !$dbw->affectedRows() ) {
806 - // Concurrent fail!
807 - return false;
808 - }
809 - // Update recentchanges table
810 - $dbw->update( 'recentchanges',
811 - array(
812 - 'rc_deleted' => $bits,
813 - 'rc_patrolled' => 1
814 - ),
815 - array(
816 - 'rc_this_oldid' => $this->revision->getId(), // condition
817 - // non-unique timestamp index
818 - 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
819 - ),
820 - __METHOD__
821 - );
822 - return true;
823 - }
824 -
825 - public function isDeleted() {
826 - return $this->revision->isDeleted( Revision::DELETED_TEXT );
827 - }
828 -
829 - public function isHideCurrentOp( $newBits ) {
830 - return ( $newBits & Revision::DELETED_TEXT )
831 - && $this->list->getCurrent() == $this->getId();
832 - }
833 -
834 - /**
835 - * Get the HTML link to the revision text.
836 - * Overridden by RevDel_ArchiveItem.
837 - */
838 - protected function getRevisionLink() {
839 - global $wgLang;
840 - $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
841 - if ( $this->isDeleted() && !$this->canViewContent() ) {
842 - return $date;
843 - }
844 - return $this->special->skin->link(
845 - $this->list->title,
846 - $date,
847 - array(),
848 - array(
849 - 'oldid' => $this->revision->getId(),
850 - 'unhide' => 1
851 - )
852 - );
853 - }
854 -
855 - /**
856 - * Get the HTML link to the diff.
857 - * Overridden by RevDel_ArchiveItem
858 - */
859 - protected function getDiffLink() {
860 - if ( $this->isDeleted() && !$this->canViewContent() ) {
861 - return wfMsgHtml('diff');
862 - } else {
863 - return
864 - $this->special->skin->link(
865 - $this->list->title,
866 - wfMsgHtml('diff'),
867 - array(),
868 - array(
869 - 'diff' => $this->revision->getId(),
870 - 'oldid' => 'prev',
871 - 'unhide' => 1
872 - ),
873 - array(
874 - 'known',
875 - 'noclasses'
876 - )
877 - );
878 - }
879 - }
880 -
881 - public function getHTML() {
882 - $difflink = $this->getDiffLink();
883 - $revlink = $this->getRevisionLink();
884 - $userlink = $this->special->skin->revUserLink( $this->revision );
885 - $comment = $this->special->skin->revComment( $this->revision );
886 - if ( $this->isDeleted() ) {
887 - $revlink = "<span class=\"history-deleted\">$revlink</span>";
888 - }
889 - return "<li>($difflink) $revlink $userlink $comment</li>";
890 - }
891 -}
892 -
893 -/**
894 - * List for archive table items, i.e. revisions deleted via action=delete
895 - */
896 -class RevDel_ArchiveList extends RevDel_RevisionList {
897 - var $type = 'archive';
898 - var $idField = 'ar_timestamp';
899 - var $dateField = 'ar_timestamp';
900 - var $authorIdField = 'ar_user';
901 - var $authorNameField = 'ar_user_text';
902 -
903 - public function doQuery( $db ) {
904 - $timestamps = array();
905 - foreach ( $this->ids as $id ) {
906 - $timestamps[] = $db->timestamp( $id );
907 - }
908 - return $db->select( 'archive', '*',
909 - array(
910 - 'ar_namespace' => $this->title->getNamespace(),
911 - 'ar_title' => $this->title->getDBkey(),
912 - 'ar_timestamp' => $timestamps
913 - ),
914 - __METHOD__,
915 - array( 'ORDER BY' => 'ar_timestamp DESC' )
916 - );
917 - }
918 -
919 - public function newItem( $row ) {
920 - return new RevDel_ArchiveItem( $this, $row );
921 - }
922 -
923 - public function doPreCommitUpdates() {
924 - return Status::newGood();
925 - }
926 -
927 - public function doPostCommitUpdates() {
928 - return Status::newGood();
929 - }
930 -}
931 -
932 -/**
933 - * Item class for a archive table row
934 - */
935 -class RevDel_ArchiveItem extends RevDel_RevisionItem {
936 - public function __construct( $list, $row ) {
937 - RevDel_Item::__construct( $list, $row );
938 - $this->revision = Revision::newFromArchiveRow( $row,
939 - array( 'page' => $this->list->title->getArticleId() ) );
940 - }
941 -
942 - public function getId() {
943 - # Convert DB timestamp to MW timestamp
944 - return $this->revision->getTimestamp();
945 - }
946 -
947 - public function setBits( $bits ) {
948 - $dbw = wfGetDB( DB_MASTER );
949 - $dbw->update( 'archive',
950 - array( 'ar_deleted' => $bits ),
951 - array( 'ar_namespace' => $this->list->title->getNamespace(),
952 - 'ar_title' => $this->list->title->getDBkey(),
953 - // use timestamp for index
954 - 'ar_timestamp' => $this->row->ar_timestamp,
955 - 'ar_rev_id' => $this->row->ar_rev_id,
956 - 'ar_deleted' => $this->getBits()
957 - ),
958 - __METHOD__ );
959 - return (bool)$dbw->affectedRows();
960 - }
961 -
962 - protected function getRevisionLink() {
963 - global $wgLang;
964 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
965 - $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
966 - if ( $this->isDeleted() && !$this->canViewContent() ) {
967 - return $date;
968 - }
969 - return $this->special->skin->link( $undelete, $date, array(),
970 - array(
971 - 'target' => $this->list->title->getPrefixedText(),
972 - 'timestamp' => $this->revision->getTimestamp()
973 - ) );
974 - }
975 -
976 - protected function getDiffLink() {
977 - if ( $this->isDeleted() && !$this->canViewContent() ) {
978 - return wfMsgHtml( 'diff' );
979 - }
980 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
981 - return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
982 - array(
983 - 'target' => $this->list->title->getPrefixedText(),
984 - 'diff' => 'prev',
985 - 'timestamp' => $this->revision->getTimestamp()
986 - ) );
987 - }
988 -}
989 -
990 -/**
991 - * List for oldimage table items
992 - */
993 -class RevDel_FileList extends RevDel_List {
994 - var $type = 'oldimage';
995 - var $idField = 'oi_archive_name';
996 - var $dateField = 'oi_timestamp';
997 - var $authorIdField = 'oi_user';
998 - var $authorNameField = 'oi_user_text';
999 - var $storeBatch, $deleteBatch, $cleanupBatch;
1000 -
1001 - public function doQuery( $db ) {
1002 - $archiveName = array();
1003 - foreach( $this->ids as $timestamp ) {
1004 - $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
1005 - }
1006 - return $db->select( 'oldimage', '*',
1007 - array(
1008 - 'oi_name' => $this->title->getDBkey(),
1009 - 'oi_archive_name' => $archiveNames
1010 - ),
1011 - __METHOD__,
1012 - array( 'ORDER BY' => 'oi_timestamp DESC' )
1013 - );
1014 - }
1015 -
1016 - public function newItem( $row ) {
1017 - return new RevDel_FileItem( $this, $row );
1018 - }
1019 -
1020 - public function clearFileOps() {
1021 - $this->deleteBatch = array();
1022 - $this->storeBatch = array();
1023 - $this->cleanupBatch = array();
1024 - }
1025 -
1026 - public function doPreCommitUpdates() {
1027 - $status = Status::newGood();
1028 - $repo = RepoGroup::singleton()->getLocalRepo();
1029 - if ( $this->storeBatch ) {
1030 - $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
1031 - }
1032 - if ( !$status->isOK() ) {
1033 - return $status;
1034 - }
1035 - if ( $this->deleteBatch ) {
1036 - $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
1037 - }
1038 - if ( !$status->isOK() ) {
1039 - // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
1040 - // modified (but destined for rollback) causes data loss
1041 - return $status;
1042 - }
1043 - if ( $this->cleanupBatch ) {
1044 - $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
1045 - }
1046 - return $status;
1047 - }
1048 -
1049 - public function doPostCommitUpdates() {
1050 - $file = wfLocalFile( $this->title );
1051 - $file->purgeCache();
1052 - $file->purgeDescription();
1053 - return Status::newGood();
1054 - }
1055 -
1056 - public function getSuppressBit() {
1057 - return File::DELETED_RESTRICTED;
1058 - }
1059 -}
1060 -
1061 -/**
1062 - * Item class for an oldimage table row
1063 - */
1064 -class RevDel_FileItem extends RevDel_Item {
1065 - var $file;
1066 -
1067 - public function __construct( $list, $row ) {
1068 - parent::__construct( $list, $row );
1069 - $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
1070 - }
1071 -
1072 - public function getId() {
1073 - $parts = explode( '!', $this->row->oi_archive_name );
1074 - return $parts[0];
1075 - }
1076 -
1077 - public function canView() {
1078 - return $this->file->userCan( File::DELETED_RESTRICTED );
1079 - }
1080 -
1081 - public function canViewContent() {
1082 - return $this->file->userCan( File::DELETED_FILE );
1083 - }
1084 -
1085 - public function getBits() {
1086 - return $this->file->getVisibility();
1087 - }
1088 -
1089 - public function setBits( $bits ) {
1090 - # Queue the file op
1091 - # FIXME: move to LocalFile.php
1092 - if ( $this->isDeleted() ) {
1093 - if ( $bits & File::DELETED_FILE ) {
1094 - # Still deleted
1095 - } else {
1096 - # Newly undeleted
1097 - $key = $this->file->getStorageKey();
1098 - $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
1099 - $this->list->storeBatch[] = array(
1100 - $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
1101 - 'public',
1102 - $this->file->getRel()
1103 - );
1104 - $this->list->cleanupBatch[] = $key;
1105 - }
1106 - } elseif ( $bits & File::DELETED_FILE ) {
1107 - # Newly deleted
1108 - $key = $this->file->getStorageKey();
1109 - $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
1110 - $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
1111 - }
1112 -
1113 - # Do the database operations
1114 - $dbw = wfGetDB( DB_MASTER );
1115 - $dbw->update( 'oldimage',
1116 - array( 'oi_deleted' => $bits ),
1117 - array(
1118 - 'oi_name' => $this->row->oi_name,
1119 - 'oi_timestamp' => $this->row->oi_timestamp,
1120 - 'oi_deleted' => $this->getBits()
1121 - ),
1122 - __METHOD__
1123 - );
1124 - return (bool)$dbw->affectedRows();
1125 - }
1126 -
1127 - public function isDeleted() {
1128 - return $this->file->isDeleted( File::DELETED_FILE );
1129 - }
1130 -
1131 - /**
1132 - * Get the link to the file.
1133 - * Overridden by RevDel_ArchivedFileItem.
1134 - */
1135 - protected function getLink() {
1136 - global $wgLang, $wgUser;
1137 - $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
1138 - if ( $this->isDeleted() ) {
1139 - # Hidden files...
1140 - if ( !$this->canViewContent() ) {
1141 - $link = $date;
1142 - } else {
1143 - $link = $this->special->skin->link(
1144 - $this->special->getTitle(),
1145 - $date, array(),
1146 - array(
1147 - 'target' => $this->list->title->getPrefixedText(),
1148 - 'file' => $this->file->getArchiveName(),
1149 - 'token' => $wgUser->editToken( $this->file->getArchiveName() )
1150 - )
1151 - );
1152 - }
1153 - return '<span class="history-deleted">' . $link . '</span>';
1154 - } else {
1155 - # Regular files...
1156 - $url = $this->file->getUrl();
1157 - return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
1158 - }
1159 - }
1160 - /**
1161 - * Generate a user tool link cluster if the current user is allowed to view it
1162 - * @return string HTML
1163 - */
1164 - protected function getUserTools() {
1165 - if( $this->file->userCan( Revision::DELETED_USER ) ) {
1166 - $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
1167 - $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
1168 - } else {
1169 - $link = wfMsgHtml( 'rev-deleted-user' );
1170 - }
1171 - if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
1172 - return '<span class="history-deleted">' . $link . '</span>';
1173 - }
1174 - return $link;
1175 - }
1176 -
1177 - /**
1178 - * Wrap and format the file's comment block, if the current
1179 - * user is allowed to view it.
1180 - *
1181 - * @return string HTML
1182 - */
1183 - protected function getComment() {
1184 - if( $this->file->userCan( File::DELETED_COMMENT ) ) {
1185 - $block = $this->special->skin->commentBlock( $this->file->description );
1186 - } else {
1187 - $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
1188 - }
1189 - if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
1190 - return "<span class=\"history-deleted\">$block</span>";
1191 - }
1192 - return $block;
1193 - }
1194 -
1195 - public function getHTML() {
1196 - global $wgLang;
1197 - $data =
1198 - wfMsg(
1199 - 'widthheight',
1200 - $wgLang->formatNum( $this->file->getWidth() ),
1201 - $wgLang->formatNum( $this->file->getHeight() )
1202 - ) .
1203 - ' (' .
1204 - wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
1205 - ')';
1206 - $pageLink = $this->getLink();
1207 -
1208 - return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
1209 - $data . ' ' . $this->getComment(). '</li>';
1210 - }
1211 -}
1212 -
1213 -/**
1214 - * List for filearchive table items
1215 - */
1216 -class RevDel_ArchivedFileList extends RevDel_FileList {
1217 - var $type = 'filearchive';
1218 - var $idField = 'fa_id';
1219 - var $dateField = 'fa_timestamp';
1220 - var $authorIdField = 'fa_user';
1221 - var $authorNameField = 'fa_user_text';
1222 -
1223 - public function doQuery( $db ) {
1224 - $ids = array_map( 'intval', $this->ids );
1225 - return $db->select( 'filearchive', '*',
1226 - array(
1227 - 'fa_name' => $this->title->getDBkey(),
1228 - 'fa_id' => $ids
1229 - ),
1230 - __METHOD__,
1231 - array( 'ORDER BY' => 'fa_id DESC' )
1232 - );
1233 - }
1234 -
1235 - public function newItem( $row ) {
1236 - return new RevDel_ArchivedFileItem( $this, $row );
1237 - }
1238 -}
1239 -
1240 -/**
1241 - * Item class for a filearchive table row
1242 - */
1243 -class RevDel_ArchivedFileItem extends RevDel_FileItem {
1244 - public function __construct( $list, $row ) {
1245 - RevDel_Item::__construct( $list, $row );
1246 - $this->file = ArchivedFile::newFromRow( $row );
1247 - }
1248 -
1249 - public function getId() {
1250 - return $this->row->fa_id;
1251 - }
1252 -
1253 - public function setBits( $bits ) {
1254 - $dbw = wfGetDB( DB_MASTER );
1255 - $dbw->update( 'filearchive',
1256 - array( 'fa_deleted' => $bits ),
1257 - array(
1258 - 'fa_id' => $this->row->fa_id,
1259 - 'fa_deleted' => $this->getBits(),
1260 - ),
1261 - __METHOD__
1262 - );
1263 - return (bool)$dbw->affectedRows();
1264 - }
1265 -
1266 - protected function getLink() {
1267 - global $wgLang, $wgUser;
1268 - $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
1269 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
1270 - $key = $this->file->getKey();
1271 - # Hidden files...
1272 - if( !$this->canViewContent() ) {
1273 - $link = $date;
1274 - } else {
1275 - $link = $this->special->skin->link( $undelete, $date, array(),
1276 - array(
1277 - 'target' => $this->list->title->getPrefixedText(),
1278 - 'file' => $key,
1279 - 'token' => $wgUser->editToken( $key )
1280 - )
1281 - );
1282 - }
1283 - if( $this->isDeleted() ) {
1284 - $link = '<span class="history-deleted">' . $link . '</span>';
1285 - }
1286 - return $link;
1287 - }
1288 -}
1289 -
1290 -/**
1291 - * List for logging table items
1292 - */
1293 -class RevDel_LogList extends RevDel_List {
1294 - var $type = 'logging';
1295 - var $idField = 'log_id';
1296 - var $dateField = 'log_timestamp';
1297 - var $authorIdField = 'log_user';
1298 - var $authorNameField = 'log_user_text';
1299 -
1300 - public function doQuery( $db ) {
1301 - global $wgMessageCache;
1302 - $wgMessageCache->loadAllMessages();
1303 - $ids = array_map( 'intval', $this->ids );
1304 - return $db->select( 'logging', '*',
1305 - array( 'log_id' => $ids ),
1306 - __METHOD__,
1307 - array( 'ORDER BY' => 'log_id DESC' )
1308 - );
1309 - }
1310 -
1311 - public function newItem( $row ) {
1312 - return new RevDel_LogItem( $this, $row );
1313 - }
1314 -
1315 - public function getSuppressBit() {
1316 - return Revision::DELETED_RESTRICTED;
1317 - }
1318 -
1319 - public function getLogAction() {
1320 - return 'event';
1321 - }
1322 -
1323 - public function getLogParams( $params ) {
1324 - return array(
1325 - implode( ',', $params['ids'] ),
1326 - "ofield={$params['oldBits']}",
1327 - "nfield={$params['newBits']}"
1328 - );
1329 - }
1330 -}
1331 -
1332 -/**
1333 - * Item class for a logging table row
1334 - */
1335 -class RevDel_LogItem extends RevDel_Item {
1336 - public function canView() {
1337 - return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
1338 - }
1339 -
1340 - public function canViewContent() {
1341 - return true; // none
1342 - }
1343 -
1344 - public function getBits() {
1345 - return $this->row->log_deleted;
1346 - }
1347 -
1348 - public function setBits( $bits ) {
1349 - $dbw = wfGetDB( DB_MASTER );
1350 - $dbw->update( 'recentchanges',
1351 - array(
1352 - 'rc_deleted' => $bits,
1353 - 'rc_patrolled' => 1
1354 - ),
1355 - array(
1356 - 'rc_logid' => $this->row->log_id,
1357 - 'rc_timestamp' => $this->row->log_timestamp // index
1358 - ),
1359 - __METHOD__
1360 - );
1361 - $dbw->update( 'logging',
1362 - array( 'log_deleted' => $bits ),
1363 - array(
1364 - 'log_id' => $this->row->log_id,
1365 - 'log_deleted' => $this->getBits()
1366 - ),
1367 - __METHOD__
1368 - );
1369 - return (bool)$dbw->affectedRows();
1370 - }
1371 -
1372 - public function getHTML() {
1373 - global $wgLang;
1374 -
1375 - $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
1376 - $paramArray = LogPage::extractParams( $this->row->log_params );
1377 - $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
1378 -
1379 - // Log link for this page
1380 - $loglink = $this->special->skin->link(
1381 - SpecialPage::getTitleFor( 'Log' ),
1382 - wfMsgHtml( 'log' ),
1383 - array(),
1384 - array( 'page' => $title->getPrefixedText() )
1385 - );
1386 - // Action text
1387 - if( !$this->canView() ) {
1388 - $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
1389 - } else {
1390 - $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
1391 - $this->special->skin, $paramArray, true, true );
1392 - if( $this->row->log_deleted & LogPage::DELETED_ACTION )
1393 - $action = '<span class="history-deleted">' . $action . '</span>';
1394 - }
1395 - // User links
1396 - $userLink = $this->special->skin->userLink( $this->row->log_user,
1397 - User::WhoIs( $this->row->log_user ) );
1398 - if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
1399 - $userLink = '<span class="history-deleted">' . $userLink . '</span>';
1400 - }
1401 - // Comment
1402 - $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
1403 - if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
1404 - $comment = '<span class="history-deleted">' . $comment . '</span>';
1405 - }
1406 - return "<li>($loglink) $date $userLink $action $comment</li>";
1407 - }
1408 -}
Index: trunk/phase3/includes/AutoLoader.php
@@ -200,7 +200,6 @@
201201 'Replacer' => 'includes/StringUtils.php',
202202 'ReverseChronologicalPager' => 'includes/Pager.php',
203203 'Revision' => 'includes/Revision.php',
204 - 'RevisionDelete' => 'includes/RevisionDelete.php',
205204 'RSSFeed' => 'includes/Feed.php',
206205 'Sanitizer' => 'includes/Sanitizer.php',
207206 'SiteConfiguration' => 'includes/SiteConfiguration.php',
Index: trunk/phase3/includes/specials/SpecialRevisiondelete.php
@@ -596,4 +596,1411 @@
597597 array( 'value' => $bitfield, 'comment' => $reason )
598598 );
599599 }
600 -}
\ No newline at end of file
 600+}
 601+
 602+/**
 603+ * Temporary b/c interface, collection of static functions.
 604+ * @ingroup SpecialPage
 605+ */
 606+class RevisionDeleter {
 607+ /**
 608+ * Checks for a change in the bitfield for a certain option and updates the
 609+ * provided array accordingly.
 610+ *
 611+ * @param $desc String: description to add to the array if the option was
 612+ * enabled / disabled.
 613+ * @param $field Integer: the bitmask describing the single option.
 614+ * @param $diff Integer: the xor of the old and new bitfields.
 615+ * @param $new Integer: the new bitfield
 616+ * @param $arr Array: the array to update.
 617+ */
 618+ protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
 619+ if( $diff & $field ) {
 620+ $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
 621+ }
 622+ }
 623+
 624+ /**
 625+ * Gets an array of message keys describing the changes made to the visibility
 626+ * of the revision. If the resulting array is $arr, then $arr[0] will contain an
 627+ * array of strings describing the items that were hidden, $arr[2] will contain
 628+ * an array of strings describing the items that were unhidden, and $arr[3] will
 629+ * contain an array with a single string, which can be one of "applied
 630+ * restrictions to sysops", "removed restrictions from sysops", or null.
 631+ *
 632+ * @param $n Integer: the new bitfield.
 633+ * @param $o Integer: the old bitfield.
 634+ * @return An array as described above.
 635+ */
 636+ protected static function getChanges( $n, $o ) {
 637+ $diff = $n ^ $o;
 638+ $ret = array( 0 => array(), 1 => array(), 2 => array() );
 639+ // Build bitfield changes in language
 640+ self::checkItem( 'revdelete-content',
 641+ Revision::DELETED_TEXT, $diff, $n, $ret );
 642+ self::checkItem( 'revdelete-summary',
 643+ Revision::DELETED_COMMENT, $diff, $n, $ret );
 644+ self::checkItem( 'revdelete-uname',
 645+ Revision::DELETED_USER, $diff, $n, $ret );
 646+ // Restriction application to sysops
 647+ if( $diff & Revision::DELETED_RESTRICTED ) {
 648+ if( $n & Revision::DELETED_RESTRICTED )
 649+ $ret[2][] = 'revdelete-restricted';
 650+ else
 651+ $ret[2][] = 'revdelete-unrestricted';
 652+ }
 653+ return $ret;
 654+ }
 655+
 656+ /**
 657+ * Gets a log message to describe the given revision visibility change. This
 658+ * message will be of the form "[hid {content, edit summary, username}];
 659+ * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
 660+ *
 661+ * @param $count Integer: The number of effected revisions.
 662+ * @param $nbitfield Integer: The new bitfield for the revision.
 663+ * @param $obitfield Integer: The old bitfield for the revision.
 664+ * @param $isForLog Boolean
 665+ * @param $forContent Boolean
 666+ */
 667+ public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false, $forContent = false ) {
 668+ global $wgLang, $wgContLang;
 669+
 670+ $lang = $forContent ? $wgContLang : $wgLang;
 671+ $msgFunc = $forContent ? "wfMsgForContent" : "wfMsg";
 672+
 673+ $s = '';
 674+ $changes = self::getChanges( $nbitfield, $obitfield );
 675+ array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
 676+
 677+ $changesText = array();
 678+
 679+ if( count( $changes[0] ) ) {
 680+ $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
 681+ }
 682+ if( count( $changes[1] ) ) {
 683+ $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
 684+ }
 685+
 686+ $s = $lang->semicolonList( $changesText );
 687+ if( count( $changes[2] ) ) {
 688+ $s .= $s ? ' (' . $changes[2][0] . ')' : ' ' . $changes[2][0];
 689+ }
 690+
 691+ $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
 692+ return wfMsgExt( $msg, $forContent ? array( 'parsemag', 'content' ) : array( 'parsemag' ), $s, $lang->formatNum($count) );
 693+ }
 694+
 695+ private static function expandMessageArray(& $msg, $key, $forContent) {
 696+ if ( is_array ($msg) ) {
 697+ array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
 698+ } else {
 699+ if ( $forContent ) {
 700+ $msg = wfMsgForContent($msg);
 701+ } else {
 702+ $msg = wfMsg($msg);
 703+ }
 704+ }
 705+ }
 706+
 707+ // Get DB field name for URL param...
 708+ // Future code for other things may also track
 709+ // other types of revision-specific changes.
 710+ // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
 711+ public static function getRelationType( $typeName ) {
 712+ if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
 713+ $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
 714+ }
 715+ if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
 716+ $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
 717+ $list = new $class( null, null, null );
 718+ return $list->getIdField();
 719+ } else {
 720+ return null;
 721+ }
 722+ }
 723+
 724+ // Checks if a revision still exists in the revision table.
 725+ // If it doesn't, returns the corresponding ar_timestamp field
 726+ // so that this key can be used instead.
 727+ public static function checkRevisionExistence( $title, $revid ) {
 728+ $dbr = wfGetDB( DB_SLAVE );
 729+ $exists = $dbr->selectField( 'revision', '1',
 730+ array( 'rev_id' => $revid ), __METHOD__ );
 731+
 732+ if ( $exists ) {
 733+ return true;
 734+ }
 735+
 736+ $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
 737+ array( 'ar_namespace' => $title->getNamespace(),
 738+ 'ar_title' => $title->getDBkey(),
 739+ 'ar_rev_id' => $revid ), __METHOD__ );
 740+
 741+ return $timestamp;
 742+ }
 743+
 744+ // Creates utility links for log entries.
 745+ public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
 746+ global $wgLang;
 747+
 748+ if( count($paramArray) >= 2 ) {
 749+ // Different revision types use different URL params...
 750+ $originalKey = $key = $paramArray[0];
 751+ // $paramArray[1] is a CSV of the IDs
 752+ $Ids = explode( ',', $paramArray[1] );
 753+ $query = $paramArray[1];
 754+ $revert = array();
 755+
 756+ // For if undeleted revisions are found amidst deleted ones.
 757+ $undeletedRevisions = array();
 758+
 759+ // This is not going to work if some revs are deleted and some
 760+ // aren't.
 761+ if ($key == 'revision') {
 762+ foreach( $Ids as $k => $id ) {
 763+ $existResult =
 764+ self::checkRevisionExistence( $title, $id );
 765+
 766+ if ($existResult !== true) {
 767+ $key = 'archive';
 768+ $Ids[$k] = $existResult;
 769+ } elseif ($key != $originalKey) {
 770+ // Undeleted revision amidst deleted ones
 771+ unset($Ids[$k]);
 772+ $undeletedRevisions[] = $id;
 773+ }
 774+ }
 775+ }
 776+
 777+ // Diff link for single rev deletions
 778+ if( count($Ids) == 1 && !count($undeletedRevisions) ) {
 779+ // Live revision diffs...
 780+ if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
 781+ $revert[] = $skin->link(
 782+ $title,
 783+ $messages['diff'],
 784+ array(),
 785+ array(
 786+ 'diff' => intval( $Ids[0] ),
 787+ 'unhide' => 1
 788+ ),
 789+ array( 'known', 'noclasses' )
 790+ );
 791+ // Deleted revision diffs...
 792+ } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
 793+ $revert[] = $skin->link(
 794+ SpecialPage::getTitleFor( 'Undelete' ),
 795+ $messages['diff'],
 796+ array(),
 797+ array(
 798+ 'target' => $title->getPrefixedDBKey(),
 799+ 'diff' => 'prev',
 800+ 'timestamp' => $Ids[0]
 801+ ),
 802+ array( 'known', 'noclasses' )
 803+ );
 804+ }
 805+ }
 806+
 807+ // View/modify link...
 808+ if ( count($undeletedRevisions) ) {
 809+ // FIXME THIS IS A HORRIBLE HORRIBLE HACK AND SHOULD DIE
 810+ // It's not possible to pass a list of both deleted and
 811+ // undeleted revisions to SpecialRevisionDelete, so we're
 812+ // stuck with two links. See bug 23363.
 813+ $restoreLinks = array();
 814+
 815+ $restoreLinks[] = $skin->link(
 816+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 817+ $messages['revdel-restore-visible'],
 818+ array(),
 819+ array(
 820+ 'target' => $title->getPrefixedText(),
 821+ 'type' => $originalKey,
 822+ 'ids' => implode(',', $undeletedRevisions),
 823+ ),
 824+ array( 'known', 'noclasses' )
 825+ );
 826+
 827+ $restoreLinks[] = $skin->link(
 828+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 829+ $messages['revdel-restore-deleted'],
 830+ array(),
 831+ array(
 832+ 'target' => $title->getPrefixedText(),
 833+ 'type' => $key,
 834+ 'ids' => implode(',', $Ids),
 835+ ),
 836+ array( 'known', 'noclasses' )
 837+ );
 838+
 839+ $revert[] = $messages['revdel-restore'] . ' [' .
 840+ $wgLang->pipeList( $restoreLinks ) . ']';
 841+ } else {
 842+ $revert[] = $skin->link(
 843+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 844+ $messages['revdel-restore'],
 845+ array(),
 846+ array(
 847+ 'target' => $title->getPrefixedText(),
 848+ 'type' => $key,
 849+ 'ids' => implode(',', $Ids),
 850+ ),
 851+ array( 'known', 'noclasses' )
 852+ );
 853+ }
 854+
 855+ // Pipe links
 856+ $revert = wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
 857+ }
 858+ return $revert;
 859+ }
 860+}
 861+
 862+/**
 863+ * Abstract base class for a list of deletable items
 864+ */
 865+abstract class RevDel_List {
 866+ var $special, $title, $ids, $res, $current;
 867+ var $type = null; // override this
 868+ var $idField = null; // override this
 869+ var $dateField = false; // override this
 870+ var $authorIdField = false; // override this
 871+ var $authorNameField = false; // override this
 872+
 873+ /**
 874+ * @param $special The parent SpecialPage
 875+ * @param $title The target title
 876+ * @param $ids Array of IDs
 877+ */
 878+ public function __construct( $special, $title, $ids ) {
 879+ $this->special = $special;
 880+ $this->title = $title;
 881+ $this->ids = $ids;
 882+ }
 883+
 884+ /**
 885+ * Get the internal type name of this list. Equal to the table name.
 886+ */
 887+ public function getType() {
 888+ return $this->type;
 889+ }
 890+
 891+ /**
 892+ * Get the DB field name associated with the ID list
 893+ */
 894+ public function getIdField() {
 895+ return $this->idField;
 896+ }
 897+
 898+ /**
 899+ * Get the DB field name storing timestamps
 900+ */
 901+ public function getTimestampField() {
 902+ return $this->dateField;
 903+ }
 904+
 905+ /**
 906+ * Get the DB field name storing user ids
 907+ */
 908+ public function getAuthorIdField() {
 909+ return $this->authorIdField;
 910+ }
 911+
 912+ /**
 913+ * Get the DB field name storing user names
 914+ */
 915+ public function getAuthorNameField() {
 916+ return $this->authorNameField;
 917+ }
 918+ /**
 919+ * Set the visibility for the revisions in this list. Logging and
 920+ * transactions are done here.
 921+ *
 922+ * @param $params Associative array of parameters. Members are:
 923+ * value: The integer value to set the visibility to
 924+ * comment: The log comment.
 925+ * @return Status
 926+ */
 927+ public function setVisibility( $params ) {
 928+ $bitPars = $params['value'];
 929+ $comment = $params['comment'];
 930+
 931+ $this->res = false;
 932+ $dbw = wfGetDB( DB_MASTER );
 933+ $this->doQuery( $dbw );
 934+ $dbw->begin();
 935+ $status = Status::newGood();
 936+ $missing = array_flip( $this->ids );
 937+ $this->clearFileOps();
 938+ $idsForLog = array();
 939+ $authorIds = $authorIPs = array();
 940+
 941+ for ( $this->reset(); $this->current(); $this->next() ) {
 942+ $item = $this->current();
 943+ unset( $missing[ $item->getId() ] );
 944+
 945+ $oldBits = $item->getBits();
 946+ // Build the actual new rev_deleted bitfield
 947+ $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
 948+
 949+ if ( $oldBits == $newBits ) {
 950+ $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
 951+ $status->failCount++;
 952+ continue;
 953+ } elseif ( $oldBits == 0 && $newBits != 0 ) {
 954+ $opType = 'hide';
 955+ } elseif ( $oldBits != 0 && $newBits == 0 ) {
 956+ $opType = 'show';
 957+ } else {
 958+ $opType = 'modify';
 959+ }
 960+
 961+ if ( $item->isHideCurrentOp( $newBits ) ) {
 962+ // Cannot hide current version text
 963+ $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
 964+ $status->failCount++;
 965+ continue;
 966+ }
 967+ if ( !$item->canView() ) {
 968+ // Cannot access this revision
 969+ $msg = ($opType == 'show') ?
 970+ 'revdelete-show-no-access' : 'revdelete-modify-no-access';
 971+ $status->error( $msg, $item->formatDate(), $item->formatTime() );
 972+ $status->failCount++;
 973+ continue;
 974+ }
 975+ // Cannot just "hide from Sysops" without hiding any fields
 976+ if( $newBits == Revision::DELETED_RESTRICTED ) {
 977+ $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
 978+ $status->failCount++;
 979+ continue;
 980+ }
 981+
 982+ // Update the revision
 983+ $ok = $item->setBits( $newBits );
 984+
 985+ if ( $ok ) {
 986+ $idsForLog[] = $item->getId();
 987+ $status->successCount++;
 988+ if( $item->getAuthorId() > 0 ) {
 989+ $authorIds[] = $item->getAuthorId();
 990+ } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
 991+ $authorIPs[] = $item->getAuthorName();
 992+ }
 993+ } else {
 994+ $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
 995+ $status->failCount++;
 996+ }
 997+ }
 998+
 999+ // Handle missing revisions
 1000+ foreach ( $missing as $id => $unused ) {
 1001+ $status->error( 'revdelete-modify-missing', $id );
 1002+ $status->failCount++;
 1003+ }
 1004+
 1005+ if ( $status->successCount == 0 ) {
 1006+ $status->ok = false;
 1007+ $dbw->rollback();
 1008+ return $status;
 1009+ }
 1010+
 1011+ // Save success count
 1012+ $successCount = $status->successCount;
 1013+
 1014+ // Move files, if there are any
 1015+ $status->merge( $this->doPreCommitUpdates() );
 1016+ if ( !$status->isOK() ) {
 1017+ // Fatal error, such as no configured archive directory
 1018+ $dbw->rollback();
 1019+ return $status;
 1020+ }
 1021+
 1022+ // Log it
 1023+ $this->updateLog( array(
 1024+ 'title' => $this->title,
 1025+ 'count' => $successCount,
 1026+ 'newBits' => $newBits,
 1027+ 'oldBits' => $oldBits,
 1028+ 'comment' => $comment,
 1029+ 'ids' => $idsForLog,
 1030+ 'authorIds' => $authorIds,
 1031+ 'authorIPs' => $authorIPs
 1032+ ) );
 1033+ $dbw->commit();
 1034+
 1035+ // Clear caches
 1036+ $status->merge( $this->doPostCommitUpdates() );
 1037+ return $status;
 1038+ }
 1039+
 1040+ /**
 1041+ * Reload the list data from the master DB. This can be done after setVisibility()
 1042+ * to allow $item->getHTML() to show the new data.
 1043+ */
 1044+ function reloadFromMaster() {
 1045+ $dbw = wfGetDB( DB_MASTER );
 1046+ $this->res = $this->doQuery( $dbw );
 1047+ }
 1048+
 1049+ /**
 1050+ * Record a log entry on the action
 1051+ * @param $params Associative array of parameters:
 1052+ * newBits: The new value of the *_deleted bitfield
 1053+ * oldBits: The old value of the *_deleted bitfield.
 1054+ * title: The target title
 1055+ * ids: The ID list
 1056+ * comment: The log comment
 1057+ * authorsIds: The array of the user IDs of the offenders
 1058+ * authorsIPs: The array of the IP/anon user offenders
 1059+ */
 1060+ protected function updateLog( $params ) {
 1061+ // Get the URL param's corresponding DB field
 1062+ $field = RevisionDeleter::getRelationType( $this->getType() );
 1063+ if( !$field ) {
 1064+ throw new MWException( "Bad log URL param type!" );
 1065+ }
 1066+ // Put things hidden from sysops in the oversight log
 1067+ if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
 1068+ $logType = 'suppress';
 1069+ } else {
 1070+ $logType = 'delete';
 1071+ }
 1072+ // Add params for effected page and ids
 1073+ $logParams = $this->getLogParams( $params );
 1074+ // Actually add the deletion log entry
 1075+ $log = new LogPage( $logType );
 1076+ $logid = $log->addEntry( $this->getLogAction(), $params['title'],
 1077+ $params['comment'], $logParams );
 1078+ // Allow for easy searching of deletion log items for revision/log items
 1079+ $log->addRelations( $field, $params['ids'], $logid );
 1080+ $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
 1081+ $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
 1082+ }
 1083+
 1084+ /**
 1085+ * Get the log action for this list type
 1086+ */
 1087+ public function getLogAction() {
 1088+ return 'revision';
 1089+ }
 1090+
 1091+ /**
 1092+ * Get log parameter array.
 1093+ * @param $params Associative array of log parameters, same as updateLog()
 1094+ * @return array
 1095+ */
 1096+ public function getLogParams( $params ) {
 1097+ return array(
 1098+ $this->getType(),
 1099+ implode( ',', $params['ids'] ),
 1100+ "ofield={$params['oldBits']}",
 1101+ "nfield={$params['newBits']}"
 1102+ );
 1103+ }
 1104+
 1105+ /**
 1106+ * Initialise the current iteration pointer
 1107+ */
 1108+ protected function initCurrent() {
 1109+ $row = $this->res->current();
 1110+ if ( $row ) {
 1111+ $this->current = $this->newItem( $row );
 1112+ } else {
 1113+ $this->current = false;
 1114+ }
 1115+ }
 1116+
 1117+ /**
 1118+ * Start iteration. This must be called before current() or next().
 1119+ * @return First list item
 1120+ */
 1121+ public function reset() {
 1122+ if ( !$this->res ) {
 1123+ $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
 1124+ } else {
 1125+ $this->res->rewind();
 1126+ }
 1127+ $this->initCurrent();
 1128+ return $this->current;
 1129+ }
 1130+
 1131+ /**
 1132+ * Get the current list item, or false if we are at the end
 1133+ */
 1134+ public function current() {
 1135+ return $this->current;
 1136+ }
 1137+
 1138+ /**
 1139+ * Move the iteration pointer to the next list item, and return it.
 1140+ */
 1141+ public function next() {
 1142+ $this->res->next();
 1143+ $this->initCurrent();
 1144+ return $this->current;
 1145+ }
 1146+
 1147+ /**
 1148+ * Get the number of items in the list.
 1149+ */
 1150+ public function length() {
 1151+ if( !$this->res ) {
 1152+ return 0;
 1153+ } else {
 1154+ return $this->res->numRows();
 1155+ }
 1156+ }
 1157+
 1158+ /**
 1159+ * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
 1160+ * STUB
 1161+ */
 1162+ public function clearFileOps() {
 1163+ }
 1164+
 1165+ /**
 1166+ * A hook for setVisibility(): do batch updates pre-commit.
 1167+ * STUB
 1168+ * @return Status
 1169+ */
 1170+ public function doPreCommitUpdates() {
 1171+ return Status::newGood();
 1172+ }
 1173+
 1174+ /**
 1175+ * A hook for setVisibility(): do any necessary updates post-commit.
 1176+ * STUB
 1177+ * @return Status
 1178+ */
 1179+ public function doPostCommitUpdates() {
 1180+ return Status::newGood();
 1181+ }
 1182+
 1183+ /**
 1184+ * Create an item object from a DB result row
 1185+ * @param $row stdclass
 1186+ */
 1187+ abstract public function newItem( $row );
 1188+
 1189+ /**
 1190+ * Do the DB query to iterate through the objects.
 1191+ * @param $db Database object to use for the query
 1192+ */
 1193+ abstract public function doQuery( $db );
 1194+
 1195+ /**
 1196+ * Get the integer value of the flag used for suppression
 1197+ */
 1198+ abstract public function getSuppressBit();
 1199+}
 1200+
 1201+/**
 1202+ * Abstract base class for deletable items
 1203+ */
 1204+abstract class RevDel_Item {
 1205+ /** The parent SpecialPage */
 1206+ var $special;
 1207+
 1208+ /** The parent RevDel_List */
 1209+ var $list;
 1210+
 1211+ /** The DB result row */
 1212+ var $row;
 1213+
 1214+ /**
 1215+ * @param $list RevDel_List
 1216+ * @param $row DB result row
 1217+ */
 1218+ public function __construct( $list, $row ) {
 1219+ $this->special = $list->special;
 1220+ $this->list = $list;
 1221+ $this->row = $row;
 1222+ }
 1223+
 1224+ /**
 1225+ * Get the ID, as it would appear in the ids URL parameter
 1226+ */
 1227+ public function getId() {
 1228+ $field = $this->list->getIdField();
 1229+ return $this->row->$field;
 1230+ }
 1231+
 1232+ /**
 1233+ * Get the date, formatted with $wgLang
 1234+ */
 1235+ public function formatDate() {
 1236+ global $wgLang;
 1237+ return $wgLang->date( $this->getTimestamp() );
 1238+ }
 1239+
 1240+ /**
 1241+ * Get the time, formatted with $wgLang
 1242+ */
 1243+ public function formatTime() {
 1244+ global $wgLang;
 1245+ return $wgLang->time( $this->getTimestamp() );
 1246+ }
 1247+
 1248+ /**
 1249+ * Get the timestamp in MW 14-char form
 1250+ */
 1251+ public function getTimestamp() {
 1252+ $field = $this->list->getTimestampField();
 1253+ return wfTimestamp( TS_MW, $this->row->$field );
 1254+ }
 1255+
 1256+ /**
 1257+ * Get the author user ID
 1258+ */
 1259+ public function getAuthorId() {
 1260+ $field = $this->list->getAuthorIdField();
 1261+ return intval( $this->row->$field );
 1262+ }
 1263+
 1264+ /**
 1265+ * Get the author user name
 1266+ */
 1267+ public function getAuthorName() {
 1268+ $field = $this->list->getAuthorNameField();
 1269+ return strval( $this->row->$field );
 1270+ }
 1271+
 1272+ /**
 1273+ * Returns true if the item is "current", and the operation to set the given
 1274+ * bits can't be executed for that reason
 1275+ * STUB
 1276+ */
 1277+ public function isHideCurrentOp( $newBits ) {
 1278+ return false;
 1279+ }
 1280+
 1281+ /**
 1282+ * Returns true if the current user can view the item
 1283+ */
 1284+ abstract public function canView();
 1285+
 1286+ /**
 1287+ * Returns true if the current user can view the item text/file
 1288+ */
 1289+ abstract public function canViewContent();
 1290+
 1291+ /**
 1292+ * Get the current deletion bitfield value
 1293+ */
 1294+ abstract public function getBits();
 1295+
 1296+ /**
 1297+ * Get the HTML of the list item. Should be include <li></li> tags.
 1298+ * This is used to show the list in HTML form, by the special page.
 1299+ */
 1300+ abstract public function getHTML();
 1301+
 1302+ /**
 1303+ * Set the visibility of the item. This should do any necessary DB queries.
 1304+ *
 1305+ * The DB update query should have a condition which forces it to only update
 1306+ * if the value in the DB matches the value fetched earlier with the SELECT.
 1307+ * If the update fails because it did not match, the function should return
 1308+ * false. This prevents concurrency problems.
 1309+ *
 1310+ * @return boolean success
 1311+ */
 1312+ abstract public function setBits( $newBits );
 1313+}
 1314+
 1315+/**
 1316+ * List for revision table items
 1317+ */
 1318+class RevDel_RevisionList extends RevDel_List {
 1319+ var $currentRevId;
 1320+ var $type = 'revision';
 1321+ var $idField = 'rev_id';
 1322+ var $dateField = 'rev_timestamp';
 1323+ var $authorIdField = 'rev_user';
 1324+ var $authorNameField = 'rev_user_text';
 1325+
 1326+ public function doQuery( $db ) {
 1327+ $ids = array_map( 'intval', $this->ids );
 1328+ return $db->select( array('revision','page'), '*',
 1329+ array(
 1330+ 'rev_page' => $this->title->getArticleID(),
 1331+ 'rev_id' => $ids,
 1332+ 'rev_page = page_id'
 1333+ ),
 1334+ __METHOD__,
 1335+ array( 'ORDER BY' => 'rev_id DESC' )
 1336+ );
 1337+ }
 1338+
 1339+ public function newItem( $row ) {
 1340+ return new RevDel_RevisionItem( $this, $row );
 1341+ }
 1342+
 1343+ public function getCurrent() {
 1344+ if ( is_null( $this->currentRevId ) ) {
 1345+ $dbw = wfGetDB( DB_MASTER );
 1346+ $this->currentRevId = $dbw->selectField(
 1347+ 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
 1348+ }
 1349+ return $this->currentRevId;
 1350+ }
 1351+
 1352+ public function getSuppressBit() {
 1353+ return Revision::DELETED_RESTRICTED;
 1354+ }
 1355+
 1356+ public function doPreCommitUpdates() {
 1357+ $this->title->invalidateCache();
 1358+ return Status::newGood();
 1359+ }
 1360+
 1361+ public function doPostCommitUpdates() {
 1362+ $this->title->purgeSquid();
 1363+ // Extensions that require referencing previous revisions may need this
 1364+ wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title ) );
 1365+ return Status::newGood();
 1366+ }
 1367+}
 1368+
 1369+/**
 1370+ * Item class for a revision table row
 1371+ */
 1372+class RevDel_RevisionItem extends RevDel_Item {
 1373+ var $revision;
 1374+
 1375+ public function __construct( $list, $row ) {
 1376+ parent::__construct( $list, $row );
 1377+ $this->revision = new Revision( $row );
 1378+ }
 1379+
 1380+ public function canView() {
 1381+ return $this->revision->userCan( Revision::DELETED_RESTRICTED );
 1382+ }
 1383+
 1384+ public function canViewContent() {
 1385+ return $this->revision->userCan( Revision::DELETED_TEXT );
 1386+ }
 1387+
 1388+ public function getBits() {
 1389+ return $this->revision->mDeleted;
 1390+ }
 1391+
 1392+ public function setBits( $bits ) {
 1393+ $dbw = wfGetDB( DB_MASTER );
 1394+ // Update revision table
 1395+ $dbw->update( 'revision',
 1396+ array( 'rev_deleted' => $bits ),
 1397+ array(
 1398+ 'rev_id' => $this->revision->getId(),
 1399+ 'rev_page' => $this->revision->getPage(),
 1400+ 'rev_deleted' => $this->getBits()
 1401+ ),
 1402+ __METHOD__
 1403+ );
 1404+ if ( !$dbw->affectedRows() ) {
 1405+ // Concurrent fail!
 1406+ return false;
 1407+ }
 1408+ // Update recentchanges table
 1409+ $dbw->update( 'recentchanges',
 1410+ array(
 1411+ 'rc_deleted' => $bits,
 1412+ 'rc_patrolled' => 1
 1413+ ),
 1414+ array(
 1415+ 'rc_this_oldid' => $this->revision->getId(), // condition
 1416+ // non-unique timestamp index
 1417+ 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
 1418+ ),
 1419+ __METHOD__
 1420+ );
 1421+ return true;
 1422+ }
 1423+
 1424+ public function isDeleted() {
 1425+ return $this->revision->isDeleted( Revision::DELETED_TEXT );
 1426+ }
 1427+
 1428+ public function isHideCurrentOp( $newBits ) {
 1429+ return ( $newBits & Revision::DELETED_TEXT )
 1430+ && $this->list->getCurrent() == $this->getId();
 1431+ }
 1432+
 1433+ /**
 1434+ * Get the HTML link to the revision text.
 1435+ * Overridden by RevDel_ArchiveItem.
 1436+ */
 1437+ protected function getRevisionLink() {
 1438+ global $wgLang;
 1439+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
 1440+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 1441+ return $date;
 1442+ }
 1443+ return $this->special->skin->link(
 1444+ $this->list->title,
 1445+ $date,
 1446+ array(),
 1447+ array(
 1448+ 'oldid' => $this->revision->getId(),
 1449+ 'unhide' => 1
 1450+ )
 1451+ );
 1452+ }
 1453+
 1454+ /**
 1455+ * Get the HTML link to the diff.
 1456+ * Overridden by RevDel_ArchiveItem
 1457+ */
 1458+ protected function getDiffLink() {
 1459+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 1460+ return wfMsgHtml('diff');
 1461+ } else {
 1462+ return
 1463+ $this->special->skin->link(
 1464+ $this->list->title,
 1465+ wfMsgHtml('diff'),
 1466+ array(),
 1467+ array(
 1468+ 'diff' => $this->revision->getId(),
 1469+ 'oldid' => 'prev',
 1470+ 'unhide' => 1
 1471+ ),
 1472+ array(
 1473+ 'known',
 1474+ 'noclasses'
 1475+ )
 1476+ );
 1477+ }
 1478+ }
 1479+
 1480+ public function getHTML() {
 1481+ $difflink = $this->getDiffLink();
 1482+ $revlink = $this->getRevisionLink();
 1483+ $userlink = $this->special->skin->revUserLink( $this->revision );
 1484+ $comment = $this->special->skin->revComment( $this->revision );
 1485+ if ( $this->isDeleted() ) {
 1486+ $revlink = "<span class=\"history-deleted\">$revlink</span>";
 1487+ }
 1488+ return "<li>($difflink) $revlink $userlink $comment</li>";
 1489+ }
 1490+}
 1491+
 1492+/**
 1493+ * List for archive table items, i.e. revisions deleted via action=delete
 1494+ */
 1495+class RevDel_ArchiveList extends RevDel_RevisionList {
 1496+ var $type = 'archive';
 1497+ var $idField = 'ar_timestamp';
 1498+ var $dateField = 'ar_timestamp';
 1499+ var $authorIdField = 'ar_user';
 1500+ var $authorNameField = 'ar_user_text';
 1501+
 1502+ public function doQuery( $db ) {
 1503+ $timestamps = array();
 1504+ foreach ( $this->ids as $id ) {
 1505+ $timestamps[] = $db->timestamp( $id );
 1506+ }
 1507+ return $db->select( 'archive', '*',
 1508+ array(
 1509+ 'ar_namespace' => $this->title->getNamespace(),
 1510+ 'ar_title' => $this->title->getDBkey(),
 1511+ 'ar_timestamp' => $timestamps
 1512+ ),
 1513+ __METHOD__,
 1514+ array( 'ORDER BY' => 'ar_timestamp DESC' )
 1515+ );
 1516+ }
 1517+
 1518+ public function newItem( $row ) {
 1519+ return new RevDel_ArchiveItem( $this, $row );
 1520+ }
 1521+
 1522+ public function doPreCommitUpdates() {
 1523+ return Status::newGood();
 1524+ }
 1525+
 1526+ public function doPostCommitUpdates() {
 1527+ return Status::newGood();
 1528+ }
 1529+}
 1530+
 1531+/**
 1532+ * Item class for a archive table row
 1533+ */
 1534+class RevDel_ArchiveItem extends RevDel_RevisionItem {
 1535+ public function __construct( $list, $row ) {
 1536+ RevDel_Item::__construct( $list, $row );
 1537+ $this->revision = Revision::newFromArchiveRow( $row,
 1538+ array( 'page' => $this->list->title->getArticleId() ) );
 1539+ }
 1540+
 1541+ public function getId() {
 1542+ # Convert DB timestamp to MW timestamp
 1543+ return $this->revision->getTimestamp();
 1544+ }
 1545+
 1546+ public function setBits( $bits ) {
 1547+ $dbw = wfGetDB( DB_MASTER );
 1548+ $dbw->update( 'archive',
 1549+ array( 'ar_deleted' => $bits ),
 1550+ array( 'ar_namespace' => $this->list->title->getNamespace(),
 1551+ 'ar_title' => $this->list->title->getDBkey(),
 1552+ // use timestamp for index
 1553+ 'ar_timestamp' => $this->row->ar_timestamp,
 1554+ 'ar_rev_id' => $this->row->ar_rev_id,
 1555+ 'ar_deleted' => $this->getBits()
 1556+ ),
 1557+ __METHOD__ );
 1558+ return (bool)$dbw->affectedRows();
 1559+ }
 1560+
 1561+ protected function getRevisionLink() {
 1562+ global $wgLang;
 1563+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 1564+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
 1565+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 1566+ return $date;
 1567+ }
 1568+ return $this->special->skin->link( $undelete, $date, array(),
 1569+ array(
 1570+ 'target' => $this->list->title->getPrefixedText(),
 1571+ 'timestamp' => $this->revision->getTimestamp()
 1572+ ) );
 1573+ }
 1574+
 1575+ protected function getDiffLink() {
 1576+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 1577+ return wfMsgHtml( 'diff' );
 1578+ }
 1579+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 1580+ return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
 1581+ array(
 1582+ 'target' => $this->list->title->getPrefixedText(),
 1583+ 'diff' => 'prev',
 1584+ 'timestamp' => $this->revision->getTimestamp()
 1585+ ) );
 1586+ }
 1587+}
 1588+
 1589+/**
 1590+ * List for oldimage table items
 1591+ */
 1592+class RevDel_FileList extends RevDel_List {
 1593+ var $type = 'oldimage';
 1594+ var $idField = 'oi_archive_name';
 1595+ var $dateField = 'oi_timestamp';
 1596+ var $authorIdField = 'oi_user';
 1597+ var $authorNameField = 'oi_user_text';
 1598+ var $storeBatch, $deleteBatch, $cleanupBatch;
 1599+
 1600+ public function doQuery( $db ) {
 1601+ $archiveName = array();
 1602+ foreach( $this->ids as $timestamp ) {
 1603+ $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
 1604+ }
 1605+ return $db->select( 'oldimage', '*',
 1606+ array(
 1607+ 'oi_name' => $this->title->getDBkey(),
 1608+ 'oi_archive_name' => $archiveNames
 1609+ ),
 1610+ __METHOD__,
 1611+ array( 'ORDER BY' => 'oi_timestamp DESC' )
 1612+ );
 1613+ }
 1614+
 1615+ public function newItem( $row ) {
 1616+ return new RevDel_FileItem( $this, $row );
 1617+ }
 1618+
 1619+ public function clearFileOps() {
 1620+ $this->deleteBatch = array();
 1621+ $this->storeBatch = array();
 1622+ $this->cleanupBatch = array();
 1623+ }
 1624+
 1625+ public function doPreCommitUpdates() {
 1626+ $status = Status::newGood();
 1627+ $repo = RepoGroup::singleton()->getLocalRepo();
 1628+ if ( $this->storeBatch ) {
 1629+ $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
 1630+ }
 1631+ if ( !$status->isOK() ) {
 1632+ return $status;
 1633+ }
 1634+ if ( $this->deleteBatch ) {
 1635+ $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
 1636+ }
 1637+ if ( !$status->isOK() ) {
 1638+ // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
 1639+ // modified (but destined for rollback) causes data loss
 1640+ return $status;
 1641+ }
 1642+ if ( $this->cleanupBatch ) {
 1643+ $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
 1644+ }
 1645+ return $status;
 1646+ }
 1647+
 1648+ public function doPostCommitUpdates() {
 1649+ $file = wfLocalFile( $this->title );
 1650+ $file->purgeCache();
 1651+ $file->purgeDescription();
 1652+ return Status::newGood();
 1653+ }
 1654+
 1655+ public function getSuppressBit() {
 1656+ return File::DELETED_RESTRICTED;
 1657+ }
 1658+}
 1659+
 1660+/**
 1661+ * Item class for an oldimage table row
 1662+ */
 1663+class RevDel_FileItem extends RevDel_Item {
 1664+ var $file;
 1665+
 1666+ public function __construct( $list, $row ) {
 1667+ parent::__construct( $list, $row );
 1668+ $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
 1669+ }
 1670+
 1671+ public function getId() {
 1672+ $parts = explode( '!', $this->row->oi_archive_name );
 1673+ return $parts[0];
 1674+ }
 1675+
 1676+ public function canView() {
 1677+ return $this->file->userCan( File::DELETED_RESTRICTED );
 1678+ }
 1679+
 1680+ public function canViewContent() {
 1681+ return $this->file->userCan( File::DELETED_FILE );
 1682+ }
 1683+
 1684+ public function getBits() {
 1685+ return $this->file->getVisibility();
 1686+ }
 1687+
 1688+ public function setBits( $bits ) {
 1689+ # Queue the file op
 1690+ # FIXME: move to LocalFile.php
 1691+ if ( $this->isDeleted() ) {
 1692+ if ( $bits & File::DELETED_FILE ) {
 1693+ # Still deleted
 1694+ } else {
 1695+ # Newly undeleted
 1696+ $key = $this->file->getStorageKey();
 1697+ $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
 1698+ $this->list->storeBatch[] = array(
 1699+ $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
 1700+ 'public',
 1701+ $this->file->getRel()
 1702+ );
 1703+ $this->list->cleanupBatch[] = $key;
 1704+ }
 1705+ } elseif ( $bits & File::DELETED_FILE ) {
 1706+ # Newly deleted
 1707+ $key = $this->file->getStorageKey();
 1708+ $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
 1709+ $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
 1710+ }
 1711+
 1712+ # Do the database operations
 1713+ $dbw = wfGetDB( DB_MASTER );
 1714+ $dbw->update( 'oldimage',
 1715+ array( 'oi_deleted' => $bits ),
 1716+ array(
 1717+ 'oi_name' => $this->row->oi_name,
 1718+ 'oi_timestamp' => $this->row->oi_timestamp,
 1719+ 'oi_deleted' => $this->getBits()
 1720+ ),
 1721+ __METHOD__
 1722+ );
 1723+ return (bool)$dbw->affectedRows();
 1724+ }
 1725+
 1726+ public function isDeleted() {
 1727+ return $this->file->isDeleted( File::DELETED_FILE );
 1728+ }
 1729+
 1730+ /**
 1731+ * Get the link to the file.
 1732+ * Overridden by RevDel_ArchivedFileItem.
 1733+ */
 1734+ protected function getLink() {
 1735+ global $wgLang, $wgUser;
 1736+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
 1737+ if ( $this->isDeleted() ) {
 1738+ # Hidden files...
 1739+ if ( !$this->canViewContent() ) {
 1740+ $link = $date;
 1741+ } else {
 1742+ $link = $this->special->skin->link(
 1743+ $this->special->getTitle(),
 1744+ $date, array(),
 1745+ array(
 1746+ 'target' => $this->list->title->getPrefixedText(),
 1747+ 'file' => $this->file->getArchiveName(),
 1748+ 'token' => $wgUser->editToken( $this->file->getArchiveName() )
 1749+ )
 1750+ );
 1751+ }
 1752+ return '<span class="history-deleted">' . $link . '</span>';
 1753+ } else {
 1754+ # Regular files...
 1755+ $url = $this->file->getUrl();
 1756+ return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
 1757+ }
 1758+ }
 1759+ /**
 1760+ * Generate a user tool link cluster if the current user is allowed to view it
 1761+ * @return string HTML
 1762+ */
 1763+ protected function getUserTools() {
 1764+ if( $this->file->userCan( Revision::DELETED_USER ) ) {
 1765+ $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
 1766+ $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
 1767+ } else {
 1768+ $link = wfMsgHtml( 'rev-deleted-user' );
 1769+ }
 1770+ if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
 1771+ return '<span class="history-deleted">' . $link . '</span>';
 1772+ }
 1773+ return $link;
 1774+ }
 1775+
 1776+ /**
 1777+ * Wrap and format the file's comment block, if the current
 1778+ * user is allowed to view it.
 1779+ *
 1780+ * @return string HTML
 1781+ */
 1782+ protected function getComment() {
 1783+ if( $this->file->userCan( File::DELETED_COMMENT ) ) {
 1784+ $block = $this->special->skin->commentBlock( $this->file->description );
 1785+ } else {
 1786+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
 1787+ }
 1788+ if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
 1789+ return "<span class=\"history-deleted\">$block</span>";
 1790+ }
 1791+ return $block;
 1792+ }
 1793+
 1794+ public function getHTML() {
 1795+ global $wgLang;
 1796+ $data =
 1797+ wfMsg(
 1798+ 'widthheight',
 1799+ $wgLang->formatNum( $this->file->getWidth() ),
 1800+ $wgLang->formatNum( $this->file->getHeight() )
 1801+ ) .
 1802+ ' (' .
 1803+ wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
 1804+ ')';
 1805+ $pageLink = $this->getLink();
 1806+
 1807+ return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
 1808+ $data . ' ' . $this->getComment(). '</li>';
 1809+ }
 1810+}
 1811+
 1812+/**
 1813+ * List for filearchive table items
 1814+ */
 1815+class RevDel_ArchivedFileList extends RevDel_FileList {
 1816+ var $type = 'filearchive';
 1817+ var $idField = 'fa_id';
 1818+ var $dateField = 'fa_timestamp';
 1819+ var $authorIdField = 'fa_user';
 1820+ var $authorNameField = 'fa_user_text';
 1821+
 1822+ public function doQuery( $db ) {
 1823+ $ids = array_map( 'intval', $this->ids );
 1824+ return $db->select( 'filearchive', '*',
 1825+ array(
 1826+ 'fa_name' => $this->title->getDBkey(),
 1827+ 'fa_id' => $ids
 1828+ ),
 1829+ __METHOD__,
 1830+ array( 'ORDER BY' => 'fa_id DESC' )
 1831+ );
 1832+ }
 1833+
 1834+ public function newItem( $row ) {
 1835+ return new RevDel_ArchivedFileItem( $this, $row );
 1836+ }
 1837+}
 1838+
 1839+/**
 1840+ * Item class for a filearchive table row
 1841+ */
 1842+class RevDel_ArchivedFileItem extends RevDel_FileItem {
 1843+ public function __construct( $list, $row ) {
 1844+ RevDel_Item::__construct( $list, $row );
 1845+ $this->file = ArchivedFile::newFromRow( $row );
 1846+ }
 1847+
 1848+ public function getId() {
 1849+ return $this->row->fa_id;
 1850+ }
 1851+
 1852+ public function setBits( $bits ) {
 1853+ $dbw = wfGetDB( DB_MASTER );
 1854+ $dbw->update( 'filearchive',
 1855+ array( 'fa_deleted' => $bits ),
 1856+ array(
 1857+ 'fa_id' => $this->row->fa_id,
 1858+ 'fa_deleted' => $this->getBits(),
 1859+ ),
 1860+ __METHOD__
 1861+ );
 1862+ return (bool)$dbw->affectedRows();
 1863+ }
 1864+
 1865+ protected function getLink() {
 1866+ global $wgLang, $wgUser;
 1867+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
 1868+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 1869+ $key = $this->file->getKey();
 1870+ # Hidden files...
 1871+ if( !$this->canViewContent() ) {
 1872+ $link = $date;
 1873+ } else {
 1874+ $link = $this->special->skin->link( $undelete, $date, array(),
 1875+ array(
 1876+ 'target' => $this->list->title->getPrefixedText(),
 1877+ 'file' => $key,
 1878+ 'token' => $wgUser->editToken( $key )
 1879+ )
 1880+ );
 1881+ }
 1882+ if( $this->isDeleted() ) {
 1883+ $link = '<span class="history-deleted">' . $link . '</span>';
 1884+ }
 1885+ return $link;
 1886+ }
 1887+}
 1888+
 1889+/**
 1890+ * List for logging table items
 1891+ */
 1892+class RevDel_LogList extends RevDel_List {
 1893+ var $type = 'logging';
 1894+ var $idField = 'log_id';
 1895+ var $dateField = 'log_timestamp';
 1896+ var $authorIdField = 'log_user';
 1897+ var $authorNameField = 'log_user_text';
 1898+
 1899+ public function doQuery( $db ) {
 1900+ global $wgMessageCache;
 1901+ $wgMessageCache->loadAllMessages();
 1902+ $ids = array_map( 'intval', $this->ids );
 1903+ return $db->select( 'logging', '*',
 1904+ array( 'log_id' => $ids ),
 1905+ __METHOD__,
 1906+ array( 'ORDER BY' => 'log_id DESC' )
 1907+ );
 1908+ }
 1909+
 1910+ public function newItem( $row ) {
 1911+ return new RevDel_LogItem( $this, $row );
 1912+ }
 1913+
 1914+ public function getSuppressBit() {
 1915+ return Revision::DELETED_RESTRICTED;
 1916+ }
 1917+
 1918+ public function getLogAction() {
 1919+ return 'event';
 1920+ }
 1921+
 1922+ public function getLogParams( $params ) {
 1923+ return array(
 1924+ implode( ',', $params['ids'] ),
 1925+ "ofield={$params['oldBits']}",
 1926+ "nfield={$params['newBits']}"
 1927+ );
 1928+ }
 1929+}
 1930+
 1931+/**
 1932+ * Item class for a logging table row
 1933+ */
 1934+class RevDel_LogItem extends RevDel_Item {
 1935+ public function canView() {
 1936+ return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
 1937+ }
 1938+
 1939+ public function canViewContent() {
 1940+ return true; // none
 1941+ }
 1942+
 1943+ public function getBits() {
 1944+ return $this->row->log_deleted;
 1945+ }
 1946+
 1947+ public function setBits( $bits ) {
 1948+ $dbw = wfGetDB( DB_MASTER );
 1949+ $dbw->update( 'recentchanges',
 1950+ array(
 1951+ 'rc_deleted' => $bits,
 1952+ 'rc_patrolled' => 1
 1953+ ),
 1954+ array(
 1955+ 'rc_logid' => $this->row->log_id,
 1956+ 'rc_timestamp' => $this->row->log_timestamp // index
 1957+ ),
 1958+ __METHOD__
 1959+ );
 1960+ $dbw->update( 'logging',
 1961+ array( 'log_deleted' => $bits ),
 1962+ array(
 1963+ 'log_id' => $this->row->log_id,
 1964+ 'log_deleted' => $this->getBits()
 1965+ ),
 1966+ __METHOD__
 1967+ );
 1968+ return (bool)$dbw->affectedRows();
 1969+ }
 1970+
 1971+ public function getHTML() {
 1972+ global $wgLang;
 1973+
 1974+ $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
 1975+ $paramArray = LogPage::extractParams( $this->row->log_params );
 1976+ $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
 1977+
 1978+ // Log link for this page
 1979+ $loglink = $this->special->skin->link(
 1980+ SpecialPage::getTitleFor( 'Log' ),
 1981+ wfMsgHtml( 'log' ),
 1982+ array(),
 1983+ array( 'page' => $title->getPrefixedText() )
 1984+ );
 1985+ // Action text
 1986+ if( !$this->canView() ) {
 1987+ $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
 1988+ } else {
 1989+ $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
 1990+ $this->special->skin, $paramArray, true, true );
 1991+ if( $this->row->log_deleted & LogPage::DELETED_ACTION )
 1992+ $action = '<span class="history-deleted">' . $action . '</span>';
 1993+ }
 1994+ // User links
 1995+ $userLink = $this->special->skin->userLink( $this->row->log_user,
 1996+ User::WhoIs( $this->row->log_user ) );
 1997+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
 1998+ $userLink = '<span class="history-deleted">' . $userLink . '</span>';
 1999+ }
 2000+ // Comment
 2001+ $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
 2002+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
 2003+ $comment = '<span class="history-deleted">' . $comment . '</span>';
 2004+ }
 2005+ return "<li>($loglink) $date $userLink $action $comment</li>";
 2006+ }
 2007+}

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r66856bug 18608 done in a fashion much closer to how I should've done it originally....reedy23:24, 24 May 2010

Status & tagging log