3 * Temporary b/c interface, collection of static functions.
6 class RevisionDeleter
{
8 * Checks for a change in the bitfield for a certain option and updates the
9 * provided array accordingly.
11 * @param $desc String: description to add to the array if the option was
13 * @param $field Integer: the bitmask describing the single option.
14 * @param $diff Integer: the xor of the old and new bitfields.
15 * @param $new Integer: the new bitfield
16 * @param $arr Array: the array to update.
18 protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
19 if( $diff & $field ) {
20 $arr[ ( $new & $field ) ?
0 : 1 ][] = $desc;
25 * Gets an array of message keys describing the changes made to the visibility
26 * of the revision. If the resulting array is $arr, then $arr[0] will contain an
27 * array of strings describing the items that were hidden, $arr[2] will contain
28 * an array of strings describing the items that were unhidden, and $arr[3] will
29 * contain an array with a single string, which can be one of "applied
30 * restrictions to sysops", "removed restrictions from sysops", or null.
32 * @param $n Integer: the new bitfield.
33 * @param $o Integer: the old bitfield.
34 * @return An array as described above.
36 protected static function getChanges( $n, $o ) {
38 $ret = array( 0 => array(), 1 => array(), 2 => array() );
39 // Build bitfield changes in language
40 self
::checkItem( 'revdelete-content',
41 Revision
::DELETED_TEXT
, $diff, $n, $ret );
42 self
::checkItem( 'revdelete-summary',
43 Revision
::DELETED_COMMENT
, $diff, $n, $ret );
44 self
::checkItem( 'revdelete-uname',
45 Revision
::DELETED_USER
, $diff, $n, $ret );
46 // Restriction application to sysops
47 if( $diff & Revision
::DELETED_RESTRICTED
) {
48 if( $n & Revision
::DELETED_RESTRICTED
)
49 $ret[2][] = 'revdelete-restricted';
51 $ret[2][] = 'revdelete-unrestricted';
57 * Gets a log message to describe the given revision visibility change. This
58 * message will be of the form "[hid {content, edit summary, username}];
59 * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
61 * @param $count Integer: The number of effected revisions.
62 * @param $nbitfield Integer: The new bitfield for the revision.
63 * @param $obitfield Integer: The old bitfield for the revision.
64 * @param $isForLog Boolean
65 * @param $forContent Boolean
67 public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false, $forContent = false ) {
68 global $wgLang, $wgContLang;
70 $lang = $forContent ?
$wgContLang : $wgLang;
71 $msgFunc = $forContent ?
"wfMsgForContent" : "wfMsg";
74 $changes = self
::getChanges( $nbitfield, $obitfield );
75 array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
77 $changesText = array();
79 if( count( $changes[0] ) ) {
80 $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
82 if( count( $changes[1] ) ) {
83 $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
86 $s = $lang->semicolonList( $changesText );
87 if( count( $changes[2] ) ) {
88 $s .= $s ?
' (' . $changes[2][0] . ')' : ' ' . $changes[2][0];
91 $msg = $isForLog ?
'logdelete-log-message' : 'revdelete-log-message';
92 return wfMsgExt( $msg, $forContent ?
array( 'parsemag', 'content' ) : array( 'parsemag' ), $s, $lang->formatNum($count) );
95 private static function expandMessageArray(& $msg, $key, $forContent) {
96 if ( is_array ($msg) ) {
97 array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
100 $msg = wfMsgForContent($msg);
107 // Get DB field name for URL param...
108 // Future code for other things may also track
109 // other types of revision-specific changes.
110 // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
111 public static function getRelationType( $typeName ) {
112 if ( isset( SpecialRevisionDelete
::$deprecatedTypeMap[$typeName] ) ) {
113 $typeName = SpecialRevisionDelete
::$deprecatedTypeMap[$typeName];
115 if ( isset( SpecialRevisionDelete
::$allowedTypes[$typeName] ) ) {
116 $class = SpecialRevisionDelete
::$allowedTypes[$typeName]['list-class'];
117 $list = new $class( null, null, null );
118 return $list->getIdField();
124 // Checks if a revision still exists in the revision table.
125 // If it doesn't, returns the corresponding ar_timestamp field
126 // so that this key can be used instead.
127 public static function checkRevisionExistence( $title, $revid ) {
128 $dbr = wfGetDB( DB_SLAVE
);
129 $exists = $dbr->selectField( 'revision', '1',
130 array( 'rev_id' => $revid ), __METHOD__
);
136 $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
137 array( 'ar_namespace' => $title->getNamespace(),
138 'ar_title' => $title->getDBkey(),
139 'ar_rev_id' => $revid ), __METHOD__
);
144 // Creates utility links for log entries.
145 public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
148 if( count($paramArray) >= 2 ) {
149 // Different revision types use different URL params...
150 $originalKey = $key = $paramArray[0];
151 // $paramArray[1] is a CSV of the IDs
152 $Ids = explode( ',', $paramArray[1] );
153 $query = $paramArray[1];
156 // For if undeleted revisions are found amidst deleted ones.
157 $undeletedRevisions = array();
159 // This is not going to work if some revs are deleted and some
161 if ($key == 'revision') {
162 foreach( $Ids as $k => $id ) {
164 self
::checkRevisionExistence( $title, $id );
166 if ($existResult !== true) {
168 $Ids[$k] = $existResult;
169 } elseif ($key != $originalKey) {
170 // Undeleted revision amidst deleted ones
172 $undeletedRevisions[] = $id;
177 // Diff link for single rev deletions
178 if( count($Ids) == 1 && !count($undeletedRevisions) ) {
179 // Live revision diffs...
180 if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
181 $revert[] = $skin->link(
186 'diff' => intval( $Ids[0] ),
189 array( 'known', 'noclasses' )
191 // Deleted revision diffs...
192 } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
193 $revert[] = $skin->link(
194 SpecialPage
::getTitleFor( 'Undelete' ),
198 'target' => $title->getPrefixedDBKey(),
200 'timestamp' => $Ids[0]
202 array( 'known', 'noclasses' )
207 // View/modify link...
208 if ( count($undeletedRevisions) ) {
209 // FIXME THIS IS A HORRIBLE HORRIBLE HACK AND SHOULD DIE
210 // It's not possible to pass a list of both deleted and
211 // undeleted revisions to SpecialRevisionDelete, so we're
212 // stuck with two links. See bug 23363.
213 $restoreLinks = array();
215 $restoreLinks[] = $skin->link(
216 SpecialPage
::getTitleFor( 'Revisiondelete' ),
217 $messages['revdel-restore-visible'],
220 'target' => $title->getPrefixedText(),
221 'type' => $originalKey,
222 'ids' => implode(',', $undeletedRevisions),
224 array( 'known', 'noclasses' )
227 $restoreLinks[] = $skin->link(
228 SpecialPage
::getTitleFor( 'Revisiondelete' ),
229 $messages['revdel-restore-deleted'],
232 'target' => $title->getPrefixedText(),
234 'ids' => implode(',', $Ids),
236 array( 'known', 'noclasses' )
239 $revert[] = $messages['revdel-restore'] . ' [' .
240 $wgLang->pipeList( $restoreLinks ) . ']';
242 $revert[] = $skin->link(
243 SpecialPage
::getTitleFor( 'Revisiondelete' ),
244 $messages['revdel-restore'],
247 'target' => $title->getPrefixedText(),
249 'ids' => implode(',', $Ids),
251 array( 'known', 'noclasses' )
256 $revert = wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
263 * Abstract base class for a list of deletable items
265 abstract class RevDel_List
{
266 var $special, $title, $ids, $res, $current;
267 var $type = null; // override this
268 var $idField = null; // override this
269 var $dateField = false; // override this
270 var $authorIdField = false; // override this
271 var $authorNameField = false; // override this
274 * @param $special The parent SpecialPage
275 * @param $title The target title
276 * @param $ids Array of IDs
278 public function __construct( $special, $title, $ids ) {
279 $this->special
= $special;
280 $this->title
= $title;
285 * Get the internal type name of this list. Equal to the table name.
287 public function getType() {
292 * Get the DB field name associated with the ID list
294 public function getIdField() {
295 return $this->idField
;
299 * Get the DB field name storing timestamps
301 public function getTimestampField() {
302 return $this->dateField
;
306 * Get the DB field name storing user ids
308 public function getAuthorIdField() {
309 return $this->authorIdField
;
313 * Get the DB field name storing user names
315 public function getAuthorNameField() {
316 return $this->authorNameField
;
319 * Set the visibility for the revisions in this list. Logging and
320 * transactions are done here.
322 * @param $params Associative array of parameters. Members are:
323 * value: The integer value to set the visibility to
324 * comment: The log comment.
327 public function setVisibility( $params ) {
328 $bitPars = $params['value'];
329 $comment = $params['comment'];
332 $dbw = wfGetDB( DB_MASTER
);
333 $this->doQuery( $dbw );
335 $status = Status
::newGood();
336 $missing = array_flip( $this->ids
);
337 $this->clearFileOps();
338 $idsForLog = array();
339 $authorIds = $authorIPs = array();
341 for ( $this->reset(); $this->current(); $this->next() ) {
342 $item = $this->current();
343 unset( $missing[ $item->getId() ] );
345 $oldBits = $item->getBits();
346 // Build the actual new rev_deleted bitfield
347 $newBits = SpecialRevisionDelete
::extractBitfield( $bitPars, $oldBits );
349 if ( $oldBits == $newBits ) {
350 $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
351 $status->failCount++
;
353 } elseif ( $oldBits == 0 && $newBits != 0 ) {
355 } elseif ( $oldBits != 0 && $newBits == 0 ) {
361 if ( $item->isHideCurrentOp( $newBits ) ) {
362 // Cannot hide current version text
363 $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
364 $status->failCount++
;
367 if ( !$item->canView() ) {
368 // Cannot access this revision
369 $msg = ($opType == 'show') ?
370 'revdelete-show-no-access' : 'revdelete-modify-no-access';
371 $status->error( $msg, $item->formatDate(), $item->formatTime() );
372 $status->failCount++
;
375 // Cannot just "hide from Sysops" without hiding any fields
376 if( $newBits == Revision
::DELETED_RESTRICTED
) {
377 $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
378 $status->failCount++
;
382 // Update the revision
383 $ok = $item->setBits( $newBits );
386 $idsForLog[] = $item->getId();
387 $status->successCount++
;
388 if( $item->getAuthorId() > 0 ) {
389 $authorIds[] = $item->getAuthorId();
390 } else if( IP
::isIPAddress( $item->getAuthorName() ) ) {
391 $authorIPs[] = $item->getAuthorName();
394 $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
395 $status->failCount++
;
399 // Handle missing revisions
400 foreach ( $missing as $id => $unused ) {
401 $status->error( 'revdelete-modify-missing', $id );
402 $status->failCount++
;
405 if ( $status->successCount
== 0 ) {
411 // Save success count
412 $successCount = $status->successCount
;
414 // Move files, if there are any
415 $status->merge( $this->doPreCommitUpdates() );
416 if ( !$status->isOK() ) {
417 // Fatal error, such as no configured archive directory
423 $this->updateLog( array(
424 'title' => $this->title
,
425 'count' => $successCount,
426 'newBits' => $newBits,
427 'oldBits' => $oldBits,
428 'comment' => $comment,
430 'authorIds' => $authorIds,
431 'authorIPs' => $authorIPs
436 $status->merge( $this->doPostCommitUpdates() );
441 * Reload the list data from the master DB. This can be done after setVisibility()
442 * to allow $item->getHTML() to show the new data.
444 function reloadFromMaster() {
445 $dbw = wfGetDB( DB_MASTER
);
446 $this->res
= $this->doQuery( $dbw );
450 * Record a log entry on the action
451 * @param $params Associative array of parameters:
452 * newBits: The new value of the *_deleted bitfield
453 * oldBits: The old value of the *_deleted bitfield.
454 * title: The target title
456 * comment: The log comment
457 * authorsIds: The array of the user IDs of the offenders
458 * authorsIPs: The array of the IP/anon user offenders
460 protected function updateLog( $params ) {
461 // Get the URL param's corresponding DB field
462 $field = RevisionDeleter
::getRelationType( $this->getType() );
464 throw new MWException( "Bad log URL param type!" );
466 // Put things hidden from sysops in the oversight log
467 if ( ( $params['newBits'] |
$params['oldBits'] ) & $this->getSuppressBit() ) {
468 $logType = 'suppress';
472 // Add params for effected page and ids
473 $logParams = $this->getLogParams( $params );
474 // Actually add the deletion log entry
475 $log = new LogPage( $logType );
476 $logid = $log->addEntry( $this->getLogAction(), $params['title'],
477 $params['comment'], $logParams );
478 // Allow for easy searching of deletion log items for revision/log items
479 $log->addRelations( $field, $params['ids'], $logid );
480 $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
481 $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
485 * Get the log action for this list type
487 public function getLogAction() {
492 * Get log parameter array.
493 * @param $params Associative array of log parameters, same as updateLog()
496 public function getLogParams( $params ) {
499 implode( ',', $params['ids'] ),
500 "ofield={$params['oldBits']}",
501 "nfield={$params['newBits']}"
506 * Initialise the current iteration pointer
508 protected function initCurrent() {
509 $row = $this->res
->current();
511 $this->current
= $this->newItem( $row );
513 $this->current
= false;
518 * Start iteration. This must be called before current() or next().
519 * @return First list item
521 public function reset() {
523 $this->res
= $this->doQuery( wfGetDB( DB_SLAVE
) );
525 $this->res
->rewind();
527 $this->initCurrent();
528 return $this->current
;
532 * Get the current list item, or false if we are at the end
534 public function current() {
535 return $this->current
;
539 * Move the iteration pointer to the next list item, and return it.
541 public function next() {
543 $this->initCurrent();
544 return $this->current
;
548 * Get the number of items in the list.
550 public function length() {
554 return $this->res
->numRows();
559 * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
562 public function clearFileOps() {
566 * A hook for setVisibility(): do batch updates pre-commit.
570 public function doPreCommitUpdates() {
571 return Status
::newGood();
575 * A hook for setVisibility(): do any necessary updates post-commit.
579 public function doPostCommitUpdates() {
580 return Status
::newGood();
584 * Create an item object from a DB result row
585 * @param $row stdclass
587 abstract public function newItem( $row );
590 * Do the DB query to iterate through the objects.
591 * @param $db Database object to use for the query
593 abstract public function doQuery( $db );
596 * Get the integer value of the flag used for suppression
598 abstract public function getSuppressBit();
602 * Abstract base class for deletable items
604 abstract class RevDel_Item
{
605 /** The parent SpecialPage */
608 /** The parent RevDel_List */
611 /** The DB result row */
615 * @param $list RevDel_List
616 * @param $row DB result row
618 public function __construct( $list, $row ) {
619 $this->special
= $list->special
;
625 * Get the ID, as it would appear in the ids URL parameter
627 public function getId() {
628 $field = $this->list->getIdField();
629 return $this->row
->$field;
633 * Get the date, formatted with $wgLang
635 public function formatDate() {
637 return $wgLang->date( $this->getTimestamp() );
641 * Get the time, formatted with $wgLang
643 public function formatTime() {
645 return $wgLang->time( $this->getTimestamp() );
649 * Get the timestamp in MW 14-char form
651 public function getTimestamp() {
652 $field = $this->list->getTimestampField();
653 return wfTimestamp( TS_MW
, $this->row
->$field );
657 * Get the author user ID
659 public function getAuthorId() {
660 $field = $this->list->getAuthorIdField();
661 return intval( $this->row
->$field );
665 * Get the author user name
667 public function getAuthorName() {
668 $field = $this->list->getAuthorNameField();
669 return strval( $this->row
->$field );
673 * Returns true if the item is "current", and the operation to set the given
674 * bits can't be executed for that reason
677 public function isHideCurrentOp( $newBits ) {
682 * Returns true if the current user can view the item
684 abstract public function canView();
687 * Returns true if the current user can view the item text/file
689 abstract public function canViewContent();
692 * Get the current deletion bitfield value
694 abstract public function getBits();
697 * Get the HTML of the list item. Should be include <li></li> tags.
698 * This is used to show the list in HTML form, by the special page.
700 abstract public function getHTML();
703 * Set the visibility of the item. This should do any necessary DB queries.
705 * The DB update query should have a condition which forces it to only update
706 * if the value in the DB matches the value fetched earlier with the SELECT.
707 * If the update fails because it did not match, the function should return
708 * false. This prevents concurrency problems.
710 * @return boolean success
712 abstract public function setBits( $newBits );
716 * List for revision table items
718 class RevDel_RevisionList
extends RevDel_List
{
720 var $type = 'revision';
721 var $idField = 'rev_id';
722 var $dateField = 'rev_timestamp';
723 var $authorIdField = 'rev_user';
724 var $authorNameField = 'rev_user_text';
726 public function doQuery( $db ) {
727 $ids = array_map( 'intval', $this->ids
);
728 return $db->select( array('revision','page'), '*',
730 'rev_page' => $this->title
->getArticleID(),
735 array( 'ORDER BY' => 'rev_id DESC' )
739 public function newItem( $row ) {
740 return new RevDel_RevisionItem( $this, $row );
743 public function getCurrent() {
744 if ( is_null( $this->currentRevId
) ) {
745 $dbw = wfGetDB( DB_MASTER
);
746 $this->currentRevId
= $dbw->selectField(
747 'page', 'page_latest', $this->title
->pageCond(), __METHOD__
);
749 return $this->currentRevId
;
752 public function getSuppressBit() {
753 return Revision
::DELETED_RESTRICTED
;
756 public function doPreCommitUpdates() {
757 $this->title
->invalidateCache();
758 return Status
::newGood();
761 public function doPostCommitUpdates() {
762 $this->title
->purgeSquid();
763 // Extensions that require referencing previous revisions may need this
764 wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title
) );
765 return Status
::newGood();
770 * Item class for a revision table row
772 class RevDel_RevisionItem
extends RevDel_Item
{
775 public function __construct( $list, $row ) {
776 parent
::__construct( $list, $row );
777 $this->revision
= new Revision( $row );
780 public function canView() {
781 return $this->revision
->userCan( Revision
::DELETED_RESTRICTED
);
784 public function canViewContent() {
785 return $this->revision
->userCan( Revision
::DELETED_TEXT
);
788 public function getBits() {
789 return $this->revision
->mDeleted
;
792 public function setBits( $bits ) {
793 $dbw = wfGetDB( DB_MASTER
);
794 // Update revision table
795 $dbw->update( 'revision',
796 array( 'rev_deleted' => $bits ),
798 'rev_id' => $this->revision
->getId(),
799 'rev_page' => $this->revision
->getPage(),
800 'rev_deleted' => $this->getBits()
804 if ( !$dbw->affectedRows() ) {
808 // Update recentchanges table
809 $dbw->update( 'recentchanges',
811 'rc_deleted' => $bits,
815 'rc_this_oldid' => $this->revision
->getId(), // condition
816 // non-unique timestamp index
817 'rc_timestamp' => $dbw->timestamp( $this->revision
->getTimestamp() ),
824 public function isDeleted() {
825 return $this->revision
->isDeleted( Revision
::DELETED_TEXT
);
828 public function isHideCurrentOp( $newBits ) {
829 return ( $newBits & Revision
::DELETED_TEXT
)
830 && $this->list->getCurrent() == $this->getId();
834 * Get the HTML link to the revision text.
835 * Overridden by RevDel_ArchiveItem.
837 protected function getRevisionLink() {
839 $date = $wgLang->timeanddate( $this->revision
->getTimestamp(), true );
840 if ( $this->isDeleted() && !$this->canViewContent() ) {
843 return $this->special
->skin
->link(
848 'oldid' => $this->revision
->getId(),
855 * Get the HTML link to the diff.
856 * Overridden by RevDel_ArchiveItem
858 protected function getDiffLink() {
859 if ( $this->isDeleted() && !$this->canViewContent() ) {
860 return wfMsgHtml('diff');
863 $this->special
->skin
->link(
868 'diff' => $this->revision
->getId(),
880 public function getHTML() {
881 $difflink = $this->getDiffLink();
882 $revlink = $this->getRevisionLink();
883 $userlink = $this->special
->skin
->revUserLink( $this->revision
);
884 $comment = $this->special
->skin
->revComment( $this->revision
);
885 if ( $this->isDeleted() ) {
886 $revlink = "<span class=\"history-deleted\">$revlink</span>";
888 return "<li>($difflink) $revlink $userlink $comment</li>";
893 * List for archive table items, i.e. revisions deleted via action=delete
895 class RevDel_ArchiveList
extends RevDel_RevisionList
{
896 var $type = 'archive';
897 var $idField = 'ar_timestamp';
898 var $dateField = 'ar_timestamp';
899 var $authorIdField = 'ar_user';
900 var $authorNameField = 'ar_user_text';
902 public function doQuery( $db ) {
903 $timestamps = array();
904 foreach ( $this->ids
as $id ) {
905 $timestamps[] = $db->timestamp( $id );
907 return $db->select( 'archive', '*',
909 'ar_namespace' => $this->title
->getNamespace(),
910 'ar_title' => $this->title
->getDBkey(),
911 'ar_timestamp' => $timestamps
914 array( 'ORDER BY' => 'ar_timestamp DESC' )
918 public function newItem( $row ) {
919 return new RevDel_ArchiveItem( $this, $row );
922 public function doPreCommitUpdates() {
923 return Status
::newGood();
926 public function doPostCommitUpdates() {
927 return Status
::newGood();
932 * Item class for a archive table row
934 class RevDel_ArchiveItem
extends RevDel_RevisionItem
{
935 public function __construct( $list, $row ) {
936 RevDel_Item
::__construct( $list, $row );
937 $this->revision
= Revision
::newFromArchiveRow( $row,
938 array( 'page' => $this->list->title
->getArticleId() ) );
941 public function getId() {
942 # Convert DB timestamp to MW timestamp
943 return $this->revision
->getTimestamp();
946 public function setBits( $bits ) {
947 $dbw = wfGetDB( DB_MASTER
);
948 $dbw->update( 'archive',
949 array( 'ar_deleted' => $bits ),
950 array( 'ar_namespace' => $this->list->title
->getNamespace(),
951 'ar_title' => $this->list->title
->getDBkey(),
952 // use timestamp for index
953 'ar_timestamp' => $this->row
->ar_timestamp
,
954 'ar_rev_id' => $this->row
->ar_rev_id
,
955 'ar_deleted' => $this->getBits()
958 return (bool)$dbw->affectedRows();
961 protected function getRevisionLink() {
963 $undelete = SpecialPage
::getTitleFor( 'Undelete' );
964 $date = $wgLang->timeanddate( $this->revision
->getTimestamp(), true );
965 if ( $this->isDeleted() && !$this->canViewContent() ) {
968 return $this->special
->skin
->link( $undelete, $date, array(),
970 'target' => $this->list->title
->getPrefixedText(),
971 'timestamp' => $this->revision
->getTimestamp()
975 protected function getDiffLink() {
976 if ( $this->isDeleted() && !$this->canViewContent() ) {
977 return wfMsgHtml( 'diff' );
979 $undelete = SpecialPage
::getTitleFor( 'Undelete' );
980 return $this->special
->skin
->link( $undelete, wfMsgHtml('diff'), array(),
982 'target' => $this->list->title
->getPrefixedText(),
984 'timestamp' => $this->revision
->getTimestamp()
990 * List for oldimage table items
992 class RevDel_FileList
extends RevDel_List
{
993 var $type = 'oldimage';
994 var $idField = 'oi_archive_name';
995 var $dateField = 'oi_timestamp';
996 var $authorIdField = 'oi_user';
997 var $authorNameField = 'oi_user_text';
998 var $storeBatch, $deleteBatch, $cleanupBatch;
1000 public function doQuery( $db ) {
1001 $archiveName = array();
1002 foreach( $this->ids
as $timestamp ) {
1003 $archiveNames[] = $timestamp . '!' . $this->title
->getDBkey();
1005 return $db->select( 'oldimage', '*',
1007 'oi_name' => $this->title
->getDBkey(),
1008 'oi_archive_name' => $archiveNames
1011 array( 'ORDER BY' => 'oi_timestamp DESC' )
1015 public function newItem( $row ) {
1016 return new RevDel_FileItem( $this, $row );
1019 public function clearFileOps() {
1020 $this->deleteBatch
= array();
1021 $this->storeBatch
= array();
1022 $this->cleanupBatch
= array();
1025 public function doPreCommitUpdates() {
1026 $status = Status
::newGood();
1027 $repo = RepoGroup
::singleton()->getLocalRepo();
1028 if ( $this->storeBatch
) {
1029 $status->merge( $repo->storeBatch( $this->storeBatch
, FileRepo
::OVERWRITE_SAME
) );
1031 if ( !$status->isOK() ) {
1034 if ( $this->deleteBatch
) {
1035 $status->merge( $repo->deleteBatch( $this->deleteBatch
) );
1037 if ( !$status->isOK() ) {
1038 // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
1039 // modified (but destined for rollback) causes data loss
1042 if ( $this->cleanupBatch
) {
1043 $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch
) );
1048 public function doPostCommitUpdates() {
1049 $file = wfLocalFile( $this->title
);
1050 $file->purgeCache();
1051 $file->purgeDescription();
1052 return Status
::newGood();
1055 public function getSuppressBit() {
1056 return File
::DELETED_RESTRICTED
;
1061 * Item class for an oldimage table row
1063 class RevDel_FileItem
extends RevDel_Item
{
1066 public function __construct( $list, $row ) {
1067 parent
::__construct( $list, $row );
1068 $this->file
= RepoGroup
::singleton()->getLocalRepo()->newFileFromRow( $row );
1071 public function getId() {
1072 $parts = explode( '!', $this->row
->oi_archive_name
);
1076 public function canView() {
1077 return $this->file
->userCan( File
::DELETED_RESTRICTED
);
1080 public function canViewContent() {
1081 return $this->file
->userCan( File
::DELETED_FILE
);
1084 public function getBits() {
1085 return $this->file
->getVisibility();
1088 public function setBits( $bits ) {
1090 # FIXME: move to LocalFile.php
1091 if ( $this->isDeleted() ) {
1092 if ( $bits & File
::DELETED_FILE
) {
1096 $key = $this->file
->getStorageKey();
1097 $srcRel = $this->file
->repo
->getDeletedHashPath( $key ) . $key;
1098 $this->list->storeBatch
[] = array(
1099 $this->file
->repo
->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
1101 $this->file
->getRel()
1103 $this->list->cleanupBatch
[] = $key;
1105 } elseif ( $bits & File
::DELETED_FILE
) {
1107 $key = $this->file
->getStorageKey();
1108 $dstRel = $this->file
->repo
->getDeletedHashPath( $key ) . $key;
1109 $this->list->deleteBatch
[] = array( $this->file
->getRel(), $dstRel );
1112 # Do the database operations
1113 $dbw = wfGetDB( DB_MASTER
);
1114 $dbw->update( 'oldimage',
1115 array( 'oi_deleted' => $bits ),
1117 'oi_name' => $this->row
->oi_name
,
1118 'oi_timestamp' => $this->row
->oi_timestamp
,
1119 'oi_deleted' => $this->getBits()
1123 return (bool)$dbw->affectedRows();
1126 public function isDeleted() {
1127 return $this->file
->isDeleted( File
::DELETED_FILE
);
1131 * Get the link to the file.
1132 * Overridden by RevDel_ArchivedFileItem.
1134 protected function getLink() {
1135 global $wgLang, $wgUser;
1136 $date = $wgLang->timeanddate( $this->file
->getTimestamp(), true );
1137 if ( $this->isDeleted() ) {
1139 if ( !$this->canViewContent() ) {
1142 $link = $this->special
->skin
->link(
1143 $this->special
->getTitle(),
1146 'target' => $this->list->title
->getPrefixedText(),
1147 'file' => $this->file
->getArchiveName(),
1148 'token' => $wgUser->editToken( $this->file
->getArchiveName() )
1152 return '<span class="history-deleted">' . $link . '</span>';
1155 $url = $this->file
->getUrl();
1156 return Xml
::element( 'a', array( 'href' => $this->file
->getUrl() ), $date );
1160 * Generate a user tool link cluster if the current user is allowed to view it
1161 * @return string HTML
1163 protected function getUserTools() {
1164 if( $this->file
->userCan( Revision
::DELETED_USER
) ) {
1165 $link = $this->special
->skin
->userLink( $this->file
->user
, $this->file
->user_text
) .
1166 $this->special
->skin
->userToolLinks( $this->file
->user
, $this->file
->user_text
);
1168 $link = wfMsgHtml( 'rev-deleted-user' );
1170 if( $this->file
->isDeleted( Revision
::DELETED_USER
) ) {
1171 return '<span class="history-deleted">' . $link . '</span>';
1177 * Wrap and format the file's comment block, if the current
1178 * user is allowed to view it.
1180 * @return string HTML
1182 protected function getComment() {
1183 if( $this->file
->userCan( File
::DELETED_COMMENT
) ) {
1184 $block = $this->special
->skin
->commentBlock( $this->file
->description
);
1186 $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
1188 if( $this->file
->isDeleted( File
::DELETED_COMMENT
) ) {
1189 return "<span class=\"history-deleted\">$block</span>";
1194 public function getHTML() {
1199 $wgLang->formatNum( $this->file
->getWidth() ),
1200 $wgLang->formatNum( $this->file
->getHeight() )
1203 wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file
->getSize() ) ) .
1205 $pageLink = $this->getLink();
1207 return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
1208 $data . ' ' . $this->getComment(). '</li>';
1213 * List for filearchive table items
1215 class RevDel_ArchivedFileList
extends RevDel_FileList
{
1216 var $type = 'filearchive';
1217 var $idField = 'fa_id';
1218 var $dateField = 'fa_timestamp';
1219 var $authorIdField = 'fa_user';
1220 var $authorNameField = 'fa_user_text';
1222 public function doQuery( $db ) {
1223 $ids = array_map( 'intval', $this->ids
);
1224 return $db->select( 'filearchive', '*',
1226 'fa_name' => $this->title
->getDBkey(),
1230 array( 'ORDER BY' => 'fa_id DESC' )
1234 public function newItem( $row ) {
1235 return new RevDel_ArchivedFileItem( $this, $row );
1240 * Item class for a filearchive table row
1242 class RevDel_ArchivedFileItem
extends RevDel_FileItem
{
1243 public function __construct( $list, $row ) {
1244 RevDel_Item
::__construct( $list, $row );
1245 $this->file
= ArchivedFile
::newFromRow( $row );
1248 public function getId() {
1249 return $this->row
->fa_id
;
1252 public function setBits( $bits ) {
1253 $dbw = wfGetDB( DB_MASTER
);
1254 $dbw->update( 'filearchive',
1255 array( 'fa_deleted' => $bits ),
1257 'fa_id' => $this->row
->fa_id
,
1258 'fa_deleted' => $this->getBits(),
1262 return (bool)$dbw->affectedRows();
1265 protected function getLink() {
1266 global $wgLang, $wgUser;
1267 $date = $wgLang->timeanddate( $this->file
->getTimestamp(), true );
1268 $undelete = SpecialPage
::getTitleFor( 'Undelete' );
1269 $key = $this->file
->getKey();
1271 if( !$this->canViewContent() ) {
1274 $link = $this->special
->skin
->link( $undelete, $date, array(),
1276 'target' => $this->list->title
->getPrefixedText(),
1278 'token' => $wgUser->editToken( $key )
1282 if( $this->isDeleted() ) {
1283 $link = '<span class="history-deleted">' . $link . '</span>';
1290 * List for logging table items
1292 class RevDel_LogList
extends RevDel_List
{
1293 var $type = 'logging';
1294 var $idField = 'log_id';
1295 var $dateField = 'log_timestamp';
1296 var $authorIdField = 'log_user';
1297 var $authorNameField = 'log_user_text';
1299 public function doQuery( $db ) {
1300 global $wgMessageCache;
1301 $wgMessageCache->loadAllMessages();
1302 $ids = array_map( 'intval', $this->ids
);
1303 return $db->select( 'logging', '*',
1304 array( 'log_id' => $ids ),
1306 array( 'ORDER BY' => 'log_id DESC' )
1310 public function newItem( $row ) {
1311 return new RevDel_LogItem( $this, $row );
1314 public function getSuppressBit() {
1315 return Revision
::DELETED_RESTRICTED
;
1318 public function getLogAction() {
1322 public function getLogParams( $params ) {
1324 implode( ',', $params['ids'] ),
1325 "ofield={$params['oldBits']}",
1326 "nfield={$params['newBits']}"
1332 * Item class for a logging table row
1334 class RevDel_LogItem
extends RevDel_Item
{
1335 public function canView() {
1336 return LogEventsList
::userCan( $this->row
, Revision
::DELETED_RESTRICTED
);
1339 public function canViewContent() {
1340 return true; // none
1343 public function getBits() {
1344 return $this->row
->log_deleted
;
1347 public function setBits( $bits ) {
1348 $dbw = wfGetDB( DB_MASTER
);
1349 $dbw->update( 'recentchanges',
1351 'rc_deleted' => $bits,
1355 'rc_logid' => $this->row
->log_id
,
1356 'rc_timestamp' => $this->row
->log_timestamp
// index
1360 $dbw->update( 'logging',
1361 array( 'log_deleted' => $bits ),
1363 'log_id' => $this->row
->log_id
,
1364 'log_deleted' => $this->getBits()
1368 return (bool)$dbw->affectedRows();
1371 public function getHTML() {
1374 $date = htmlspecialchars( $wgLang->timeanddate( $this->row
->log_timestamp
) );
1375 $paramArray = LogPage
::extractParams( $this->row
->log_params
);
1376 $title = Title
::makeTitle( $this->row
->log_namespace
, $this->row
->log_title
);
1378 // Log link for this page
1379 $loglink = $this->special
->skin
->link(
1380 SpecialPage
::getTitleFor( 'Log' ),
1383 array( 'page' => $title->getPrefixedText() )
1386 if( !$this->canView() ) {
1387 $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
1389 $action = LogPage
::actionText( $this->row
->log_type
, $this->row
->log_action
, $title,
1390 $this->special
->skin
, $paramArray, true, true );
1391 if( $this->row
->log_deleted
& LogPage
::DELETED_ACTION
)
1392 $action = '<span class="history-deleted">' . $action . '</span>';
1395 $userLink = $this->special
->skin
->userLink( $this->row
->log_user
,
1396 User
::WhoIs( $this->row
->log_user
) );
1397 if( LogEventsList
::isDeleted($this->row
,LogPage
::DELETED_USER
) ) {
1398 $userLink = '<span class="history-deleted">' . $userLink . '</span>';
1401 $comment = $wgLang->getDirMark() . $this->special
->skin
->commentBlock( $this->row
->log_comment
);
1402 if( LogEventsList
::isDeleted($this->row
,LogPage
::DELETED_COMMENT
) ) {
1403 $comment = '<span class="history-deleted">' . $comment . '</span>';
1405 return "<li>($loglink) $date $userLink $action $comment</li>";