3 * List for revision table items
5 * This will check both the 'revision' table for live revisions and the
6 * 'archive' table for traditionally-deleted revisions that have an
9 * See RevDel_RevisionItem and RevDel_ArchivedRevisionItem for items.
11 class RevDel_RevisionList
extends RevDel_List
{
14 public function getType() {
18 public static function getRelationType() {
23 * @param $db DatabaseBase
26 public function doQuery( $db ) {
27 $ids = array_map( 'intval', $this->ids
);
28 $live = $db->select( array('revision','page'), '*',
30 'rev_page' => $this->title
->getArticleID(),
35 array( 'ORDER BY' => 'rev_id DESC' )
38 if ( $live->numRows() >= count( $ids ) ) {
39 // All requested revisions are live, keeps things simple!
43 // Check if any requested revisions are available fully deleted.
44 $archived = $db->select( array( 'archive' ), '*',
49 array( 'ORDER BY' => 'ar_rev_id DESC' )
52 if ( $archived->numRows() == 0 ) {
54 } elseif ( $live->numRows() == 0 ) {
57 // Combine the two! Whee
59 foreach ( $live as $row ) {
60 $rows[$row->rev_id
] = $row;
62 foreach ( $archived as $row ) {
63 $rows[$row->ar_rev_id
] = $row;
66 return new FakeResultWrapper( array_values( $rows ) );
70 public function newItem( $row ) {
71 if ( isset( $row->rev_id
) ) {
72 return new RevDel_RevisionItem( $this, $row );
73 } elseif ( isset( $row->ar_rev_id
) ) {
74 return new RevDel_ArchivedRevisionItem( $this, $row );
76 // This shouldn't happen. :)
77 throw new MWException( 'Invalid row type in RevDel_RevisionList' );
81 public function getCurrent() {
82 if ( is_null( $this->currentRevId
) ) {
83 $dbw = wfGetDB( DB_MASTER
);
84 $this->currentRevId
= $dbw->selectField(
85 'page', 'page_latest', $this->title
->pageCond(), __METHOD__
);
87 return $this->currentRevId
;
90 public function getSuppressBit() {
91 return Revision
::DELETED_RESTRICTED
;
94 public function doPreCommitUpdates() {
95 $this->title
->invalidateCache();
96 return Status
::newGood();
99 public function doPostCommitUpdates() {
100 $this->title
->purgeSquid();
101 // Extensions that require referencing previous revisions may need this
102 wfRunHooks( 'ArticleRevisionVisibilitySet', array( &$this->title
) );
103 return Status
::newGood();
108 * Item class for a live revision table row
110 class RevDel_RevisionItem
extends RevDel_Item
{
113 public function __construct( $list, $row ) {
114 parent
::__construct( $list, $row );
115 $this->revision
= new Revision( $row );
118 public function getIdField() {
122 public function getTimestampField() {
123 return 'rev_timestamp';
126 public function getAuthorIdField() {
130 public function getAuthorNameField() {
131 return 'rev_user_text';
134 public function canView() {
135 return $this->revision
->userCan( Revision
::DELETED_RESTRICTED
);
138 public function canViewContent() {
139 return $this->revision
->userCan( Revision
::DELETED_TEXT
);
142 public function getBits() {
143 return $this->revision
->mDeleted
;
146 public function setBits( $bits ) {
147 $dbw = wfGetDB( DB_MASTER
);
148 // Update revision table
149 $dbw->update( 'revision',
150 array( 'rev_deleted' => $bits ),
152 'rev_id' => $this->revision
->getId(),
153 'rev_page' => $this->revision
->getPage(),
154 'rev_deleted' => $this->getBits()
158 if ( !$dbw->affectedRows() ) {
162 // Update recentchanges table
163 $dbw->update( 'recentchanges',
165 'rc_deleted' => $bits,
169 'rc_this_oldid' => $this->revision
->getId(), // condition
170 // non-unique timestamp index
171 'rc_timestamp' => $dbw->timestamp( $this->revision
->getTimestamp() ),
178 public function isDeleted() {
179 return $this->revision
->isDeleted( Revision
::DELETED_TEXT
);
182 public function isHideCurrentOp( $newBits ) {
183 return ( $newBits & Revision
::DELETED_TEXT
)
184 && $this->list->getCurrent() == $this->getId();
188 * Get the HTML link to the revision text.
189 * Overridden by RevDel_ArchiveItem.
191 protected function getRevisionLink() {
192 $date = $this->list->getLang()->timeanddate( $this->revision
->getTimestamp(), true );
193 if ( $this->isDeleted() && !$this->canViewContent() ) {
201 'oldid' => $this->revision
->getId(),
208 * Get the HTML link to the diff.
209 * Overridden by RevDel_ArchiveItem
211 protected function getDiffLink() {
212 if ( $this->isDeleted() && !$this->canViewContent() ) {
213 return wfMsgHtml('diff');
221 'diff' => $this->revision
->getId(),
233 public function getHTML() {
234 $difflink = $this->getDiffLink();
235 $revlink = $this->getRevisionLink();
236 $userlink = Linker
::revUserLink( $this->revision
);
237 $comment = Linker
::revComment( $this->revision
);
238 if ( $this->isDeleted() ) {
239 $revlink = "<span class=\"history-deleted\">$revlink</span>";
241 return "<li>($difflink) $revlink $userlink $comment</li>";
246 * List for archive table items, i.e. revisions deleted via action=delete
248 class RevDel_ArchiveList
extends RevDel_RevisionList
{
249 public function getType() {
253 public static function getRelationType() {
254 return 'ar_timestamp';
258 * @param $db DatabaseBase
261 public function doQuery( $db ) {
262 $timestamps = array();
263 foreach ( $this->ids
as $id ) {
264 $timestamps[] = $db->timestamp( $id );
266 return $db->select( 'archive', '*',
268 'ar_namespace' => $this->title
->getNamespace(),
269 'ar_title' => $this->title
->getDBkey(),
270 'ar_timestamp' => $timestamps
273 array( 'ORDER BY' => 'ar_timestamp DESC' )
277 public function newItem( $row ) {
278 return new RevDel_ArchiveItem( $this, $row );
281 public function doPreCommitUpdates() {
282 return Status
::newGood();
285 public function doPostCommitUpdates() {
286 return Status
::newGood();
291 * Item class for a archive table row
293 class RevDel_ArchiveItem
extends RevDel_RevisionItem
{
294 public function __construct( $list, $row ) {
295 RevDel_Item
::__construct( $list, $row );
296 $this->revision
= Revision
::newFromArchiveRow( $row,
297 array( 'page' => $this->list->title
->getArticleId() ) );
300 public function getIdField() {
301 return 'ar_timestamp';
304 public function getTimestampField() {
305 return 'ar_timestamp';
308 public function getAuthorIdField() {
312 public function getAuthorNameField() {
313 return 'ar_user_text';
316 public function getId() {
317 # Convert DB timestamp to MW timestamp
318 return $this->revision
->getTimestamp();
321 public function setBits( $bits ) {
322 $dbw = wfGetDB( DB_MASTER
);
323 $dbw->update( 'archive',
324 array( 'ar_deleted' => $bits ),
326 'ar_namespace' => $this->list->title
->getNamespace(),
327 'ar_title' => $this->list->title
->getDBkey(),
328 // use timestamp for index
329 'ar_timestamp' => $this->row
->ar_timestamp
,
330 'ar_rev_id' => $this->row
->ar_rev_id
,
331 'ar_deleted' => $this->getBits()
334 return (bool)$dbw->affectedRows();
337 protected function getRevisionLink() {
338 $undelete = SpecialPage
::getTitleFor( 'Undelete' );
339 $date = $this->list->getLang()->timeanddate( $this->revision
->getTimestamp(), true );
340 if ( $this->isDeleted() && !$this->canViewContent() ) {
343 return Linker
::link( $undelete, $date, array(),
345 'target' => $this->list->title
->getPrefixedText(),
346 'timestamp' => $this->revision
->getTimestamp()
350 protected function getDiffLink() {
351 if ( $this->isDeleted() && !$this->canViewContent() ) {
352 return wfMsgHtml( 'diff' );
354 $undelete = SpecialPage
::getTitleFor( 'Undelete' );
355 return Linker
::link( $undelete, wfMsgHtml('diff'), array(),
357 'target' => $this->list->title
->getPrefixedText(),
359 'timestamp' => $this->revision
->getTimestamp()
366 * Item class for a archive table row by ar_rev_id -- actually
367 * used via RevDel_RevisionList.
369 class RevDel_ArchivedRevisionItem
extends RevDel_ArchiveItem
{
370 public function __construct( $list, $row ) {
371 RevDel_Item
::__construct( $list, $row );
373 $this->revision
= Revision
::newFromArchiveRow( $row,
374 array( 'page' => $this->list->title
->getArticleId() ) );
377 public function getIdField() {
381 public function getId() {
382 return $this->revision
->getId();
385 public function setBits( $bits ) {
386 $dbw = wfGetDB( DB_MASTER
);
387 $dbw->update( 'archive',
388 array( 'ar_deleted' => $bits ),
389 array( 'ar_rev_id' => $this->row
->ar_rev_id
,
390 'ar_deleted' => $this->getBits()
393 return (bool)$dbw->affectedRows();
398 * List for oldimage table items
400 class RevDel_FileList
extends RevDel_List
{
401 public function getType() {
405 public static function getRelationType() {
406 return 'oi_archive_name';
409 var $storeBatch, $deleteBatch, $cleanupBatch;
412 * @param $db DatabaseBase
415 public function doQuery( $db ) {
416 $archiveNames = array();
417 foreach( $this->ids
as $timestamp ) {
418 $archiveNames[] = $timestamp . '!' . $this->title
->getDBkey();
420 return $db->select( 'oldimage', '*',
422 'oi_name' => $this->title
->getDBkey(),
423 'oi_archive_name' => $archiveNames
426 array( 'ORDER BY' => 'oi_timestamp DESC' )
430 public function newItem( $row ) {
431 return new RevDel_FileItem( $this, $row );
434 public function clearFileOps() {
435 $this->deleteBatch
= array();
436 $this->storeBatch
= array();
437 $this->cleanupBatch
= array();
440 public function doPreCommitUpdates() {
441 $status = Status
::newGood();
442 $repo = RepoGroup
::singleton()->getLocalRepo();
443 if ( $this->storeBatch
) {
444 $status->merge( $repo->storeBatch( $this->storeBatch
, FileRepo
::OVERWRITE_SAME
) );
446 if ( !$status->isOK() ) {
449 if ( $this->deleteBatch
) {
450 $status->merge( $repo->deleteBatch( $this->deleteBatch
) );
452 if ( !$status->isOK() ) {
453 // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
454 // modified (but destined for rollback) causes data loss
457 if ( $this->cleanupBatch
) {
458 $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch
) );
463 public function doPostCommitUpdates() {
464 $file = wfLocalFile( $this->title
);
466 $file->purgeDescription();
467 return Status
::newGood();
470 public function getSuppressBit() {
471 return File
::DELETED_RESTRICTED
;
476 * Item class for an oldimage table row
478 class RevDel_FileItem
extends RevDel_Item
{
485 public function __construct( $list, $row ) {
486 parent
::__construct( $list, $row );
487 $this->file
= RepoGroup
::singleton()->getLocalRepo()->newFileFromRow( $row );
490 public function getIdField() {
491 return 'oi_archive_name';
494 public function getTimestampField() {
495 return 'oi_timestamp';
498 public function getAuthorIdField() {
502 public function getAuthorNameField() {
503 return 'oi_user_text';
506 public function getId() {
507 $parts = explode( '!', $this->row
->oi_archive_name
);
511 public function canView() {
512 return $this->file
->userCan( File
::DELETED_RESTRICTED
);
515 public function canViewContent() {
516 return $this->file
->userCan( File
::DELETED_FILE
);
519 public function getBits() {
520 return $this->file
->getVisibility();
523 public function setBits( $bits ) {
525 # @todo FIXME: Move to LocalFile.php
526 if ( $this->isDeleted() ) {
527 if ( $bits & File
::DELETED_FILE
) {
531 $key = $this->file
->getStorageKey();
532 $srcRel = $this->file
->repo
->getDeletedHashPath( $key ) . $key;
533 $this->list->storeBatch
[] = array(
534 $this->file
->repo
->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
536 $this->file
->getRel()
538 $this->list->cleanupBatch
[] = $key;
540 } elseif ( $bits & File
::DELETED_FILE
) {
542 $key = $this->file
->getStorageKey();
543 $dstRel = $this->file
->repo
->getDeletedHashPath( $key ) . $key;
544 $this->list->deleteBatch
[] = array( $this->file
->getRel(), $dstRel );
547 # Do the database operations
548 $dbw = wfGetDB( DB_MASTER
);
549 $dbw->update( 'oldimage',
550 array( 'oi_deleted' => $bits ),
552 'oi_name' => $this->row
->oi_name
,
553 'oi_timestamp' => $this->row
->oi_timestamp
,
554 'oi_deleted' => $this->getBits()
558 return (bool)$dbw->affectedRows();
561 public function isDeleted() {
562 return $this->file
->isDeleted( File
::DELETED_FILE
);
566 * Get the link to the file.
567 * Overridden by RevDel_ArchivedFileItem.
569 protected function getLink() {
570 $date = $this->list->getLang()->timeanddate( $this->file
->getTimestamp(), true );
571 if ( $this->isDeleted() ) {
573 if ( !$this->canViewContent() ) {
576 $revdelete = SpecialPage
::getTitleFor( 'Revisiondelete' );
577 $link = Linker
::link(
581 'target' => $this->list->title
->getPrefixedText(),
582 'file' => $this->file
->getArchiveName(),
583 'token' => $this->list->getUser()->editToken(
584 $this->file
->getArchiveName() )
588 return '<span class="history-deleted">' . $link . '</span>';
591 return Xml
::element( 'a', array( 'href' => $this->file
->getUrl() ), $date );
595 * Generate a user tool link cluster if the current user is allowed to view it
596 * @return string HTML
598 protected function getUserTools() {
599 if( $this->file
->userCan( Revision
::DELETED_USER
) ) {
600 $link = Linker
::userLink( $this->file
->user
, $this->file
->user_text
) .
601 Linker
::userToolLinks( $this->file
->user
, $this->file
->user_text
);
603 $link = wfMsgHtml( 'rev-deleted-user' );
605 if( $this->file
->isDeleted( Revision
::DELETED_USER
) ) {
606 return '<span class="history-deleted">' . $link . '</span>';
612 * Wrap and format the file's comment block, if the current
613 * user is allowed to view it.
615 * @return string HTML
617 protected function getComment() {
618 if( $this->file
->userCan( File
::DELETED_COMMENT
) ) {
619 $block = Linker
::commentBlock( $this->file
->description
);
621 $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
623 if( $this->file
->isDeleted( File
::DELETED_COMMENT
) ) {
624 return "<span class=\"history-deleted\">$block</span>";
629 public function getHTML() {
633 $this->list->getLang()->formatNum( $this->file
->getWidth() ),
634 $this->list->getLang()->formatNum( $this->file
->getHeight() )
637 wfMsgExt( 'nbytes', 'parsemag', $this->list->getLang()->formatNum( $this->file
->getSize() ) ) .
640 return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
641 $data . ' ' . $this->getComment(). '</li>';
646 * List for filearchive table items
648 class RevDel_ArchivedFileList
extends RevDel_FileList
{
649 public function getType() {
650 return 'filearchive';
653 public static function getRelationType() {
658 * @param $db DatabaseBase
661 public function doQuery( $db ) {
662 $ids = array_map( 'intval', $this->ids
);
663 return $db->select( 'filearchive', '*',
665 'fa_name' => $this->title
->getDBkey(),
669 array( 'ORDER BY' => 'fa_id DESC' )
673 public function newItem( $row ) {
674 return new RevDel_ArchivedFileItem( $this, $row );
679 * Item class for a filearchive table row
681 class RevDel_ArchivedFileItem
extends RevDel_FileItem
{
682 public function __construct( $list, $row ) {
683 RevDel_Item
::__construct( $list, $row );
684 $this->file
= ArchivedFile
::newFromRow( $row );
687 public function getIdField() {
691 public function getTimestampField() {
692 return 'fa_timestamp';
695 public function getAuthorIdField() {
699 public function getAuthorNameField() {
700 return 'fa_user_text';
703 public function getId() {
704 return $this->row
->fa_id
;
707 public function setBits( $bits ) {
708 $dbw = wfGetDB( DB_MASTER
);
709 $dbw->update( 'filearchive',
710 array( 'fa_deleted' => $bits ),
712 'fa_id' => $this->row
->fa_id
,
713 'fa_deleted' => $this->getBits(),
717 return (bool)$dbw->affectedRows();
720 protected function getLink() {
721 $date = $this->list->getLang()->timeanddate( $this->file
->getTimestamp(), true );
722 $undelete = SpecialPage
::getTitleFor( 'Undelete' );
723 $key = $this->file
->getKey();
725 if( !$this->canViewContent() ) {
728 $link = Linker
::link( $undelete, $date, array(),
730 'target' => $this->list->title
->getPrefixedText(),
732 'token' => $this->list->getUser()->editToken( $key )
736 if( $this->isDeleted() ) {
737 $link = '<span class="history-deleted">' . $link . '</span>';
744 * List for logging table items
746 class RevDel_LogList
extends RevDel_List
{
747 public function getType() {
751 public static function getRelationType() {
756 * @param $db DatabaseBase
759 public function doQuery( $db ) {
760 $ids = array_map( 'intval', $this->ids
);
761 return $db->select( 'logging', '*',
762 array( 'log_id' => $ids ),
764 array( 'ORDER BY' => 'log_id DESC' )
768 public function newItem( $row ) {
769 return new RevDel_LogItem( $this, $row );
772 public function getSuppressBit() {
773 return Revision
::DELETED_RESTRICTED
;
776 public function getLogAction() {
780 public function getLogParams( $params ) {
782 implode( ',', $params['ids'] ),
783 "ofield={$params['oldBits']}",
784 "nfield={$params['newBits']}"
790 * Item class for a logging table row
792 class RevDel_LogItem
extends RevDel_Item
{
793 public function getIdField() {
797 public function getTimestampField() {
798 return 'log_timestamp';
801 public function getAuthorIdField() {
805 public function getAuthorNameField() {
806 return 'log_user_text';
809 public function canView() {
810 return LogEventsList
::userCan( $this->row
, Revision
::DELETED_RESTRICTED
);
813 public function canViewContent() {
817 public function getBits() {
818 return $this->row
->log_deleted
;
821 public function setBits( $bits ) {
822 $dbw = wfGetDB( DB_MASTER
);
823 $dbw->update( 'recentchanges',
825 'rc_deleted' => $bits,
829 'rc_logid' => $this->row
->log_id
,
830 'rc_timestamp' => $this->row
->log_timestamp
// index
834 $dbw->update( 'logging',
835 array( 'log_deleted' => $bits ),
837 'log_id' => $this->row
->log_id
,
838 'log_deleted' => $this->getBits()
842 return (bool)$dbw->affectedRows();
845 public function getHTML() {
846 $date = htmlspecialchars( $this->list->getLang()->timeanddate( $this->row
->log_timestamp
) );
847 $paramArray = LogPage
::extractParams( $this->row
->log_params
);
848 $title = Title
::makeTitle( $this->row
->log_namespace
, $this->row
->log_title
);
850 // Log link for this page
851 $loglink = Linker
::link(
852 SpecialPage
::getTitleFor( 'Log' ),
855 array( 'page' => $title->getPrefixedText() )
858 if( !$this->canView() ) {
859 $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
861 $skin = $this->list->getUser()->getSkin();
862 $action = LogPage
::actionText( $this->row
->log_type
, $this->row
->log_action
,
863 $title, $skin, $paramArray, true, true );
864 if( $this->row
->log_deleted
& LogPage
::DELETED_ACTION
)
865 $action = '<span class="history-deleted">' . $action . '</span>';
868 $userLink = Linker
::userLink( $this->row
->log_user
,
869 User
::WhoIs( $this->row
->log_user
) );
870 if( LogEventsList
::isDeleted($this->row
,LogPage
::DELETED_USER
) ) {
871 $userLink = '<span class="history-deleted">' . $userLink . '</span>';
874 $comment = $this->list->getLang()->getDirMark() . Linker
::commentBlock( $this->row
->log_comment
);
875 if( LogEventsList
::isDeleted($this->row
,LogPage
::DELETED_COMMENT
) ) {
876 $comment = '<span class="history-deleted">' . $comment . '</span>';
878 return "<li>($loglink) $date $userLink $action $comment</li>";