r20446 - Code Review

From MediaWiki.org

Jump to: navigation, search
Repository:MediaWiki
Revision:r20445 | r20446 (on ViewVC) | r20447 >
Date:15:50, 14 March 2007
Author:aaron
Status:new
Tags:
Comment:*Merge in phase3_rev_deleted/includes
Modified paths:

Diff [purge]

Index: trunk/phase3/includes/Article.php
===================================================================
--- trunk/phase3/includes/Article.php	(revision 20445)
+++ trunk/phase3/includes/Article.php	(revision 20446)
@@ -1782,6 +1782,8 @@
 		$confirm = $wgRequest->wasPosted() &&
 			$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
 		$reason = $wgRequest->getText( 'wpReason' );
+		# Flag to hide all contents of the archived revisions
+		$suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('deleterevision');
 
 		# This code desperately needs to be totally rewritten
 
@@ -1813,7 +1815,7 @@
 		}
 
 		if( $confirm ) {
-			$this->doDelete( $reason );
+			$this->doDelete( $reason, $suppress );
 			if( $wgRequest->getCheck( 'wpWatch' ) ) {
 				$this->doWatch();
 			} elseif( $this->mTitle->userIsWatching() ) {
@@ -1959,7 +1961,14 @@
 		$delcom = htmlspecialchars( wfMsg( 'deletecomment' ) );
 		$token = htmlspecialchars( $wgUser->editToken() );
 		$watch = Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '2' ) );
-
+		if ( $wgUser->isAllowed( 'deleterevision' ) ) {
+			$supress = "<tr><td>&nbsp;</td><td>";
+			$supress .= Xml::checkLabel( wfMsg( 'revdelete-suppress' ), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '2' ) );
+			$supress .= "</td></tr>";
+		} else {
+			$supress = '';
+		}
+		
 		$wgOut->addHTML( "
 <form id='deleteconfirm' method='post' action=\"{$formaction}\">
 	<table border='0'>
@@ -1971,6 +1980,7 @@
 				<input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" tabindex=\"1\" />
 			</td>
 		</tr>
+		$supress
 		<tr>
 			<td>&nbsp;</td>
 			<td>$watch</td>
@@ -2009,12 +2019,12 @@
 	/**
 	 * Perform a deletion and output success or failure messages
 	 */
-	function doDelete( $reason ) {
+	function doDelete( $reason, $suppress = false ) {
 		global $wgOut, $wgUser;
 		wfDebug( __METHOD__."\n" );
 
 		if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
-			if ( $this->doDeleteArticle( $reason ) ) {
+			if ( $this->doDeleteArticle( $reason, $suppress ) ) {
 				$deleted = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
 
 				$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
@@ -2037,7 +2047,7 @@
 	 * Deletes the article with database consistency, writes logs, purges caches
 	 * Returns success
 	 */
-	function doDeleteArticle( $reason ) {
+	function doDeleteArticle( $reason, $suppress = false ) {
 		global $wgUseSquid, $wgDeferredUpdateList;
 		global $wgUseTrackbacks;
 
@@ -2055,6 +2065,17 @@
 		$u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 );
 		array_push( $wgDeferredUpdateList, $u );
 
+		// Bitfields to further supress the content
+		if ( $suppress ) {
+			$bitfield = 0;
+			$bitfield |= Revision::DELETED_TEXT;
+			$bitfield |= Revision::DELETED_COMMENT;
+			$bitfield |= Revision::DELETED_USER;
+			$bitfield |= Revision::DELETED_RESTRICTED;
+		} else {
+			$bitfield = 'rev_deleted';
+		}
+		
 		// For now, shunt the revision data into the archive table.
 		// Text is *not* removed from the text table; bulk storage
 		// is left intact to avoid breaking block-compression or
@@ -2078,7 +2099,7 @@
 				'ar_text_id'    => 'rev_text_id',
 				'ar_text'       => '\'\'', // Be explicit to appease
 				'ar_flags'      => '\'\'', // MySQL's "strict mode"...
-				'ar_len'		=> 'rev_len'
+				'ar_deleted'	=> $bitfield
 			), array(
 				'page_id' => $id,
 				'page_id = rev_page'
@@ -2119,8 +2140,9 @@
 		# Clear caches
 		Article::onArticleDelete( $this->mTitle );
 
-		# Log the deletion
-		$log = new LogPage( 'delete' );
+		# Log the deletion, if the page was suppressed, log it at Oversight instead
+		$logtype = ($suppress) ? 'oversight' : 'delete';
+		$log = new LogPage( $logtype );
 		$log->addEntry( 'delete', $this->mTitle, $reason );
 
 		# Clear the cached article id so the interface doesn't act like we exist
@@ -2226,8 +2248,13 @@
 			);
 		}
 
+		$target = Revision::newFromId( $s->rev_id );
+		# Revision *must* be public and we don't well handle deleted edits on top
+		if ( $target->isDeleted(REVISION::DELETED_TEXT) ) {
+			$wgOut->setPageTitle( wfMsg('rollbackfailed') );
+			$wgOut->addHTML( wfMsg( 'missingarticle' ) );
+		}
 		# Get the edit summary
-		$target = Revision::newFromId( $s->rev_id );
 		$newComment = wfMsgForContent( 'revertpage', $target->getUserText(), $from );
 		$newComment = $wgRequest->getText( 'summary', $newComment );
 
@@ -2405,10 +2432,30 @@
 			? wfMsg( 'diff' )
 			: $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid );
 
-		$userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() )
-						. $sk->userToolLinks( $revision->getUser(), $revision->getUserText() );
+		$cdel='';
+		if( $wgUser->isAllowed( 'deleterevision' ) ) {		
+			$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+			if( $revision->isCurrent() ) {
+			// We don't handle top deleted edits too well
+				$cdel = wfMsgHtml('rev-delundel');	
+			} else if( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) {
+			// If revision was hidden from sysops
+				$cdel = wfMsgHtml('rev-delundel');	
+			} else {
+				$cdel = $sk->makeKnownLinkObj( $revdel,
+					wfMsgHtml('rev-delundel'),
+					'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) .
+					'&oldid=' . urlencode( $oldid ) );
+				// Bolden oversighted content
+				if( $revision->isDeleted( Revision::DELETED_RESTRICTED ) )
+					$cdel = "<strong>$cdel</strong>";
+			}
+			$cdel = "(<small>$cdel</small>)";
+		}
 
-		$r = "\n\t\t\t\t<div id=\"mw-revision-info\">" . wfMsg( 'revision-info', $td, $userlinks ) . "</div>\n" .
+		$userlinks = $sk->revUserTools( $revision, true );
+
+		$r = "\n\t\t\t\t<div id=\"mw-revision-info\">" . "<tt>$cdel</tt>" . wfMsg( 'revision-info', $td, $userlinks ) . "</div>\n" .
 		     "\n\t\t\t\t<div id=\"mw-revision-nav\">" . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
 		$wgOut->setSubtitle( $r );
 	}
Index: trunk/phase3/includes/RecentChange.php
===================================================================
--- trunk/phase3/includes/RecentChange.php	(revision 20445)
+++ trunk/phase3/includes/RecentChange.php	(revision 20446)
@@ -25,6 +25,11 @@
  * 	rc_patrolled    boolean whether or not someone has marked this edit as patrolled
  * 	rc_old_len	integer byte length of the text before the edit
  * 	rc_new_len	the same after the edit
+ *	rc_deleted		partial deletion
+ *	rc_logid		the log_id value for this log entry (or zero)
+ *  rc_log_type		the log type (or null)
+ *	rc_log_action	the log action (or null)
+ *  rc_params		log params
  *
  * mExtra:
  * 	prefixedDBkey   prefixed db key, used by external app via msg queue
@@ -227,8 +232,7 @@
 
 	# Makes an entry in the database corresponding to an edit
 	/*static*/ function notifyEdit( $timestamp, &$title, $minor, &$user, $comment,
-		$oldId, $lastTimestamp, $bot = "default", $ip = '', $oldSize = 0, $newSize = 0,
-		$newId = 0)
+		$oldId, $lastTimestamp, $bot="default", $ip='', $oldSize=0, $newSize=0, $newId=0)
 	{
 
 		if ( $bot === 'default' ) {
@@ -263,7 +267,12 @@
 			'rc_patrolled'	=> 0,
 			'rc_new'	=> 0,  # obsolete
 			'rc_old_len'	=> $oldSize,
-			'rc_new_len'	=> $newSize
+			'rc_new_len'	=> $newSize,
+			'rc_deleted'	=> 0,
+			'rc_logid'		=> 0,
+			'rc_log_type'	=> null,
+			'rc_log_action'	=> '',
+			'rc_params'		=> ''
 		);
 
 		$rc->mExtra =  array(
@@ -284,7 +293,7 @@
 	 * @static
 	 */
 	public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot = "default",
-	  $ip='', $size = 0, $newId = 0 )
+	  $ip='', $size=0, $newId=0 )
 	{
 		if ( !$ip ) {
 			$ip = wfGetIP();
@@ -292,6 +301,7 @@
 				$ip = '';
 			}
 		}
+				
 		if ( $bot == 'default' ) {
 			$bot = $user->isAllowed( 'bot' );
 		}
@@ -315,9 +325,14 @@
 			'rc_moved_to_title' => '',
 			'rc_ip'             => $ip,
 			'rc_patrolled'      => 0,
-			'rc_new'	    => 1, # obsolete
+			'rc_new'	    	=> 1, # obsolete
 			'rc_old_len'        => 0,
-			'rc_new_len'	    => $size
+			'rc_new_len'	    => $size,
+			'rc_deleted'		=> 0,
+			'rc_logid'			=> 0,
+			'rc_log_type'		=> null,
+			'rc_log_action'		=> '',
+			'rc_params'			=> ''
 		);
 
 		$rc->mExtra =  array(
@@ -339,7 +354,7 @@
 				$ip = '';
 			}
 		}
-
+		
 		$rc = new RecentChange;
 		$rc->mAttribs = array(
 			'rc_timestamp'	=> $timestamp,
@@ -362,6 +377,11 @@
 			'rc_patrolled'	=> 1,
 			'rc_old_len'	=> NULL,
 			'rc_new_len'	=> NULL,
+			'rc_deleted'	=> 0,
+			'rc_logid'		=> 0, # notifyMove not used anymore
+			'rc_log_type'	=> null,
+			'rc_log_action'	=> '',
+			'rc_params'		=> ''
 		);
 
 		$rc->mExtra = array(
@@ -380,18 +400,14 @@
 		RecentChange::notifyMove( $timestamp, $oldTitle, $newTitle, $user, $comment, $ip, true );
 	}
 
-	# A log entry is different to an edit in that previous revisions are
-	# not kept
-	/*static*/ function notifyLog( $timestamp, &$title, &$user, $comment, $ip='',
-	   $type, $action, $target, $logComment, $params )
+	# A log entry is different to an edit in that previous revisions are not kept
+	/*static*/ function notifyLog( $timestamp, &$title, &$user, $actionText = null, $ip='',
+	   $type, $action, $target, $logComment, $params, $newId=0 )
 	{
 		if ( !$ip ) {
 			$ip = wfGetIP();
-			if ( !$ip ) {
-				$ip = '';
-			}
+			if ( !$ip ) $ip = '';
 		}
-
 		$rc = new RecentChange;
 		$rc->mAttribs = array(
 			'rc_timestamp'	=> $timestamp,
@@ -403,7 +419,7 @@
 			'rc_cur_id'	=> $title->getArticleID(),
 			'rc_user'	=> $user->getID(),
 			'rc_user_text'	=> $user->getName(),
-			'rc_comment'	=> $comment,
+			'rc_comment'	=> $logComment,
 			'rc_this_oldid'	=> 0,
 			'rc_last_oldid'	=> 0,
 			'rc_bot'	=> $user->isAllowed( 'bot' ) ? 1 : 0,
@@ -414,6 +430,11 @@
 			'rc_new'	=> 0, # obsolete
 			'rc_old_len'	=> NULL,
 			'rc_new_len'	=> NULL,
+			'rc_deleted'	=> 0,
+			'rc_logid'		=> $newId,
+			'rc_log_type'	=> $type,
+			'rc_log_action'	=> $action,
+			'rc_params'		=> $params
 		);
 		$rc->mExtra =  array(
 			'prefixedDBkey'	=> $title->getPrefixedDBkey(),
@@ -460,6 +481,11 @@
 			'rc_new' => $row->page_is_new, # obsolete
 			'rc_old_len' => $row->rc_old_len,
 			'rc_new_len' => $row->rc_new_len,
+			'rc_deleted'  => $row->rc_deleted,
+			'rc_logid'	=> $row->rc_logid,
+			'rc_log_type'	=> $row->rc_log_type,
+			'rc_log_action'	=> $row->rc_log_action,
+			'rc_params'	=> $row->rc_params
 		);
 
 		$this->mExtra = array();
Index: trunk/phase3/includes/SpecialRecentchanges.php
===================================================================
--- trunk/phase3/includes/SpecialRecentchanges.php	(revision 20445)
+++ trunk/phase3/includes/SpecialRecentchanges.php	(revision 20446)
@@ -404,7 +404,7 @@
 			rcFormatDiff( $obj ),
 			$title->getFullURL(),
 			$obj->rc_timestamp,
-			$obj->rc_user_text,
+			($obj->rc_deleted & Revision::DELETED_USER) ? wfMsgHtml('rev-deleted-user') : $obj->rc_user_text,
 			$talkpage->getFullURL()
 			);
 		$feed->outItem( $item );
@@ -613,15 +613,18 @@
 	return rcFormatDiffRow( $titleObj,
 		$row->rc_last_oldid, $row->rc_this_oldid,
 		$timestamp,
-		$row->rc_comment );
+		($row->rc_deleted & Revision::DELETED_COMMENT) ? wfMsgHtml('rev-deleted-comment') : $row->rc_comment,
+		($row->rc_deleted & Revision::DELETED_NAME) ? wfMsgHtml('rev-deleted-event') : $row->rc_actiontext );
 }
 
-function rcFormatDiffRow( $title, $oldid, $newid, $timestamp, $comment ) {
+function rcFormatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext='' ) {
 	global $wgFeedDiffCutoff, $wgContLang, $wgUser;
 	$fname = 'rcFormatDiff';
 	wfProfileIn( $fname );
 
 	$skin = $wgUser->getSkin();
+	# log enties
+	if( $actiontext ) $comment = "$actiontext $comment";
 	$completeText = '<p>' . $skin->formatComment( $comment ) . "</p>\n";
 
 	if( $title->getNamespace() >= 0 && $title->userCan( 'read' ) ) {
Index: trunk/phase3/includes/ImagePage.php
===================================================================
--- trunk/phase3/includes/ImagePage.php	(revision 20445)
+++ trunk/phase3/includes/ImagePage.php	(revision 20446)
@@ -508,7 +508,9 @@
 		$reason = $wgRequest->getVal( 'wpReason' );
 		$image = $wgRequest->getVal( 'image' );
 		$oldimage = $wgRequest->getVal( 'oldimage' );
-
+		# Flag to hide all contents of the archived revisions
+		$suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('deleterevision');
+		
 		# Only sysops can delete images. Previously ordinary users could delete
 		# old revisions, but this is no longer the case.
 		if ( !$wgUser->isAllowed('delete') ) {
@@ -536,7 +538,7 @@
 		# Deleting old images doesn't require confirmation
 		if ( !is_null( $oldimage ) || $confirm ) {
 			if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $oldimage ) ) {
-				$this->doDelete( $reason );
+				$this->doDelete( $reason, $suppress );
 			} else {
 				$wgOut->showFatalError( wfMsg( 'sessionfailure' ) );
 			}
@@ -557,7 +559,7 @@
 	 * Delete an image.
 	 * @param $reason User provided reason for deletion.
 	 */
-	function doDelete( $reason ) {
+	function doDelete( $reason, $suppress=false ) {
 		global $wgOut, $wgRequest;
 
 		$oldimage = $wgRequest->getVal( 'oldimage' );
@@ -571,12 +573,12 @@
 				$wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars($oldimage) );
 				return;
 			}
-			if ( !$this->doDeleteOldImage( $oldimage ) ) {
+			if ( !$this->doDeleteOldImage( $oldimage, $suppress ) ) {
 				return;
 			}
 			$deleted = $oldimage;
 		} else {
-			$ok = $this->img->delete( $reason );
+			$ok = $this->img->delete( $reason, $suppress );
 			if( !$ok ) {
 				# If the deletion operation actually failed, bug out:
 				$wgOut->showFileDeleteError( $this->img->getName() );
@@ -587,7 +589,7 @@
 			# Now we remove the image description page.
 	
 			$article = new Article( $this->mTitle );
-			$article->doDeleteArticle( $reason ); # ignore errors
+			$article->doDeleteArticle( $reason, $suppress ); # ignore errors
 
 			$deleted = $this->img->getName();
 		}
@@ -606,11 +608,11 @@
 	/**
 	 * @return success
 	 */
-	function doDeleteOldImage( $oldimage )
+	function doDeleteOldImage( $oldimage, $suppress=false )
 	{
 		global $wgOut;
 
-		$ok = $this->img->deleteOld( $oldimage, '' );
+		$ok = $this->img->deleteOld( $oldimage, '', $suppress );
 		if( !$ok ) {
 			# If we actually have a file and can't delete it, throw an error.
 			# Something went awry...
Index: trunk/phase3/includes/SpecialRevisiondelete.php
===================================================================
--- trunk/phase3/includes/SpecialRevisiondelete.php	(revision 20445)
+++ trunk/phase3/includes/SpecialRevisiondelete.php	(revision 20446)
@@ -2,36 +2,41 @@
 
 /**
  * Not quite ready for production use yet; need to fix up the restricted mode,
- * and provide for preservation across delete/undelete of the page.
- *
- * To try this out, set up extra permissions something like:
- * $wgGroupPermissions['sysop']['deleterevision'] = true;
- * $wgGroupPermissions['bureaucrat']['hiderevision'] = true;
+ * and provide for preservation across delete/undelete of images.
  */
 
 function wfSpecialRevisiondelete( $par = null ) {
 	global $wgOut, $wgRequest;
 	
-	$target = $wgRequest->getVal( 'target' );
+	$target = $wgRequest->getText( 'target' );
+	// handle our many different possible input types
 	$oldid = $wgRequest->getIntArray( 'oldid' );
-	
-	$page = Title::newFromUrl( $target );
-	
+	$logid = $wgRequest->getIntArray( 'logid' );
+	$arid = $wgRequest->getIntArray( 'arid' );
+	$fileid = $wgRequest->getIntArray( 'fileid' );
+
+	$page = Title::newFromUrl( $target, false );
 	if( is_null( $page ) ) {
 		$wgOut->showErrorPage( 'notargettitle', 'notargettext' );
 		return;
 	}
-	
-	if( is_null( $oldid ) ) {
+
+	$input_types = !is_null( $oldid ) + !is_null( $logid ) + !is_null( $arid ) + !is_null( $fileid );
+	if( $input_types > 1 || $input_types==0 ) {
+	//one target set at a time please!
 		$wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
 		return;
 	}
 	
-	$form = new RevisionDeleteForm( $wgRequest );
+	$form = new RevisionDeleteForm( $wgRequest, $oldid, $logid, $arid, $fileid );
 	if( $wgRequest->wasPosted() ) {
 		$form->submit( $wgRequest );
-	} else {
-		$form->show( $wgRequest );
+	} else if( $oldid || $arid ) {
+		$form->showRevs( $wgRequest );
+	} else if( $logid ) {
+		$form->showEvents( $wgRequest );
+	} else if( $fileid ) {
+		$form->showImages( $wgRequest );
 	}
 }
 
@@ -40,52 +45,259 @@
 	 * @param Title $page
 	 * @param int $oldid
 	 */
-	function __construct( $request ) {
+	function __construct( $request, $oldid, $logid, $arid, $fileid ) {
 		global $wgUser;
 		
-		$target = $request->getVal( 'target' );
-		$this->page = Title::newFromUrl( $target );
+		$target = $request->getText( 'target' );
+		$this->page = Title::newFromUrl( $target, false );
 		
 		$this->revisions = $request->getIntArray( 'oldid', array() );
+		$this->events = $request->getIntArray( 'logid', array() );
+		$this->archrevs = $request->getIntArray( 'arid', array() );
+		$this->files = $request->getIntArray( 'fileid', array() );
 		
 		$this->skin = $wgUser->getSkin();
+	
+		// log events don't have text to hide, but hiding the page name is useful
+		if ( $fileid ) {
+		   $hide_text_name = array( 'revdelete-hide-image', 'wpHideImage', Image::DELETED_FILE );
+		   $this->deletetype='file';
+		} else if ( $logid ) {
+		   $hide_text_name = array( 'revdelete-hide-name', 'wpHideName', LogViewer::DELETED_ACTION );
+		   $this->deletetype='log';
+		} else {
+		   $hide_text_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT );
+		   if ( $arid ) $this->deletetype='ar';
+		   else $this->deletetype='old';
+		}
 		$this->checks = array(
-			array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ),
+			$hide_text_name,
 			array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ),
 			array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ),
 			array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED ) );
 	}
 	
 	/**
+	 * This sets any fields that are true to a bitfield to true on a given bitfield
+	 * @param $bitfield, running bitfield
+	 * @param $nbitfield, new bitfiled
+	 */	
+	function setBitfield( $bitfield, $nbitfield ) {
+		if ( $nbitfield & Revision::DELETED_TEXT) $bitfield |= Revision::DELETED_TEXT;
+		if ( $nbitfield & LogViewer::DELETED_ACTION) $bitfield |= LogViewer::DELETED_ACTION;
+		if ( $nbitfield & Image::DELETED_FILE) $bitfield |= Image::DELETED_FILE;
+		if ( $nbitfield & Revision::DELETED_COMMENT) $bitfield |= Revision::DELETED_COMMENT;
+		if ( $nbitfield & Revision::DELETED_USER) $bitfield |= Revision::DELETED_USER;
+		if ( $nbitfield & Revision::DELETED_RESTRICTED) $bitfield |= Revision::DELETED_RESTRICTED;
+		return $bitfield;
+	}
+	
+	/**
+	 * This lets a user set restrictions for live and archived revisions
 	 * @param WebRequest $request
 	 */
-	function show( $request ) {
-		global $wgOut, $wgUser;
+	function showRevs( $request ) {
+		global $wgOut, $wgUser, $action;
 
-		$wgOut->addWikiText( wfMsg( 'revdelete-selected', $this->page->getPrefixedText() ) );
+		$UserAllowed = true;
+		$wgOut->addWikiText( wfMsgHtml( 'revdelete-selected', $this->page->getPrefixedText() ) );
 		
+		$bitfields = 0;
 		$wgOut->addHtml( "<ul>" );
-		foreach( $this->revisions as $revid ) {
-			$rev = Revision::newFromTitle( $this->page, $revid );
-			if( !isset( $rev ) ) {
+		if ( $this->deletetype=='old') {
+			foreach( $this->revisions as $revid ) {
+				$rev = Revision::newFromTitle( $this->page, $revid );
+				// Hiding top revisison is bad
+				if( !isset( $rev ) || $rev->isCurrent() ) {
+					$wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+					return;
+				} else if( !$rev->userCan(Revision::DELETED_RESTRICTED) ) {
+				// If a rev is hidden from sysops
+					if ( $action != 'submit') {
+						$wgOut->permissionRequired( 'hiderevision' ); return;
+					}
+					$UserAllowed=false;
+				}
+			$wgOut->addHtml( $this->historyLine( $rev ) );
+			$bitfields = $this->setBitfield( $bitfields, $rev->mDeleted );
+			}
+		} else if ( $this->deletetype=='ar') {
+		   $archive = new PageArchive( $this->page );
+			foreach( $this->archrevs as $revid ) {
+    			$rev = $archive->getRevision('', $revid );
+				if( !isset( $rev ) ) {
+					$wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+					return;
+				} else if( !$rev->userCan(Revision::DELETED_RESTRICTED) ) {
+				//if a rev is hidden from sysops
+					if ( $action != 'submit') {
+						$wgOut->permissionRequired( 'hiderevision' ); return;
+					}
+					$UserAllowed=false;
+				}
+			$wgOut->addHtml( $this->historyLine( $rev ) );
+			$bitfields = $this->setBitfield( $bitfields, $rev->mDeleted );
+			}
+		} 
+		$wgOut->addHtml( "</ul>" );
+		
+		$wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) );
+		//Normal sysops can always see what they did, but can't always change it
+		if ( !$UserAllowed ) return;
+		
+		$items = array(
+			wfInputLabel( wfMsgHtml( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
+			wfSubmitButton( wfMsgHtml( 'revdelete-submit' ) ) );
+		$hidden = array(
+			wfHidden( 'wpEditToken', $wgUser->editToken() ),
+			wfHidden( 'target', $this->page->getPrefixedText() ),
+			wfHidden( 'type', $this->deletetype ) );
+		if( $this->deletetype=='old' ) {
+			foreach( $this->revisions as $revid ) {
+				$hidden[] = wfHidden( 'oldid[]', $revid );
+			}	
+		} else if( $this->deletetype=='ar' ) {	
+			foreach( $this->archrevs as $revid ) {
+				$hidden[] = wfHidden( 'arid[]', $revid );
+			}
+		}
+		$special = SpecialPage::getTitleFor( 'Revisiondelete' );
+		$wgOut->addHtml( wfElement( 'form', array(
+			'method' => 'post',
+			'action' => $special->getLocalUrl( 'action=submit' ) ),
+			null ) );
+		
+		$wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' );
+		// FIXME: all items checked for just one rev are checked, even if not set for the others
+		foreach( $this->checks as $item ) {
+			list( $message, $name, $field ) = $item;
+			$wgOut->addHtml( '<div>' .
+				wfCheckLabel( wfMsgHtml( $message), $name, $name, $bitfields & $field ) .
+				'</div>' );
+		}
+		$wgOut->addHtml( '</fieldset>' );
+		foreach( $items as $item ) {
+			$wgOut->addHtml( '<p>' . $item . '</p>' );
+		}
+		foreach( $hidden as $item ) {
+			$wgOut->addHtml( $item );
+		}
+		
+		$wgOut->addHtml( '</form>' );
+	}
+
+	/**
+	 * This lets a user set restrictions for archived images
+	 * @param WebRequest $request
+	 */
+	function showImages( $request ) {
+		global $wgOut, $wgUser, $action;
+
+		$UserAllowed = true;
+		$wgOut->addWikiText( wfMsgHtml( 'revdelete-selected', $this->page->getPrefixedText() ) );
+		
+		$bitfields = 0;
+		$wgOut->addHtml( "<ul>" );
+			foreach( $this->files as $fileid ) {
+				$file = new FSarchivedFile( $this->page, $fileid );
+				if( !isset( $file->mId ) ) {
+					$wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+					return;
+				} else if( !$file->userCan(Revision::DELETED_RESTRICTED) ) {
+				// If a rev is hidden from sysops
+					if ( $action != 'submit') {
+						$wgOut->permissionRequired( 'hiderevision' ); return;
+					}
+					$UserAllowed=false;
+				}
+			$wgOut->addHtml( $this->uploadLine( $file ) );
+			$bitfields = $this->setBitfield( $bitfields, $file->mDeleted );
+			}
+		$wgOut->addHtml( "</ul>" );
+		
+		$wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) );
+		//Normal sysops can always see what they did, but can't always change it
+		if ( !$UserAllowed ) return;
+		
+		$items = array(
+			wfInputLabel( wfMsgHtml( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
+			wfSubmitButton( wfMsgHtml( 'revdelete-submit' ) ) );
+		$hidden = array(
+			wfHidden( 'wpEditToken', $wgUser->editToken() ),
+			wfHidden( 'target', $this->page->getPrefixedText() ),
+			wfHidden( 'type', $this->deletetype ) );
+		foreach( $this->files as $fileid ) {
+			$hidden[] = wfHidden( 'fileid[]', $fileid );
+		}	
+		$special = SpecialPage::getTitleFor( 'Revisiondelete' );
+		$wgOut->addHtml( wfElement( 'form', array(
+			'method' => 'post',
+			'action' => $special->getLocalUrl( 'action=submit' ) ),
+			null ) );
+		
+		$wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' );
+		// FIXME: all items checked for just one file are checked, even if not set for the others
+		foreach( $this->checks as $item ) {
+			list( $message, $name, $field ) = $item;
+			$wgOut->addHtml( '<div>' .
+				wfCheckLabel( wfMsgHtml( $message), $name, $name, $bitfields & $field ) .
+				'</div>' );
+		}
+		$wgOut->addHtml( '</fieldset>' );
+		foreach( $items as $item ) {
+			$wgOut->addHtml( '<p>' . $item . '</p>' );
+		}
+		foreach( $hidden as $item ) {
+			$wgOut->addHtml( $item );
+		}
+		
+		$wgOut->addHtml( '</form>' );
+	}
+		
+	/**
+	 * This lets a user set restrictions for log items
+	 * @param WebRequest $request
+	 */
+	function showEvents( $request ) {
+		global $wgOut, $wgUser, $action;
+
+		$UserAllowed = true;
+		$wgOut->addWikiText( wfMsgHtml( 'logdelete-selected', $this->page->getPrefixedText() ) );
+		
+		$bitfields = 0;
+		$wgOut->addHtml( "<ul>" );
+		foreach( $this->events as $logid ) {
+			$log = new LogViewer( $wgRequest );
+			$event = LogReader::newFromTitle( $this->page, $logid );
+			// Don't hide from oversight log!!!
+			if( !isset( $event ) || $event->log_type == 'oversight' ) {
 				$wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
 				return;
+			} else if( !$log->userCan($event, Revision::DELETED_RESTRICTED) ) {
+			// If an event is hidden from sysops
+				if ( $action != 'submit') {
+					$wgOut->permissionRequired( 'hiderevision' ); return;
+				}
+				$UserAllowed=false;
 			}
-			$wgOut->addHtml( $this->historyLine( $rev ) );
-			$bitfields[] = $rev->mDeleted; // FIXME
+			$wgOut->addHtml( $this->logLine( $log, $event ) );
+			$bitfields = $this->setBitfield( $bitfields, $event->log_deleted );
 		}
 		$wgOut->addHtml( "</ul>" );
-	
-		$wgOut->addWikiText( wfMsg( 'revdelete-text' ) );
+
+		$wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) );
+		//Normal sysops can always see what they did, but can't always change it
+		if ( !$UserAllowed ) return;
 		
 		$items = array(
-			wfInputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
-			wfSubmitButton( wfMsg( 'revdelete-submit' ) ) );
+			wfInputLabel( wfMsgHtml( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
+			wfSubmitButton( wfMsgHtml( 'revdelete-submit' ) ) );
 		$hidden = array(
 			wfHidden( 'wpEditToken', $wgUser->editToken() ),
-			wfHidden( 'target', $this->page->getPrefixedText() ) );
-		foreach( $this->revisions as $revid ) {
-			$hidden[] = wfHidden( 'oldid[]', $revid );
+			wfHidden( 'target', $this->page->getPrefixedText() ),
+			wfHidden( 'type', $this->deletetype ) );
+		foreach( $this->events as $logid ) {
+			$hidden[] = wfHidden( 'logid[]', $logid );
 		}
 		
 		$special = SpecialPage::getTitleFor( 'Revisiondelete' );
@@ -95,10 +307,11 @@
 			null ) );
 		
 		$wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' );
+		// FIXME: all items checked for just on event are checked, even if not set for the others
 		foreach( $this->checks as $item ) {
 			list( $message, $name, $field ) = $item;
 			$wgOut->addHtml( '<div>' .
-				wfCheckLabel( wfMsg( $message), $name, $name, $rev->isDeleted( $field ) ) .
+				wfCheckLabel( wfMsgHtml( $message), $name, $name, $bitfields & $field ) .
 				'</div>' );
 		}
 		$wgOut->addHtml( '</fieldset>' );
@@ -119,32 +332,136 @@
 	function historyLine( $rev ) {
 		global $wgContLang;
 		$date = $wgContLang->timeanddate( $rev->getTimestamp() );
+		
+		$difflink=''; $del = '';
+		if( $this->deletetype=='old' ) {
+			$difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'), 
+		'&diff=' . $rev->getId() . '&oldid=prev' ) . ')';
+			$revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() );
+		} else if( $this->deletetype=='ar' ) {
+			$undelete = SpecialPage::getTitleFor( 'Undelete' );
+			$target = $this->page->getPrefixedText();
+			$revlink = $this->skin->makeLinkObj( $undelete, $date, "target=$target&timestamp=" . $rev->getTimestamp() );
+		}
+	
+		if ( $rev->isDeleted(Revision::DELETED_TEXT) ) {
+			$revlink = '<span class="history-deleted">'.$revlink.'</span>';
+			$del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
+			if ( !$rev->userCan(Revision::DELETED_TEXT) ) {
+				$revlink = '<span class="history-deleted">'.$date.'</span>';
+			}
+		}
+		
 		return
-			"<li>" .
-			$this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ) .
-			" " .
-			$this->skin->revUserLink( $rev ) .
-			" " .
-			$this->skin->revComment( $rev ) .
-			"</li>";
+			"<li> $difflink $revlink " . $this->skin->revUserLink( $rev ) . " " . $this->skin->revComment( $rev ) . "$del</li>";
 	}
 	
 	/**
+	 * @param Image $file
+	 * @returns string
+	 */	
+	function uploadLine( $file ) {
+		global $wgContLang;
+		
+		$target = $this->page->getPrefixedText();
+		$date = $wgContLang->timeanddate( $file->mTimestamp, true  );
+
+		$del = '';
+		if ( $file->mGroup == 'deleted' ) {
+			$undelete = SpecialPage::getTitleFor( 'Undelete' );
+			$pageLink = $this->skin->makeKnownLinkObj( $undelete, $date, "target=$target&file=$file->mKey" );
+		} else {
+			$pageLink = $this->skin->makeKnownLinkObj( $this->page, $date, "file=$file->mKey" );
+		}
+		if ( $file->isDeleted(Image::DELETED_FILE) ) {
+			$pageLink = '<span class="history-deleted">' . $pageLink . '</span>';
+			$del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
+			if ( !$file->userCan(Image::DELETED_FILE) ) {
+				$pageLink = '<span class="history-deleted">'.$date.'</span>';
+			}
+		}
+		
+		$data = wfMsgHtml( 'widthheight',
+						$wgContLang->formatNum( $file->mWidth ),
+						$wgContLang->formatNum( $file->mHeight ) ) .
+				' (' . wfMsgHtml( 'nbytes', $wgContLang->formatNum( $file->mSize ) ) . ')';	
+	
+		return
+			"<li> $pageLink " . $this->skin->fileUserLink( $file ) . " $data " . $this->skin->fileComment( $file ) . "$del</li>";
+	}
+	
+	/**
+	 * @param Revision $rev
+	 * @returns string
+	 */
+	function logLine( $log, $event ) {
+		global $wgContLang;
+
+		$date = $wgContLang->timeanddate( $event->log_timestamp );
+		$paramArray = LogPage::extractParams( $event->log_params );
+
+		if ( !LogViewer::userCan($event,LogViewer::DELETED_ACTION) ) {
+			$action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';	
+		} else {	
+			$action = LogPage::actionText( $event->log_type, $event->log_action, $this->page, $this->skin, $paramArray, true, true );
+			if( $event->log_deleted & LogViewer::DELETED_ACTION )
+				$action = '<span class="history-deleted">' . $action . '</span>';
+		}
+		return
+			"<li>$date" . " " . $this->skin->logUserLink( $event ) . " $action " . $this->skin->logComment( $event ) . "</li>";
+	}
+	
+	/**
 	 * @param WebRequest $request
 	 */
 	function submit( $request ) {
 		$bitfield = $this->extractBitfield( $request );
 		$comment = $request->getText( 'wpReason' );
-		if( $this->save( $bitfield, $comment ) ) {
-			return $this->success( $request );
-		} else {
-			return $this->show( $request );
-		}
+		
+		$target = $request->getText( 'target' );
+		$title = Title::newFromURL( $target, false );
+		
+		if( $this->save( $bitfield, $comment, $title ) ) {
+			$this->success( $request );
+		} else if( $request->getCheck( 'oldid' ) || $request->getCheck( 'arid' ) ) {
+			return $this->showRevs( $request );
+		} else if( $request->getCheck( 'logid' ) ) {
+			return $this->showLogs( $request );
+		} else if( $request->getCheck( 'fileid' ) ) {
+			return $this->showImages( $request );
+		} 
 	}
 	
 	function success( $request ) {
 		global $wgOut;
-		$wgOut->addWikiText( 'woo' );
+		
+		$wgOut->setPagetitle( wfMsgHtml( 'actioncomplete' ) );
+		
+		$target = $request->getText( 'target' );
+		$type = $request->getText( 'type' );
+
+		$title = Title::newFromURL( $target, false );
+		$name = $title->makeName( $title->getNamespace(), $title->getText() );
+		
+		$logtitle = SpecialPage::getTitleFor( 'Log' );
+        $loglink = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'viewpagelogs' ),
+		wfArrayToCGI( array('page' => $name ) ) );
+		$histlink = $this->skin->makeKnownLinkObj( $title, wfMsgHtml( 'revhistory' ),
+		wfArrayToCGI( array('action' => 'history' ) ) );
+		
+		if ( $title->getNamespace() > -1)
+			$wgOut->setSubtitle( '<p>'.$histlink.' / '.$loglink.'</p>' );
+		
+		if( $type=='log' ) {
+			$wgOut->addWikiText( wfMsgHtml('logdelete-success', $target), false );
+			$this->showEvents( $request );
+		} else if( $type=='old' || $type=='ar' ) {
+		  	$wgOut->addWikiText( wfMsgHtml('revdelete-success', $target), false );
+		  	$this->showRevs( $request );
+		} else if ( $type=='file' ) {
+		  	$wgOut->addWikiText( wfMsgHtml('revdelete-success', $target), false );
+		  	$this->showImages( $request );
+		}
 	}
 	
 	/**
@@ -163,10 +480,19 @@
 		return $bitfield;
 	}
 	
-	function save( $bitfield, $reason ) {
+	function save( $bitfield, $reason, $title ) {
 		$dbw = wfGetDB( DB_MASTER );
 		$deleter = new RevisionDeleter( $dbw );
-		$deleter->setVisibility( $this->revisions, $bitfield, $reason );
+
+		if( $this->revisions ) {
+			return $deleter->setRevVisibility( $title, $this->revisions, $bitfield, $reason );
+		} else if( $this->events ) {
+			return $deleter->setEventVisibility( $title, $this->events, $bitfield, $reason );
+		} else if( $this->archrevs ) {
+			return $deleter->setArchiveVisibility( $title, $this->archrevs, $bitfield, $reason );
+		} else if( $this->files ) {
+			return $deleter->setFileVisibility( $title, $this->files, $bitfield, $reason );
+		}
 	}
 }
 
@@ -177,42 +503,192 @@
 	}
 	
 	/**
+	 * @param $title, the page these events apply to
 	 * @param array $items list of revision ID numbers
 	 * @param int $bitfield new rev_deleted value
 	 * @param string $comment Comment for log records
 	 */
-	function setVisibility( $items, $bitfield, $comment ) {
-		$pages = array();
+	function setRevVisibility( $title, $items, $bitfield, $comment ) {
+		global $wgOut;
 		
+		$UserAllowedAll = true;
+		$pages_count = array(); $pages_revIds = array();
 		// To work!
 		foreach( $items as $revid ) {
-			$rev = Revision::newFromId( $revid );
-			if( !isset( $rev ) ) {
+			$rev = Revision::newFromTitle( $title, $revid );
+			if( !isset( $rev ) || $rev->isCurrent() ) {
 				return false;
+			} else if( !$rev->userCan(Revision::DELETED_RESTRICTED) ) {
+    			$UserAllowedAll=false; 
+				continue;
 			}
-			$this->updateRevision( $rev, $bitfield );
-			$this->updateRecentChanges( $rev, $bitfield );
-			
+			$pageid = $rev->getPage();
 			// For logging, maintain a count of revisions per page
-			$pageid = $rev->getPage();
-			if( isset( $pages[$pageid] ) ) {
-				$pages[$pageid]++;
-			} else {
-				$pages[$pageid] = 1;
+			if ( !isset($pages_count[$pageid]) ) {
+				$pages_count[$pageid]=0;
+				$pages_revIds[$pageid]=array();
 			}
+			// Which pages did we change anything about?
+			if ( $rev->mDeleted != $bitfield ) {
+				$pages_count[$pageid]++;
+				$pages_revIds[$pageid][]=$revid;
+				
+			   	$this->updateRevision( $rev, $bitfield );
+				$this->updateRecentChangesEdits( $rev, $bitfield, false );
+			}
 		}
 		
 		// Clear caches...
-		foreach( $pages as $pageid => $count ) {
-			$title = Title::newFromId( $pageid );
-			$this->updatePage( $title );
-			$this->updateLog( $title, $count, $bitfield, $comment );
+		foreach( $pages_count as $pageid => $count ) {
+			//Don't log or touch if nothing changed
+			if ( $count > 0 ) {
+			   $title = Title::newFromId( $pageid );
+			   $this->updatePage( $title );
+			   $this->updateLog( $title, $count, $bitfield, $comment, $title, 'old', $pages_revIds[$pageid] );
+			}
 		}
+		// Where all revs allowed to be set?
+		if ( !$UserAllowedAll ) {
+		//FIXME: still might be confusing???
+			$wgOut->permissionRequired( 'hiderevision' ); return false;
+		}
 		
 		return true;
 	}
 	
+	 /**
+	 * @param $title, the page these events apply to
+	 * @param array $items list of revision ID numbers
+	 * @param int $bitfield new rev_deleted value
+	 * @param string $comment Comment for log records
+	 */
+	function setArchiveVisibility( $title, $items, $bitfield, $comment ) {
+		global $wgOut;
+		
+		$UserAllowedAll = true;
+		$count = 0; $Id_set = array();
+		// To work!
+		$archive = new PageArchive( $title );
+		foreach( $items as $revid ) {
+			$rev = $archive->getRevision( '', $revid );
+			if( !isset( $rev ) ) {
+				return false;
+			} else if( !$rev->userCan(Revision::DELETED_RESTRICTED) ) {
+    			$UserAllowedAll=false;
+				continue;
+			}
+			// For logging, maintain a count of revisions
+			if ( $rev->mDeleted != $bitfield ) {
+			   $Id_set[]=$revid;
+			   $count++;
+			}		
+			$this->updateArchive( $rev, $bitfield );
+		}
+		
+		// Log if something was changed
+		if ( $count > 0 ) {
+			$this->updateLog( $title, $count, $bitfield, $comment, $title, 'ar', $Id_set );
+		}
+		// Where all revs allowed to be set?
+		if ( !$UserAllowedAll ) {
+			$wgOut->permissionRequired( 'hiderevision' ); return false;
+		}
+		
+		return true;
+	}
+	
+	 /**
+	 * @param $title, the page these events apply to
+	 * @param array $items list of revision ID numbers
+	 * @param int $bitfield new rev_deleted value
+	 * @param string $comment Comment for log records
+	 */
+	function setFileVisibility( $title, $items, $bitfield, $comment ) {
+		global $wgOut;
+		
+		$UserAllowedAll = true;
+		$count = 0; $Id_set = array();
+		// To work!
+		foreach( $items as $fileid ) {
+			$file = new FSarchivedFile( $title, $fileid );
+			if( !isset( $file ) ) {
+				return false;
+			} else if( !$file->userCan(Revision::DELETED_RESTRICTED) ) {
+    			$UserAllowedAll=false;
+				continue;
+			}
+			// For logging, maintain a count of revisions
+			if ( $file->mDeleted != $bitfield ) {
+			   $Id_set[]=$fileid;
+			   $count++;
+			}		
+			$this->updateFiles( $file, $bitfield );
+		}
+		
+		// Log if something was changed
+		if ( $count > 0 ) {
+			$this->updateLog( $title, $count, $bitfield, $comment, $title, 'file', $Id_set );
+		}
+		// Where all revs allowed to be set?
+		if ( !$UserAllowedAll ) {
+			$wgOut->permissionRequired( 'hiderevision' ); return false;
+		}
+		
+		return true;
+	}
+
 	/**
+	 * @param $title, the page these events apply to
+	 * @param array $items list of log ID numbers
+	 * @param int $bitfield new log_deleted value
+	 * @param string $comment Comment for log records
+	 */
+	function setEventVisibility( $title, $items, $bitfield, $comment ) {
+		global $wgOut;
+		
+		$UserAllowedAll = true;
+		$logs_count = array(); $logs_Ids = array();
+		// To work!
+		foreach( $items as $logid ) {
+			$event = LogReader::newFromTitle( $title, $logid );
+			if( !isset( $event ) ) {
+				return false;
+			} else if( !LogViewer::userCan($event, Revision::DELETED_RESTRICTED) || $event->log_type == 'oversight' ) {
+			// Don't hide from oversight log!!!
+    			$UserAllowedAll=false;
+    			continue;
+			}
+			$logtype = $event->log_type;
+			// For logging, maintain a count of events per log type
+			if( !isset( $logs_count[$logtype] ) ) {
+				$logs_count[$logtype]=0;
+				$logs_Ids[$logtype]=array();
+			}
+			// Which logs did we change anything about?
+			if ( $event->log_deleted != $bitfield ) {
+				$logs_Ids[$logtype][]=$logid;
+				$logs_count[$logtype]++;
+			   
+			   	$this->updateLogs( $event, $bitfield );
+				$this->updateRecentChangesLog( $event, $bitfield, true );
+			}
+		}
+		foreach( $logs_count as $logtype => $count ) {
+			//Don't log or touch if nothing changed
+			if ( $count > 0 ) {
+			   $target = SpecialPage::getTitleFor( 'Log', $logtype );
+			   $this->updateLog( $target, $count, $bitfield, $comment, $title, 'log', $logs_Ids[$logtype] );
+			}
+		}
+		// Where all revs allowed to be set?
+		if ( !$UserAllowedAll ) {
+			$wgOut->permissionRequired( 'hiderevision' ); return false;
+		}
+		
+		return true;
+	}	
+	
+	/**
 	 * Update the revision's rev_deleted field
 	 * @param Revision $rev
 	 * @param int $bitfield new rev_deleted bitfield value
@@ -225,19 +701,65 @@
 	}
 	
 	/**
+	 * Update the revision's rev_deleted field
+	 * @param Revision $rev
+	 * @param int $bitfield new rev_deleted bitfield value
+	 */
+	function updateArchive( $rev, $bitfield ) {
+		$this->db->update( 'archive',
+			array( 'ar_deleted' => $bitfield ),
+			array( 'ar_rev_id' => $rev->getId() ),
+			'RevisionDeleter::updateArchive' );
+	}
+
+	/**
+	 * Update the images's fa_deleted field
+	 * @param Revision $file
+	 * @param int $bitfield new rev_deleted bitfield value
+	 */
+	function updateFiles( $file, $bitfield ) {
+		$this->db->update( 'filearchive',
+			array( 'fa_deleted' => $bitfield ),
+			array( 'fa_id' => $file->mId ),
+			'RevisionDeleter::updateFiles' );
+	}	
+	
+	/**
+	 * Update the logging log_deleted field
+	 * @param Revision $rev
+	 * @param int $bitfield new rev_deleted bitfield value
+	 */
+	function updateLogs( $event, $bitfield ) {
+		$this->db->update( 'logging',
+			array( 'log_deleted' => $bitfield ),
+			array( 'log_id' => $event->log_id ),
+			'RevisionDeleter::updateLogs' );
+	}
+	
+	/**
 	 * Update the revision's recentchanges record if fields have been hidden
+	 * @param Revision $event
+	 * @param int $bitfield new rev_deleted bitfield value
+	 */
+	function updateRecentChangesLog( $event, $bitfield ) {
+		$this->db->update( 'recentchanges',
+			array( 'rc_deleted' => $bitfield,
+				   'rc_patrolled' => 1),
+			array( 'rc_logid' => $event->log_id ),
+			'RevisionDeleter::updateRecentChangesLog' );
+	}
+	
+	/**
+	 * Update the revision's recentchanges record if fields have been hidden
 	 * @param Revision $rev
 	 * @param int $bitfield new rev_deleted bitfield value
 	 */
-	function updateRecentChanges( $rev, $bitfield ) {
+	function updateRecentChangesEdits( $rev, $bitfield ) {
 		$this->db->update( 'recentchanges',
-			array(
-				'rc_user' => ($bitfield & Revision::DELETED_USER) ? 0 : $rev->getUser(),
-				'rc_user_text' => ($bitfield & Revision::DELETED_USER) ? wfMsg( 'rev-deleted-user' ) : $rev->getUserText(),
-				'rc_comment' => ($bitfield & Revision::DELETED_COMMENT) ? wfMsg( 'rev-deleted-comment' ) : $rev->getComment() ),
-			array(
-				'rc_this_oldid' => $rev->getId() ),
-			'RevisionDeleter::updateRecentChanges' );
+			array( 'rc_deleted' => $bitfield,
+				   'rc_patrolled' => 1),
+			array( 'rc_this_oldid' => $rev->getId() ),
+			'RevisionDeleter::updateRecentChangesEdits' );
 	}
 	
 	/**
@@ -257,11 +779,25 @@
 	 * @param int $bitfield the new rev_deleted value
 	 * @param string $comment
 	 */
-	function updateLog( $title, $count, $bitfield, $comment ) {
-		$log = new LogPage( 'delete' );
-		$reason = "changed $count revisions to $bitfield";
-		$reason .= ": $comment";
-		$log->addEntry( 'revision', $title, $reason );
+	function updateLog( $title, $count, $bitfield, $comment, $target, $prefix, $items = array() ) {
+		// Put things hidden from sysops in the oversight log
+		$logtype = ( $bitfield & Revision::DELETED_RESTRICTED ) ? 'oversight' : 'delete';
+		// Add params for effected page and ids
+		$params = array( $target->getPrefixedText(), $prefix, implode( ',', $items) );
+		$log = new LogPage( $logtype );	
+		if ( $prefix=='log' ) {
+    		$reason = wfMsgExt('logdelete-logaction', array('parsemag'), $count, $bitfield, $target->getPrefixedText() );
+			if ($comment) $reason .= ": $comment";
+			$log->addEntry( 'event', $title, $reason, $params );
+		} else if ( $prefix=='old' ) {
+    		$reason = wfMsgExt('revdelete-logaction', array('parsemag'), $count, $bitfield );
+			if ($comment) $reason .= ": $comment";
+			$log->addEntry( 'revision', $title, $reason, $params );
+		} else if ( $prefix=='file' ) {
+    		$reason = wfMsgExt('revdelete-logaction', array('parsemag'), $count, $bitfield );
+			if ($comment) $reason .= ": $comment";
+			$log->addEntry( 'file', $title, $reason, $params );
+		}
 	}
 }
 
Index: trunk/phase3/includes/Linker.php
===================================================================
--- trunk/phase3/includes/Linker.php	(revision 20445)
+++ trunk/phase3/includes/Linker.php	(revision 20446)
@@ -830,10 +830,13 @@
 	/**
 	 * Generate a user link if the current user is allowed to view it
 	 * @param $rev Revision object.
+	 * @param $isPublic, bool, show only if all users can see it
 	 * @return string HTML
 	 */
-	function revUserLink( $rev ) {
-		if( $rev->userCan( Revision::DELETED_USER ) ) {
+	function revUserLink( $rev, $isPublic = false ) {
+		if( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		} else if( $rev->userCan( Revision::DELETED_USER ) ) {
 			$link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() );
 		} else {
 			$link = wfMsgHtml( 'rev-deleted-user' );
@@ -843,27 +846,122 @@
 		}
 		return $link;
 	}
+	
+	/**
+	 * Generate a user link if the current user is allowed to view it
+	 * @param $event, log item.
+	 * @param $isPublic, bool, show only if all users can see it
+	 * @return string HTML
+	 */
+	function logUserLink( $event, $isPublic = false ) {
+		if( LogViewer::isDeleted( $event, LogViewer::DELETED_USER ) && $isPublic ) {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		} else if( LogViewer::userCan( $event, LogViewer::DELETED_USER ) ) {
+			if ( isset($event->user_name) ) {
+				$link = $this->userLink( $event->log_user, $event->user_name );
+			} else {
+				$user = $event->log_user;
+				$link = $this->userLink( $event->log_user, User::whoIs( $user ) );
+			}
+		} else {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		}
+		if( LogViewer::isDeleted( $event, LogViewer::DELETED_USER ) ) {
+			return '<span class="history-deleted">' . $link . '</span>';
+		}
+		return $link;
+	}
 
 	/**
+	 * Generate a user link if the current user is allowed to view it
+	 * @param $file, filestore file
+	 * @param $isPublic, bool, show only if all users can see it
+	 * @return string HTML
+	 */
+	function fileUserLink( $file, $isPublic = false ) {
+		if( $file->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		} else if( $file->userCan( Revision::DELETED_USER ) ) {
+			$link = $this->userLink( $file->mUser, $file->mUserText );
+		} else {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		}
+		if( $file->isDeleted( Revision::DELETED_USER ) ) {
+			return '<span class="history-deleted">' . $link . '</span>';
+		}
+		return $link;
+	}
+
+	/**
 	 * Generate a user tool link cluster if the current user is allowed to view it
 	 * @param $rev Revision object.
+	 * @param $isPublic, bool, show only if all users can see it
 	 * @return string HTML
 	 */
-	function revUserTools( $rev ) {
-		if( $rev->userCan( Revision::DELETED_USER ) ) {
+	function revUserTools( $rev, $isPublic = false ) {
+		if( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		} else if( $rev->userCan( Revision::DELETED_USER ) ) {
 			$link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() ) .
-				' ' .
-				$this->userToolLinks( $rev->getRawUser(), $rev->getRawUserText() );
+			' ' . $this->userToolLinks( $rev->getRawUser(), $rev->getRawUserText() );
 		} else {
 			$link = wfMsgHtml( 'rev-deleted-user' );
 		}
 		if( $rev->isDeleted( Revision::DELETED_USER ) ) {
+			return ' <span class="history-deleted">' . $link . '</span>';
+		}
+		return " $link";
+	}
+
+	/**
+	 * Generate a user tool link cluster if the current user is allowed to view it
+	 * @param $event, log item.
+	 * @param $isPublic, bool, show only if all users can see it
+	 * @return string HTML
+	 */
+	function logUserTools( $event, $isPublic = false ) {
+		if( LogViewer::isDeleted( $event, LogViewer::DELETED_USER ) && $isPublic ) {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		} else if( LogViewer::userCan( $event, LogViewer::DELETED_USER ) ) {
+			if ( isset($event->user_name) ) {
+				$link = $this->userLink( $event->log_user, $event->user_name ) . 
+				' ' . $this->userToolLinks( $event->log_user, $event->user_name );
+			} else {
+				$usertext = User::whoIs( $event->log_user );
+				$link = $this->userLink( $event->log_user, $usertext ) . 
+				' ' . $this->userToolLinks( $event->log_user, $usertext );
+			}
+		} else {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		}
+		if( LogViewer::isDeleted( $event, LogViewer::DELETED_USER ) ) {
 			return '<span class="history-deleted">' . $link . '</span>';
 		}
 		return $link;
 	}
-	
+
 	/**
+	 * Generate a user tool link cluster if the current user is allowed to view it
+	 * @param $file, filestore file
+	 * @param $isPublic, bool, show only if all users can see it
+	 * @return string HTML
+	 */
+	function fileUserTools( $file, $isPublic = false ) {
+		if( $file->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		} else if( $file->userCan( Revision::DELETED_USER ) ) {
+			$link = $this->userLink( $file->mUser, $file->mUserText ) .
+			$this->userToolLinks( $file->mUser, $file->mUserText );
+		} else {
+			$link = wfMsgHtml( 'rev-deleted-user' );
+		}
+		if( $file->isDeleted( Revision::DELETED_USER ) ) {
+			return '<span class="history-deleted">' . $link . '</span>';
+		}
+		return $link;
+	}
+		
+	/**
 	 * This function is called by all recent changes variants, by the page history,
 	 * and by the user contributions list. It is responsible for formatting edit
 	 * comments. It escapes any HTML in the comment, but adds some CSS to format
@@ -985,21 +1083,65 @@
 	 *
 	 * @param Revision $rev
 	 * @param bool $local Whether section links should refer to local page
+	 * @param $isPublic, show only if all users can see it
 	 * @return string HTML
 	 */
-	function revComment( Revision $rev, $local = false ) {
-		if( $rev->userCan( Revision::DELETED_COMMENT ) ) {
+	function revComment( Revision $rev, $local = false, $isPublic = false ) {
+		if( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
+			$block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
+		} else if( $rev->userCan( Revision::DELETED_COMMENT ) ) {
 			$block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle(), $local );
 		} else {
-			$block = " <span class=\"comment\">" .
-				wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
+			$block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
 		}
 		if( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
 			return " <span class=\"history-deleted\">$block</span>";
 		}
 		return $block;
 	}
+		
+	/**
+	 * Wrap and format the given event's comment block, if the current
+	 * user is allowed to view it.
+	 *
+	 * @param Revision $rev
+	 * @return string HTML
+	 */
+	function logComment( $event, $isPublic = false ) {
+		if( LogViewer::isDeleted( $event, LogViewer::DELETED_COMMENT ) && $isPublic ) {
+			$block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+		} else if( LogViewer::userCan( $event, LogViewer::DELETED_COMMENT ) ) {
+			$block = $this->commentBlock( LogViewer::getRawComment( $event ) );
+		} else {
+			$block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+		}
+		if( LogViewer::isDeleted( $event, LogViewer::DELETED_COMMENT ) ) {
+			return "<span class=\"history-deleted\">$block</span>";
+		}
+		return $block;
+	}
 
+	/**
+	 * Wrap and format the given file's comment block, if the current
+	 * user is allowed to view it.
+	 *
+	 * @param FileStore file object $file
+	 * @return string HTML
+	 */
+	function fileComment( $file, $isPublic = false ) {
+		if( $file->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
+			$block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+		} else if( $file->userCan( Revision::DELETED_COMMENT ) ) {
+			$block = $this->commentBlock( $file->mDescription );
+		} else {
+			$block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+		}
+		if( $file->isDeleted( Revision::DELETED_COMMENT ) ) {
+			return "<span class=\"history-deleted\">$block</span>";
+		}
+		return $block;
+	}
+
 	/** @todo document */
 	function tocIndent() {
 		return "\n<ul>";
Index: trunk/phase3/includes/Export.php
===================================================================
--- trunk/phase3/includes/Export.php	(revision 20445)
+++ trunk/phase3/includes/Export.php	(revision 20446)
@@ -139,7 +139,10 @@
 		$fname = "do_list_authors" ;
 		wfProfileIn( $fname );
 		$this->author_list = "<contributors>";
-		$sql = "SELECT DISTINCT rev_user_text,rev_user FROM {$page},{$revision} WHERE page_id=rev_page AND " . $cond ;
+		//rev_deleted
+		$deleted = '(rev_deleted & '.Revision::DELETED_USER.') !=1 ';
+		
+		$sql = "SELECT DISTINCT rev_user_text,rev_user FROM {$page},{$revision} WHERE page_id=rev_page AND $deleted AND " . $cond ;
 		$result = $this->db->query( $sql, $fname );
 		$resultset = $this->db->resultObject( $result );
 		while( $row = $resultset->fetchObject() ) {
Index: trunk/phase3/includes/SpecialBlockip.php
===================================================================
--- trunk/phase3/includes/SpecialBlockip.php	(revision 20445)
+++ trunk/phase3/includes/SpecialBlockip.php	(revision 20446)
@@ -45,7 +45,7 @@
 	var $BlockAddress, $BlockExpiry, $BlockReason;
 
 	function IPBlockForm( $par ) {
-		global $wgRequest;
+		global $wgRequest, $wgUser;
 
 		$this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) );
 		$this->BlockAddress = strtr( $this->BlockAddress, '_', ' ' );
@@ -59,6 +59,8 @@
 		$this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault );
 		$this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault );
 		$this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault );
+		# Re-check user's rights to hide names, very serious, defaults to 0
+		$this->BlockHideName = $wgRequest->getBool( 'wpHideName', 0 ) && $wgUser->isAllowed( 'hideuser' );
 	}
 
 	function showForm( $err ) {
@@ -131,6 +133,7 @@
 			</td>
 			");
 		}
+		
 		$wgOut->addHTML("
 		</tr>
 		<tr id='wpBlockOther'>
@@ -150,31 +153,46 @@
 		<tr id='wpAnonOnlyRow'>
 			<td>&nbsp;</td>
 			<td align=\"left\">
-				" . wfCheckLabel( wfMsg( 'ipbanononly' ),
+				" . wfCheckLabel( wfMsgHtml( 'ipbanononly' ),
 					'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly,
-					array( 'tabindex' => 4 ) ) . "
+					array( 'tabindex' => '4' ) ) . "
 			</td>
 		</tr>
 		<tr id='wpCreateAccountRow'>
 			<td>&nbsp;</td>
 			<td align=\"left\">
-				" . wfCheckLabel( wfMsg( 'ipbcreateaccount' ),
+				" . wfCheckLabel( wfMsgHtml( 'ipbcreateaccount' ),
 					'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount,
-					array( 'tabindex' => 5 ) ) . "
+					array( 'tabindex' => '5' ) ) . "
 			</td>
 		</tr>
 		<tr id='wpEnableAutoblockRow'>
 			<td>&nbsp;</td>
 			<td align=\"left\">
-				" . wfCheckLabel( wfMsg( 'ipbenableautoblock' ),
+				" . wfCheckLabel( wfMsgHtml( 'ipbenableautoblock' ),
 						'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock,
-							array( 'tabindex' => 6 ) ) . "
+							array( 'tabindex' => '6' ) ) . "
 			</td>
 		</tr>
+		");
+		// Allow some users to hide name from block log, blocklist and listusers
+		if ( $wgUser->isAllowed( 'hideuser' ) ) {
+			$wgOut->addHTML("
+			<tr>
+			<td>&nbsp;</td>
+				<td align=\"left\">
+					" . wfCheckLabel( wfMsgHtml( 'ipbhidename' ),
+							'wpHideName', 'wpHideName', $this->BlockHideName,
+								array( 'tabindex' => '6' ) ) . "
+				</td>
+			</tr>
+			");
+		}
+		$wgOut->addHTML("
 		<tr>
 			<td style='padding-top: 1em'>&nbsp;</td>
 			<td style='padding-top: 1em' align=\"left\">
-				" . Xml::submitButton( wfMsg( 'ipbsubmit' ),
+				" . Xml::submitButton( wfMsgHtml( 'ipbsubmit' ),
 							array( 'name' => 'wpBlock', 'tabindex' => '7' ) ) . "
 			</td>
 		</tr>
@@ -191,8 +209,6 @@
 			$this->showLogFragment( $wgOut, $user->getUserPage() );
 		} elseif( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->BlockAddress ) ) {
 			$this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) );
-		} elseif( preg_match( '/^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/', $this->BlockAddress ) ) {
-			$this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) );
 		}
 	}
 
@@ -200,9 +216,12 @@
 		global $wgOut, $wgUser, $wgSysopUserBans, $wgSysopRangeBans;
 
 		$userId = 0;
-		# Expand valid IPv6 addresses, usernames are left as is
-		$this->BlockAddress = IP::sanitizeIP( $this->BlockAddress );
-		# isIPv4() and IPv6() are used for final validation
+		$this->BlockAddress = trim( $this->BlockAddress );
+		# Expand valid IPv6 addresses
+		if ( IP::isIPv6( $this->BlockAddress ) ) {
+			$this->BlockAddress = IP::expandIP( $this->BlockAddress );
+		}
+		# The above validation is good enough that those below will suffice from here
 		$rxIP4 = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';
 		$rxIP6 = '\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}';
 		$rxIP = "($rxIP4|$rxIP6)";
@@ -283,7 +302,7 @@
 
 		$block = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
 			$this->BlockReason, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly,
-			$this->BlockCreateAccount, $this->BlockEnableAutoblock );
+			$this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName);
 
 		if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) {
 
@@ -300,8 +319,9 @@
 			$logParams[] = $expirestr;
 			$logParams[] = $this->blockLogFlags();
 
-			# Make log entry
-			$log = new LogPage( 'block' );
+			# Make log entry, if the name is hidden, put it in the oversight log
+			$log_type = ($this->BlockHideName) ? 'oversight' : 'block';
+			$log = new LogPage( $log_type );
 			$log->addEntry( 'block', Title::makeTitle( NS_USER, $this->BlockAddress ),
 			  $this->BlockReason, $logParams );
 
Index: trunk/phase3/includes/ChangesList.php
===================================================================
--- trunk/phase3/includes/ChangesList.php	(revision 20445)
+++ trunk/phase3/includes/ChangesList.php	(revision 20446)
@@ -1,5 +1,6 @@
 <?php
 /**
+ * @package MediaWiki
  * Contain class to show various lists of change:
  * - what's link here
  * - related changes
@@ -8,14 +9,16 @@
 
 /**
  * @todo document
+ * @package MediaWiki
  */
 class RCCacheEntry extends RecentChange
 {
 	var $secureName, $link;
-	var $curlink , $difflink, $lastlink , $usertalklink , $versionlink ;
+	var $curlinks, $difflink, $lastlink , $usertalklink , $versionlink ;
 	var $userlink, $timestamp, $watched;
 
-	function newFromParent( $rc ) {
+	function newFromParent( $rc )
+	{
 		$rc2 = new RCCacheEntry;
 		$rc2->mAttribs = $rc->mAttribs;
 		$rc2->mExtra = $rc->mExtra;
@@ -24,13 +27,14 @@
 } ;
 
 /**
+ * @package MediaWiki
  */
 class ChangesList {
 	# Called by history lists and recent changes
 	#
 
 	/** @todo document */
-	function __construct( &$skin ) {
+	function ChangesList( &$skin ) {
 		$this->skin =& $skin;
 		$this->preCacheMessages();
 	}
@@ -43,7 +47,7 @@
 	 * @return ChangesList derivative
 	 */
 	public static function newFromUser( &$user ) {
-		$sk = $user->getSkin();
+		$sk =& $user->getSkin();
 		$list = NULL;
 		if( wfRunHooks( 'FetchChangesList', array( &$user, &$sk, &$list ) ) ) {
 			return $user->getOption( 'usenewrc' ) ? new EnhancedChangesList( $sk ) : new OldChangesList( $sk );
@@ -77,7 +81,7 @@
 				: $nothing;
 		$f .= $bot ? '<span class="bot">' . $this->message['boteditletter'] . '</span>' : $nothing;
 		$f .= $patrolled ? '<span class="unpatrolled">!</span>' : $nothing;
-		return $f;
+		return "<tt>$f</tt>";
 	}
 
 	/**
@@ -103,6 +107,32 @@
 		}
 	}
 
+	/**
+	 * int $field one of DELETED_* bitfield constants
+	 * @return bool
+	 */
+	function isDeleted( $rc, $field ) {
+		return ($rc->mAttribs['rc_deleted'] & $field) == $field;
+	}
+	
+	/**
+	 * Determine if the current user is allowed to view a particular
+	 * field of this revision, if it's marked as deleted.
+	 * @param int $field
+	 * @return bool
+	 */
+	function userCan( $rc, $field ) {
+		if( ( $rc->mAttribs['rc_deleted'] & $field ) == $field ) {
+			global $wgUser;
+			$permission = ( $rc->mAttribs['rc_deleted'] & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
+				? 'hiderevision'
+				: 'deleterevision';
+			wfDebug( "Checking for $permission due to $field match on $rc->mAttribs['rc_deleted']\n" );
+			return $wgUser->isAllowed( $permission );
+		} else {
+			return true;
+		}
+	}
 
 	function insertMove( &$s, $rc ) {
 		# Diff
@@ -138,11 +168,12 @@
 		$s .= '(' . $this->skin->makeKnownLinkObj($title, $logname ) . ')';
 	}
 
-
 	function insertDiffHist(&$s, &$rc, $unpatrolled) {
 		# Diff link
-		if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) {
+		if( !$this->userCan($rc,Revision::DELETED_TEXT) ) {
 			$diffLink = $this->message['diff'];
+		} else if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG) {
+			$diffLink = $this->message['diff'];
 		} else {
 			$rcidparam = $unpatrolled
 				? array( 'rcid' => $rc->mAttribs['rc_id'] )
@@ -172,7 +203,12 @@
 		$params = ( $unpatrolled && $rc->mAttribs['rc_type'] == RC_NEW )
 			? 'rcid='.$rc->mAttribs['rc_id']
 			: '';
-		$articlelink = ' '. $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params );
+		if( $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
+			$articlelink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params );
+			$articlelink = '<span class="history-deleted">'.$articlelink.'</span>';
+		} else {
+		    $articlelink = ' '. $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params );
+		}
 		if($watched) $articlelink = '<strong>'.$articlelink.'</strong>';
 		global $wgContLang;
 		$articlelink .= $wgContLang->getDirMark();
@@ -188,15 +224,37 @@
 
 	/** Insert links to user page, user talk page and eventually a blocking link */
 	function insertUserRelatedLinks(&$s, &$rc) {
-		$s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
-		$s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+		if ( $this->isDeleted($rc,Revision::DELETED_USER) ) {
+		   $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-user') . '</span>';   
+		} else {
+		  $s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+		  $s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+		}
 	}
 
+	/** insert a formatted action */
+	function insertAction(&$s, &$rc) {
+		# Add comment
+		if( $rc->mAttribs['rc_type'] == RC_LOG ) {
+			// log action
+			if ( $this->isDeleted($rc,LogViewer::DELETED_ACTION) ) {
+				$s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+			} else {
+				$s .= ' ' . LogPage::actionText( $rc->mAttribs['rc_log_type'], $rc->mAttribs['rc_log_action'], $rc->getTitle(), $this->skin, LogPage::extractParams($rc->mAttribs['rc_params']), true, true );
+			}
+		}
+	}
+
 	/** insert a formatted comment */
 	function insertComment(&$s, &$rc) {
 		# Add comment
 		if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
-			$s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
+			// log comment
+			if ( $this->isDeleted($rc,Revision::DELETED_COMMENT) ) {
+				$s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>';
+			} else {
+				$s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
+			}
 		}
 	}
 
@@ -242,7 +300,6 @@
 		wfProfileIn( $fname );
 
 		# Extract DB fields into local scope
-		// FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
 		extract( $rc->mAttribs );
 
 		# Should patrol-related stuff be shown?
@@ -252,19 +309,23 @@
 
 		$s .= '<li>';
 
-		// moved pages
+		// Moved pages
 		if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
 			$this->insertMove( $s, $rc );
-		// log entries
-		} elseif ( $rc_namespace == NS_SPECIAL ) {
+		// Log entries (old format) or log targets, and special pages
+		} elseif( $rc_namespace == NS_SPECIAL ) {
 			list( $specialName, $specialSubpage ) = SpecialPage::resolveAliasWithSubpage( $rc_title );
 			if ( $specialName == 'Log' ) {
 				$this->insertLog( $s, $rc->getTitle(), $specialSubpage );
 			} else {
 				wfDebug( "Unexpected special page in recentchanges\n" );
 			}
-		// all other stuff
-		} else {
+		// Log entries
+		} elseif( $rc_log_type !='' ) {
+			$logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+			$this->insertLog( $s, $logtitle, $rc_log_type );
+		// All other stuff
+		}  else {
 			wfProfileIn($fname.'-page');
 
 			$this->insertDiffHist($s, $rc, $unpatrolled);
@@ -285,10 +346,16 @@
 		}
 
 		$this->insertUserRelatedLinks($s,$rc);
+		$this->insertAction($s, $rc);
 		$this->insertComment($s, $rc);
+		
+		# Mark revision as deleted
+		if ( $this->isDeleted($rc,Revision::DELETED_TEXT) )
+		   $s .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
+		if($rc->numberofWatchingusers > 0) {
+			$s .= ' ' . wfMsg('number_of_watching_users_RCview',  $wgContLang->formatNum($rc->numberofWatchingusers));
+		}
 
-		$s .=  rtrim(' ' . $this->numberofWatchingusers($rc->numberofWatchingusers));
-
 		$s .= "</li>\n";
 
 		wfProfileOut( $fname.'-rest' );
@@ -313,7 +380,6 @@
 		$rc = RCCacheEntry::newFromParent( $baseRC );
 
 		# Extract fields from DB into the function scope (rc_xxxx variables)
-		// FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
 		extract( $rc->mAttribs );
 		$curIdEq = 'curid=' . $rc_cur_id;
 
@@ -335,12 +401,14 @@
 			$rc->unpatrolled = false;
 		}
 
+		$showrev=true;
 		# Make article link
 		if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
 			$msg = ( $rc_type == RC_MOVE ) ? "1movedto2" : "1movedto2_redir";
 			$clink = wfMsg( $msg, $this->skin->makeKnownLinkObj( $rc->getTitle(), '', 'redirect=no' ),
 			  $this->skin->makeKnownLinkObj( $rc->getMovedToTitle(), '' ) );
-		} elseif( $rc_namespace == NS_SPECIAL ) {
+		} else if( $rc_namespace == NS_SPECIAL ) {
+		// Log entries (old format) and special pages
 			list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $rc_title );
 			if ( $specialName == 'Log' ) {
 				# Log updates, etc
@@ -350,7 +418,16 @@
 				wfDebug( "Unexpected special page in recentchanges\n" );
 				$clink = '';
 			}
-		} elseif( $rc->unpatrolled && $rc_type == RC_NEW ) {
+		} elseif ( $rc_log_type !='' ) {
+		// Log entries
+			$logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+			$logname = LogPage::logName( $rc_log_type );
+			$clink = '(' . $this->skin->makeKnownLinkObj($logtitle, $logname ) . ')';
+		} if ( $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
+		    $clink = '<span class="history-deleted">' . $this->skin->makeKnownLinkObj( $rc->getTitle(), '' ) . '</span>';
+		    if ( !ChangesList::userCan($rc,Revision::DELETED_TEXT) )
+		       $showrev=false;
+		} else if( $rc->unpatrolled && $rc_type == RC_NEW ) {
 			# Unpatrolled new page, give rc_id in query
 			$clink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '', "rcid={$rc_id}" );
 		} else {
@@ -373,7 +450,10 @@
 		$querydiff = $curIdEq."&diff=$rc_this_oldid&oldid=$rc_last_oldid$rcIdQuery";
 		$aprops = ' tabindex="'.$baseRC->counter.'"';
 		$curLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['cur'], $querycur, '' ,'', $aprops );
-		if( $rc_type == RC_NEW || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
+		if ( !$showrev ) {
+		   $curLink = $this->message['cur'];
+		   $diffLink = $this->message['diff'];
+		} else if( $rc_type == RC_NEW || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
 			if( $rc_type != RC_NEW ) {
 				$curLink = $this->message['cur'];
 			}
@@ -383,21 +463,27 @@
 		}
 
 		# Make "last" link
-		if( $rc_last_oldid == 0 || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
+		if ( !$showrev ) {
+		    $lastLink = $this->message['last'];
+		} else if( $rc_last_oldid == 0 || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
 			$lastLink = $this->message['last'];
 		} else {
 			$lastLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['last'],
-			  $curIdEq.'&diff='.$rc_this_oldid.'&oldid='.$rc_last_oldid . $rcIdQuery );
+			$curIdEq.'&diff='.$rc_this_oldid.'&oldid='.$rc_last_oldid . $rcIdQuery );
 		}
+		
+		# Make user links
+		if ( $this->isDeleted($rc,Revision::DELETED_USER) ) {
+		   	$rc->userlink = ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-user') . '</span>';
+		} else {
+			$rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text );
+			$rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text );
+		}
 
-		$rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text );
-
 		$rc->lastlink = $lastLink;
 		$rc->curlink  = $curLink;
 		$rc->difflink = $diffLink;
 
-		$rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text );
-
 		# Put accumulated information into the cache, for later display
 		# Page moves go on their own line
 		$title = $rc->getTitle();
@@ -419,10 +505,11 @@
 	 */
 	function recentChangesBlockGroup( $block ) {
 		global $wgLang, $wgContLang, $wgRCShowChangedSize;
-		$r = '';
+		$r = '<table cellpadding="0" cellspacing="0"><tr>';
 
 		# Collate list of users
 		$isnew = false;
+		$namehidden = true;
 		$unpatrolled = false;
 		$userlinks = array();
 		foreach( $block as $rcObj ) {
@@ -430,6 +517,11 @@
 			if( $rcObj->mAttribs['rc_new'] ) {
 				$isnew = true;
 			}
+			// if all log actions to this page were hidden, then don't
+			// give the name of the affected page for this block
+			if( !($rcObj->mAttribs['rc_deleted'] & LogViewer::DELETED_ACTION) ) {
+				$namehidden = false;
+			}
 			$u = $rcObj->userlink;
 			if( !isset( $userlinks[$u] ) ) {
 				$userlinks[$u] = 0;
@@ -463,24 +555,25 @@
 		$toggleLink = "javascript:toggleVisibility('$rci','$rcm','$rcl')";
 		$tl  = '<span id="'.$rcm.'"><a href="'.$toggleLink.'">' . $this->sideArrow() . '</a></span>';
 		$tl .= '<span id="'.$rcl.'" style="display:none"><a href="'.$toggleLink.'">' . $this->downArrow() . '</a></span>';
-		$r .= $tl;
+		$r .= '<td valign="top">'.$tl;
 
 		# Main line
-		$r .= '<tt>';
-		$r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, '&nbsp;', $bot );
+		$r .= ' '.$this->recentChangesFlags( $isnew, false, $unpatrolled, '&nbsp;', $bot );
 
 		# Timestamp
-		$r .= ' '.$block[0]->timestamp.' </tt>';
+		$r .= ' '.$block[0]->timestamp.'&nbsp;&nbsp;</td><td>';
 
 		# Article link
-		$r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
+		if ( $namehidden )
+			$r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+		else
+			$r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
 		$r .= $wgContLang->getDirMark();
 
 		$curIdEq = 'curid=' . $block[0]->mAttribs['rc_cur_id'];
 		$currentRevision = $block[0]->mAttribs['rc_this_oldid'];
 		if( $block[0]->mAttribs['rc_type'] != RC_LOG ) {
 			# Changes
-
 			$n = count($block);
 			static $nchanges = array();
 			if ( !isset( $nchanges[$n] ) ) {
@@ -490,77 +583,94 @@
 
 			$r .= ' (';
 
-			if( $isnew ) {
+			if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
+			    $r .= $nchanges[$n];
+			} else if( $isnew ) {
 				$r .= $nchanges[$n];
 			} else {
 				$r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(),
 					$nchanges[$n], $curIdEq."&diff=$currentRevision&oldid=$oldid" );
 			}
 
-			$r .= ') . . ';
-
 			# Character difference
 			$chardiff = $rcObj->getCharacterDifference( $block[ count( $block ) - 1 ]->mAttribs['rc_old_len'],
 					$block[0]->mAttribs['rc_new_len'] );
 			if( $chardiff == '' ) {
-				$r .= ' (';
+				$r .= '; ';
 			} else {
-				$r .= ' ' . $chardiff. ' . . (';
+				$r .= '; ' . $chardiff . ' ';
 			}
-			
 
 			# History
-			$r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(),
-				$this->message['history'], $curIdEq.'&action=history' );
+			$r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(), $this->message['history'], $curIdEq.'&action=history' );
+
 			$r .= ')';
 		}
 
 		$r .= $users;
 
-		$r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers);
-		$r .= "<br />\n";
+		if($block[0]->numberofWatchingusers > 0) {
+			global $wgContLang;
+			$r .= wfMsg('number_of_watching_users_RCview',  $wgContLang->formatNum($block[0]->numberofWatchingusers));
+		}
+		$r .= "</td></tr></table>\n";
 
 		# Sub-entries
-		$r .= '<div id="'.$rci.'" style="display:none">';
+		$r .= '<div id="'.$rci.'" style="display:none; font-size:95%;"><table cellpadding="0" cellspacing="0">';
 		foreach( $block as $rcObj ) {
 			# Get rc_xxxx variables
-			// FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
 			extract( $rcObj->mAttribs );
 
-			$r .= $this->spacerArrow();
-			$r .= '<tt>&nbsp; &nbsp; &nbsp; &nbsp;';
+			#$r .= '<tr><td valign="top">'.$this->spacerArrow();
+			$r .= '<tr><td valign="top">'.$this->spacerIndent();
+			$r .= '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
 			$r .= $this->recentChangesFlags( $rc_new, $rc_minor, $rcObj->unpatrolled, '&nbsp;', $rc_bot );
-			$r .= '&nbsp;</tt>';
+			$r .= '&nbsp;&nbsp;</td><td valign="top">';
 
 			$o = '';
 			if( $rc_this_oldid != 0 ) {
 				$o = 'oldid='.$rc_this_oldid;
 			}
+			# Revision link
 			if( $rc_type == RC_LOG ) {
-				$link = $rcObj->timestamp;
+				$link = $rcObj->timestamp.' ';
+			} else if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
+				$link = '<span class="history-deleted">'.$rcObj->timestamp.'</span> ';
 			} else {
 				$link = $this->skin->makeKnownLinkObj( $rcObj->getTitle(), $rcObj->timestamp, $curIdEq.'&'.$o );
+				if( $this->isDeleted($rcObj,Revision::DELETED_TEXT) )
+					$link = '<span class="history-deleted">'.$link.'</span> ';
 			}
-			$link = '<tt>'.$link.'</tt>';
-
 			$r .= $link;
-			$r .= ' (';
-			$r .= $rcObj->curlink;
-			$r .= '; ';
-			$r .= $rcObj->lastlink;
-			$r .= ') . . ';
+			
+			if ( !$rc_log_type ) {
+				$r .= ' (';
+				$r .= $rcObj->curlink;
+				$r .= '; ';
+				$r .= $rcObj->lastlink;
+				$r .= ')';
+			} else {
+				$logname = LogPage::logName( $rc_log_type );
+				$logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+				$r .= '(' . $this->skin->makeKnownLinkObj($logtitle, $logname ) . ')';
+			}
+			$r .= ' . . ';
 
 			# Character diff
 			if( $wgRCShowChangedSize ) {
 				$r .= ( $rcObj->getCharacterDifference() == '' ? '' : $rcObj->getCharacterDifference() . ' . . ' ) ;
 			}
-
+			# User links
 			$r .= $rcObj->userlink;
 			$r .= $rcObj->usertalklink;
-			$r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
-			$r .= "<br />\n";
+			// log action
+			parent::insertAction($r, $rcObj);
+			// log comment
+			parent::insertComment($r, $rcObj);
+
+			$r .= "</td></tr>\n";
 		}
-		$r .= "</div>\n";
+		$r .= "</table></div>\n";
 
 		$this->rcCacheIndex++;
 		return $r;
@@ -617,8 +727,23 @@
 	 * @access private
 	 */
 	function spacerArrow() {
+	//FIXME: problems with FF 1.5x
 		return $this->arrow( '', ' ' );
 	}
+	
+	/**
+	 * Generate HTML for the equivilant of a spacer image for tables
+	 * @return string HTML <td> tag
+	 * @access private
+	 */	
+	function spacerColumn() {
+		return '<td width="12"></td>';
+	}	
+	
+	// Adds a few spaces
+	function spacerIndent() {
+		return '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
+	}	
 
 	/**
 	 * Enhanced RC ungrouped line.
@@ -628,50 +753,69 @@
 		global $wgContLang, $wgRCShowChangedSize;
 
 		# Get rc_xxxx variables
-		// FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
 		extract( $rcObj->mAttribs );
 		$curIdEq = 'curid='.$rc_cur_id;
 
-		$r = '';
+		$r = '<table cellspacing="0" cellpadding="0"><tr><td>';
 
-		# Spacer image
-		$r .= $this->spacerArrow();
-
+		# spacerArrow() causes issues in FF
+		$r .= $this->spacerColumn();
+		$r .= '<td valign="top">';
+		
 		# Flag and Timestamp
-		$r .= '<tt>';
-
 		if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
-			$r .= '&nbsp;&nbsp;&nbsp;';
+			$r .= '&nbsp;&nbsp;&nbsp;&nbsp;';
 		} else {
-			$r .= $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, '&nbsp;', $rc_bot );
+			$r .= '&nbsp;'.$this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, '&nbsp;', $rc_bot );
 		}
-		$r .= ' '.$rcObj->timestamp.' </tt>';
-
+		$r .= '&nbsp;'.$rcObj->timestamp.'&nbsp;&nbsp;</td><td>';
+		
 		# Article link
-		$r .= $this->maybeWatchedLink( $rcObj->link, $rcObj->watched );
-
-		# Diff
-		$r .= ' ('. $rcObj->difflink .'; ';
-
-		# Hist
-		$r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), $curIdEq.'&action=history' ) . ') . . ';
-
+		if ( $rc_log_type !='' ) {
+			$logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+			$logname = LogPage::logName( $rc_log_type );
+			$r .= '(' . $this->skin->makeKnownLinkObj($logtitle, $logname ) . ')';
+		// All other stuff
+		} else {
+			$r .= $this->maybeWatchedLink( $rcObj->link, $rcObj->watched );
+		}
+		if ( $rc_type != RC_LOG ) {
+		   # Diff
+		   $r .= ' ('. $rcObj->difflink .'; ';
+		   # Hist
+		   $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), $curIdEq.'&action=history' ) . ')';
+		}
+		$r .= ' . . ';
+		
 		# Character diff
 		if( $wgRCShowChangedSize ) {
 			$r .= ( $rcObj->getCharacterDifference() == '' ? '' : '&nbsp;' . $rcObj->getCharacterDifference() . ' . . ' ) ;
 		}
 
 		# User/talk
-		$r .= $rcObj->userlink . $rcObj->usertalklink;
+		$r .= ' '.$rcObj->userlink . $rcObj->usertalklink;
 
 		# Comment
 		if( $rc_type != RC_MOVE && $rc_type != RC_MOVE_OVER_REDIRECT ) {
-			$r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
+			// log action
+			if ( $this->isDeleted($rcObj,LogViewer::DELETED_ACTION) ) {
+			   $r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+			} else {
+				$r .= ' ' . LogPage::actionText( $rc_log_type, $rc_log_action, $rcObj->getTitle(), $this->skin, LogPage::extractParams($rc_params), true, true );
+			} 
+			// log comment
+			if ( $this->isDeleted($rcObj,LogViewer::DELETED_COMMENT) ) {
+			   $r .= ' <span class="history-deleted">' . wfMsg('rev-deleted-comment') . '</span>';
+			} else {
+			  $r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
+			}
 		}
 
-		$r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers);
+		if( $rcObj->numberofWatchingusers > 0 ) {
+			$r .= wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($rcObj->numberofWatchingusers));
+		}
 
-		$r .= "<br />\n";
+		$r .= "</td></tr></table>\n";
 		return $r;
 	}
 
Index: trunk/phase3/includes/SpecialUndelete.php
===================================================================
--- trunk/phase3/includes/SpecialUndelete.php	(revision 20445)
+++ trunk/phase3/includes/SpecialUndelete.php	(revision 20446)
@@ -99,7 +99,7 @@
 	function listRevisions() {
 		$dbr = wfGetDB( DB_SLAVE );
 		$res = $dbr->select( 'archive',
-			array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len' ),
+			array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_rev_id', 'ar_deleted', 'ar_len' ),
 			array( 'ar_namespace' => $this->title->getNamespace(),
 			       'ar_title' => $this->title->getDBkey() ),
 			'PageArchive::listRevisions',
@@ -116,7 +116,7 @@
 	 * @return ResultWrapper
 	 * @fixme Does this belong in Image for fuller encapsulation?
 	 */
-	function listFiles() {
+	function listFiles() {		
 		if( $this->title->getNamespace() == NS_IMAGE ) {
 			$dbr = wfGetDB( DB_SLAVE );
 			$res = $dbr->select( 'filearchive',
@@ -130,7 +130,8 @@
 					'fa_description',
 					'fa_user',
 					'fa_user_text',
-					'fa_timestamp' ),
+					'fa_timestamp',
+					'fa_deleted' ),
 				array( 'fa_name' => $this->title->getDbKey() ),
 				__METHOD__,
 				array( 'ORDER BY' => 'fa_timestamp DESC' ) );
@@ -151,14 +152,25 @@
 		$rev = $this->getRevision( $timestamp );
 		return $rev ? $rev->getText() : null;
 	}
+	
+	function getRevisionConds( $timestamp, $id ) {
+		if ( $id ) {
+			$id = intval($id);
+			return "ar_rev_id=$id";
+		} else if ( $timestamp ) {
+			return "ar_timestamp=$timestamp";
+		} else {
+			return 'ar_rev_id=0';
+		}
+	}
 
 	/**
 	 * Return a Revision object containing data for the deleted revision.
-	 * Note that the result *may* or *may not* have a null page ID.
-	 * @param string $timestamp
+	 * Note that the result *may* have a null page ID.
+	 * @param string $timestamp or $id
 	 * @return Revision
 	 */
-	function getRevision( $timestamp ) {
+	function getRevision( $timestamp, $id=null ) {
 		$dbr = wfGetDB( DB_SLAVE );
 		$row = $dbr->selectRow( 'archive',
 			array(
@@ -171,10 +183,11 @@
 				'ar_minor_edit',
 				'ar_flags',
 				'ar_text_id',
+				'ar_deleted',
 				'ar_len' ),
 			array( 'ar_namespace' => $this->title->getNamespace(),
 			       'ar_title' => $this->title->getDbkey(),
-			       'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
+			       $this->getRevisionConds( $dbr->timestamp($timestamp), $id ) ),
 			__METHOD__ );
 		if( $row ) {
 			return new Revision( array(
@@ -188,7 +201,9 @@
 				'user_text'  => $row->ar_user_text,
 				'timestamp'  => $row->ar_timestamp,
 				'minor_edit' => $row->ar_minor_edit,
-				'text_id'    => $row->ar_text_id ) );
+				'text_id'    => $row->ar_text_id,
+				'deleted'    => $row->ar_deleted,
+				'len'        => $row->ar_len) );
 		} else {
 			return null;
 		}
@@ -260,7 +275,7 @@
 	 *
 	 * @return true on success.
 	 */
-	function undelete( $timestamps, $comment = '', $fileVersions = array() ) {
+	function undelete( $timestamps, $comment = '', $fileVersions = array(), $Unsuppress = false) {
 		// If both the set of text revisions and file revisions are empty,
 		// restore everything. Otherwise, just restore the requested items.
 		$restoreAll = empty( $timestamps ) && empty( $fileVersions );
@@ -268,17 +283,20 @@
 		$restoreText = $restoreAll || !empty( $timestamps );
 		$restoreFiles = $restoreAll || !empty( $fileVersions );
 		
-		if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) {
-			$img = new Image( $this->title );
-			$filesRestored = $img->restore( $fileVersions );
+		if( $restoreText ) {
+			$textRestored = $this->undeleteRevisions( $timestamps, $Unsuppress );
 		} else {
-			$filesRestored = 0;
+			$textRestored = 0;
 		}
 		
-		if( $restoreText ) {
-			$textRestored = $this->undeleteRevisions( $timestamps );
+		if ( $restoreText && !$textRestored) {
+		// if the image page didn't restore right, don't restore the file either!!!
+			$filesRestored = 0;
+		} else if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) {
+			$img = new Image( $this->title );
+			$filesRestored = $img->restore( $fileVersions, $Unsuppress );
 		} else {
-			$textRestored = 0;
+			$filesRestored = 0;
 		}
 		
 		// Touch the log!
@@ -286,14 +304,14 @@
 		$log = new LogPage( 'delete' );
 		
 		if( $textRestored && $filesRestored ) {
-			$reason = wfMsgForContent( 'undeletedrevisions-files',
+			$reason = wfMsgExt( 'undeletedrevisions-files', array('parsemag'),
 				$wgContLang->formatNum( $textRestored ),
 				$wgContLang->formatNum( $filesRestored ) );
 		} elseif( $textRestored ) {
-			$reason = wfMsgForContent( 'undeletedrevisions',
+			$reason = wfMsgExt( 'undeletedrevisions', array('parsemag'),
 				$wgContLang->formatNum( $textRestored ) );
 		} elseif( $filesRestored ) {
-			$reason = wfMsgForContent( 'undeletedfiles',
+			$reason = wfMsgExt( 'undeletedfiles', array('parsemag'),
 				$wgContLang->formatNum( $filesRestored ) );
 		} else {
 			wfDebug( "Undelete: nothing undeleted...\n" );
@@ -315,10 +333,11 @@
 	 * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
 	 * @param string $comment
 	 * @param array $fileVersions
+	 * @param bool $Unsuppress, remove all ar_deleted/fa_deleted restrictions of seletected revs
 	 *
 	 * @return int number of revisions restored
 	 */
-	private function undeleteRevisions( $timestamps ) {
+	private function undeleteRevisions( $timestamps, $Unsuppress = false ) {
 		global $wgDBtype;
 
 		$restoreAll = empty( $timestamps );
@@ -362,7 +381,7 @@
 		}
 
 		/**
-		 * Restore each revision...
+		 * Select each archived revision...
 		 */
 		$result = $dbw->select( 'archive',
 			/* fields */ array(
@@ -375,6 +394,7 @@
 				'ar_minor_edit',
 				'ar_flags',
 				'ar_text_id',
+				'ar_deleted',
 				'ar_len' ),
 			/* WHERE */ array(
 				'ar_namespace' => $this->title->getNamespace(),
@@ -384,15 +404,30 @@
 			/* options */ array(
 				'ORDER BY' => 'ar_timestamp' )
 			);
-		if( $dbw->numRows( $result ) < count( $timestamps ) ) {
+		$rev_count = $dbw->numRows( $result );
+		if( $rev_count < count( $timestamps ) ) {
+			# Remove page stub per failure
+			$dbw->delete( 'page', array( 'page_id' => $pageId ), __METHOD__);
 			wfDebug( __METHOD__.": couldn't find all requested rows\n" );
 			return false;
 		}
 		
+		$ret = $dbw->resultObject( $result );		
+		// FIXME: We don't currently handle well changing the top revision's settings
+		$ret->seek( $rev_count - 1 );
+		$last_row = $ret->fetchObject();
+		if ( !$Unsuppress && $last_row->ar_deleted && $last_row->ar_rev_id > $previousRevId ) {
+			# Remove page stub per failure
+			$dbw->delete( 'page', array( 'page_id' => $pageId ), __METHOD__);
+			wfDebug( __METHOD__.": couldn't find all requested rows or not all of them could be restored\n" );
+			return false;
+		}
+
 		$revision = null;
 		$restored = 0;
-
-		while( $row = $dbw->fetchObject( $result ) ) {
+		
+		$ret->seek( 0 );
+		while( $row = $ret->fetchObject() ) {
 			if( $row->ar_text_id ) {
 				// Revision was deleted in 1.5+; text is in
 				// the regular text table, use the reference.
@@ -415,7 +450,8 @@
 				'timestamp'  => $row->ar_timestamp,
 				'minor_edit' => $row->ar_minor_edit,
 				'text_id'    => $row->ar_text_id,
-				'len'		 => $row->ar_len
+				'deleted' 	 => ($Unsuppress) ? 0 : $row->ar_deleted,
+				'len'        => $row->ar_len
 				) );
 			$revision->insertOn( $dbw );
 			$restored++;
@@ -477,6 +513,7 @@
 		$this->mRestore = $request->getCheck( 'restore' ) && $posted;
 		$this->mPreview = $request->getCheck( 'preview' ) && $posted;
 		$this->mComment = $request->getText( 'wpComment' );
+		$this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'oversight' );
 		
 		if( $par != "" ) {
 			$this->mTarget = $par;
@@ -512,21 +549,25 @@
 	}
 
 	function execute() {
-		global $wgOut;
+		global $wgOut, $wgUser;
 		if ( $this->mAllowed ) {
-			$wgOut->setPagetitle( wfMsg( "undeletepage" ) );
+			$wgOut->setPagetitle( wfMsgHtml( "undeletepage" ) );
 		} else {
-			$wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) );
+			$wgOut->setPagetitle( wfMsgHtml( "viewdeletedpage" ) );
 		}
 		
 		if( is_null( $this->mTargetObj ) ) {
-			$this->showSearchForm();
+		# Not all users can just browse every deleted page from the list
+			if ( $wgUser->isAllowed( 'browsearchive' ) ) {
+				$this->showSearchForm();
 
-			# List undeletable articles
-			if( $this->mSearchPrefix ) {
-				$result = PageArchive::listPagesByPrefix(
-					$this->mSearchPrefix );
-				$this->showList( $result );
+				# List undeletable articles
+				if( $this->mSearchPrefix ) {
+					$result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
+					$this->showList( $result );
+				}
+			} else {
+				$wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) );
 			}
 			return;
 		}
@@ -534,7 +575,14 @@
 			return $this->showRevision( $this->mTimestamp );
 		}
 		if( $this->mFile !== null ) {
-			return $this->showFile( $this->mFile );
+			$file = new ArchivedFile( $this->mTargetObj, '', $this->mFile );
+			// Check if user is allowed to see this file
+			if ( !$file->userCan( Image::DELETED_FILE ) ) {
+				$wgOut->permissionRequired( 'hiderevision' ); 
+				return false;
+			} else {
+				return $this->showFile( $this->mFile );
+			}
 		}
 		if( $this->mRestore && $this->mAction == "submit" ) {
 			return $this->undelete();
@@ -544,7 +592,7 @@
 
 	function showSearchForm() {
 		global $wgOut, $wgScript;
-		$wgOut->addWikiText( wfMsg( 'undelete-header' ) );
+		$wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) );
 		
 		$wgOut->addHtml(
 			Xml::openElement( 'form', array(
@@ -563,6 +611,7 @@
 			'</form>' );
 	}
 
+	// Generic list of deleted pages
 	/* private */ function showList( $result ) {
 		global $wgLang, $wgContLang, $wgUser, $wgOut;
 		
@@ -612,11 +661,22 @@
 			return;
 		}
 		
+		if( $rev->isDeleted(Revision::DELETED_TEXT) ) {
+			if( !$rev->userCan(Revision::DELETED_TEXT) ) {
+				$wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
+				return;
+			} else {
+				$wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
+				$wgOut->addHTML( '<br/>' );
+				// and we are allowed to see...
+				}
+			}
+		
 		wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
 		
 		if( $this->mPreview ) {
 			$wgOut->addHtml( "<hr />\n" );
-			$wgOut->addWikiTextTitle( $rev->getText(), $this->mTargetObj, false );
+			$wgOut->addWikiTextTitle( $rev->revText(), $this->mTargetObj, false );
 		}
 
 		$wgOut->addHtml(
@@ -624,7 +684,7 @@
 					'readonly' => true,
 					'cols' => intval( $wgUser->getOption( 'cols' ) ),
 					'rows' => intval( $wgUser->getOption( 'rows' ) ) ),
-				$rev->getText() . "\n" ) .
+				$rev->revText() . "\n" ) .
 			wfOpenElement( 'div' ) .
 			wfOpenElement( 'form', array(
 				'method' => 'post',
@@ -674,7 +734,7 @@
 	/* private */ function showHistory() {
 		global $wgLang, $wgUser, $wgOut;
 
-		$sk = $wgUser->getSkin();
+		$this->sk = $wgUser->getSkin();
 		if ( $this->mAllowed ) {
 			$wgOut->setPagetitle( wfMsg( "undeletepage" ) );
 		} else {
@@ -690,9 +750,10 @@
 		}
 		*/
 		if ( $this->mAllowed ) {
-			$wgOut->addWikiText( wfMsg( "undeletehistory" ) );
+			$wgOut->addWikiText( '<p>' . wfMsgHtml( "undeletehistory" ) . '</p>' );
+			$wgOut->addHtml( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' );
 		} else {
-			$wgOut->addWikiText( wfMsg( "undeletehistorynoadmin" ) );
+			$wgOut->addWikiText( wfMsgHtml( "undeletehistorynoadmin" ) );
 		}
 
 		# List all stored revisions
@@ -738,7 +799,16 @@
 					array( 'page' => $this->mTargetObj->getPrefixedText(),
 						   'type' => 'delete' ) ) ) );
 		$logViewer->showList( $wgOut );
-		
+		# Show relevant lines from the oversight log if user is allowed to see it:
+		if ( $wgUser->isAllowed( 'oversight' ) ) {
+			$wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'oversight' ) ) . "</h2>\n" );
+			$logViewer = new LogViewer(
+				new LogReader(
+					new FauxRequest(
+						array( 'page' => $this->mTargetObj->getPrefixedText(),
+							   'type' => 'oversight' ) ) ) );
+			$logViewer->showList( $wgOut );
+		}
 		if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
 			# Format the user-visible controls (comment field, submission button)
 			# in a nice little table
@@ -746,6 +816,10 @@
 			$table .= '<td colspan="2">' . wfMsgWikiHtml( 'undeleteextrahelp' ) . '</td></tr><tr>';
 			$table .= '<td align="right"><strong>' . wfMsgHtml( 'undeletecomment' ) . '</strong></td>';
 			$table .= '<td>' . wfInput( 'wpComment', 50, $this->mComment ) . '</td>';
+			if ( $wgUser->isAllowed( 'oversight' ) ) {
+				$table .= '</tr><tr><td>&nbsp;</td><td>';
+				$table .= Xml::checkLabel( wfMsg( 'revdelete-unsuppress' ), 'wpUnsuppress', 'wpUnsuppress', false, array( 'tabindex' => '2' ) );
+			}
 			$table .= '</tr><tr><td>&nbsp;</td><td>';
 			$table .= wfSubmitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore' ) );
 			$table .= wfElement( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ) ) );
@@ -761,26 +835,48 @@
 			$target = urlencode( $this->mTarget );
 			while( $row = $revisions->fetchObject() ) {
 				$ts = wfTimestamp( TS_MW, $row->ar_timestamp );
+				// We don't handle top edits with rev_deleted 
 				if ( $this->mAllowed ) {
-					$checkBox = wfCheck( "ts$ts" );
-					$pageLink = $sk->makeKnownLinkObj( $titleObj,
-						$wgLang->timeanddate( $ts, true ),
-						"target=$target&timestamp=$ts" );
+					$checkBox = wfCheck( "ts$ts", false );
+					$titleObj = SpecialPage::getTitleFor( "Undelete" );
+					$pageLink = $this->getPageLink( $row, $titleObj, $ts, $target );
 				} else {
 					$checkBox = '';
 					$pageLink = $wgLang->timeanddate( $ts, true );
 				}
-				$userLink = $sk->userLink( $row->ar_user, $row->ar_user_text ) . $sk->userToolLinks( $row->ar_user, $row->ar_user_text );
+				$userLink = $this->getUser( $row );
 				$stxt = '';
 				if (!is_null($size = $row->ar_len)) {
 					if ($size == 0)
 					$stxt = wfMsgHtml('historyempty');
-				else
+					else
 					$stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) );
 				}
-				$comment = $sk->commentBlock( $row->ar_comment );
-				$wgOut->addHTML( "<li>$checkBox $pageLink . . $userLink $stxt $comment</li>\n" );
-	
+				$comment = $this->getComment( $row );
+				$rd='';
+				if( $wgUser->isAllowed( 'deleterevision' ) ) {
+					$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+					if( !$this->userCan( $row, Revision::DELETED_RESTRICTED ) ) {
+					// If revision was hidden from sysops
+						$del = wfMsgHtml( 'rev-delundel' );			
+					} else {
+						$del = $this->sk->makeKnownLinkObj( $revdel,
+							wfMsg( 'rev-delundel' ),
+							'target=' . urlencode( $this->mTarget ) .
+							'&arid=' . urlencode( $row->ar_rev_id ) );
+						// Bolden oversighted content
+						if( $this->isDeleted( $row, Revision::DELETED_RESTRICTED ) )
+							$del = "<strong>$del</strong>";
+					}
+				$rd = "<tt>(<small>$del</small>)</tt>";
+				}
+				
+				$dflag='';
+				if( $this->isDeleted( $row, Revision::DELETED_TEXT ) )
+					$dflag = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
+				
+				// Do we still need checkboxes?
+				$wgOut->addHTML( "<li>$checkBox $rd $pageLink . . $userLink $stxt $comment$dflag</li>\n" );
 			}
 			$revisions->free();
 			$wgOut->addHTML("</ul>");
@@ -795,17 +891,21 @@
 			while( $row = $files->fetchObject() ) {
 				$ts = wfTimestamp( TS_MW, $row->fa_timestamp );
 				if ( $this->mAllowed && $row->fa_storage_key ) {
-					$checkBox = wfCheck( "fileid" . $row->fa_id );
+					if ( !$this->userCan( $row, Revision::DELETED_RESTRICTED ) )
+					// Grey out boxes to files restricted beyond this user.
+					// In the future, all image storage may use FileStore, allowing
+					// for such items to be restored and retain their fa_deleted status
+						$checkBox = wfCheck( "", false, array("disabled" => "disabled") );
+					else
+						$checkBox = wfCheck( "fileid" . $row->fa_id );
 					$key = urlencode( $row->fa_storage_key );
 					$target = urlencode( $this->mTarget );
-					$pageLink = $sk->makeKnownLinkObj( $titleObj,
-						$wgLang->timeanddate( $ts, true ),
-						"target=$target&file=$key" );
+					$pageLink = $this->getFileLink( $row, $titleObj, $ts, $target, $key );
 				} else {
 					$checkBox = '';
 					$pageLink = $wgLang->timeanddate( $ts, true );
 				}
- 				$userLink = $sk->userLink( $row->fa_user, $row->fa_user_text ) . $sk->userToolLinks( $row->fa_user, $row->fa_user_text );
+ 				$userLink = $this->getFileUser( $row );
 				$data =
 					wfMsgHtml( 'widthheight',
 						$wgLang->formatNum( $row->fa_width ),
@@ -813,8 +913,25 @@
 					' (' .
 					wfMsgHtml( 'nbytes', $wgLang->formatNum( $row->fa_size ) ) .
 					')';
-				$comment = $sk->commentBlock( $row->fa_description );
-				$wgOut->addHTML( "<li>$checkBox $pageLink . . $userLink $data $comment</li>\n" );
+				$comment = $this->getFileComment( $row );
+				$rd='';
+				if( $wgUser->isAllowed( 'deleterevision' ) ) {
+					$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+					if( !$this->userCan( $row, Image::DELETED_RESTRICTED ) ) {
+					// If revision was hidden from sysops
+						$del = wfMsgHtml( 'rev-delundel' );			
+					} else {
+						$del = $this->sk->makeKnownLinkObj( $revdel,
+							wfMsg( 'rev-delundel' ),
+							'target=' . urlencode( $this->mTarget ) .
+							'&fileid=' . urlencode( $row->fa_id ) );
+						// Bolden oversighted content
+						if( $this->isDeleted( $row, Image::DELETED_RESTRICTED ) )
+							$del = "<strong>$del</strong>";
+					}
+				$rd = "<tt>(<small>$del</small>)</tt>";
+				}
+				$wgOut->addHTML( "<li>$checkBox $rd $pageLink . . $userLink $data $comment</li>\n" );
 			}
 			$files->free();
 			$wgOut->addHTML( "</ul>" );
@@ -830,6 +947,143 @@
 		return true;
 	}
 
+	/**
+	 * Fetch revision text link if it's available to all users
+	 * @return string
+	 */
+	function getPageLink( $row, $titleObj, $ts, $target ) {
+		global $wgLang;
+		
+		if ( !$this->userCan($row, Revision::DELETED_TEXT) ) {
+			return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
+		} else {
+			$link = $this->sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), "target=$target&timestamp=$ts" );
+			if ( $this->isDeleted($row, Revision::DELETED_TEXT) )
+				$link = '<span class="history-deleted">' . $link . '</span>';
+			return $link;
+		}
+	}
+	
+	/**
+	 * Fetch image view link if it's available to all users
+	 * @return string
+	 */
+	function getFileLink( $row, $titleObj, $ts, $target, $key ) {
+		global $wgLang;
+
+		if ( !$this->userCan($row, Image::DELETED_FILE) ) {
+			return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
+		} else {
+			$link = $this->sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), "target=$target&file=$key" );
+			if ( $this->isDeleted($row, Image::DELETED_FILE) )
+				$link = '<span class="history-deleted">' . $link . '</span>';
+			return $link;
+		}
+	}
+
+	/**
+	 * Fetch revision's user id if it's available to this user
+	 * @return string
+	 */
+	function getUser( $row ) {	
+		if ( !$this->userCan($row, Revision::DELETED_USER) ) {
+			return '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+		} else {
+			$link = $this->sk->userLink( $row->ar_user, $row->ar_user_text ) . $this->sk->userToolLinks( $row->ar_user, $row->ar_user_text );
+			if ( $this->isDeleted($row, Revision::DELETED_USER) )
+				$link = '<span class="history-deleted">' . $link . '</span>';
+			return $link;
+		}
+	}
+
+	/**
+	 * Fetch file's user id if it's available to this user
+	 * @return string
+	 */
+	function getFileUser( $row ) {	
+		if ( !$this->userCan($row, Image::DELETED_USER) ) {
+			return '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+		} else {
+			$link = $this->sk->userLink( $row->fa_user, $row->fa_user_text ) . $this->sk->userToolLinks( $row->fa_user, $row->fa_user_text );
+			if ( $this->isDeleted($row, Image::DELETED_USER) )
+				$link = '<span class="history-deleted">' . $link . '</span>';
+			return $link;
+		}
+	}
+
+	/**
+	 * Fetch revision comment if it's available to this user
+	 * @return string
+	 */
+	function getComment( $row ) {
+		if ( !$this->userCan($row, Revision::DELETED_COMMENT) ) {
+			return '<span class="history-deleted"><span class="comment">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>';
+		} else {
+			$link = $this->sk->commentBlock( $row->ar_comment );
+			if ( $this->isDeleted($row, Revision::DELETED_COMMENT) )
+				$link = '<span class="history-deleted">' . $link . '</span>';
+			return $link;
+		}
+	}
+
+	/**
+	 * Fetch file upload comment if it's available to this user
+	 * @return string
+	 */
+	function getFileComment( $row ) {
+		if ( !$this->userCan($row, Image::DELETED_COMMENT) ) {
+			return '<span class="history-deleted"><span class="comment">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>';
+		} else {
+			$link = $this->sk->commentBlock( $row->fa_description );
+			if ( $this->isDeleted($row, Image::DELETED_COMMENT) )
+				$link = '<span class="history-deleted">' . $link . '</span>';
+			return $link;
+		}
+	}	
+	
+	/**
+	 * int $field one of DELETED_* bitfield constants
+	 * for file or revision rows
+	 * @return bool
+	 */
+	function isDeleted( $row, $field ) {
+		if ( isset($row->ar_deleted) )
+		// page revisions
+			return ($row->ar_deleted & $field) == $field;
+		else if ( isset($row->fa_deleted) )
+		// files
+			return ($row->fa_deleted & $field) == $field;
+		return false;
+	}
+		
+	/**
+	 * Determine if the current user is allowed to view a particular
+	 * field of this revision, if it's marked as deleted.
+	 * @param int $field					
+	 * @return bool
+	 */
+	function userCan( $row, $field ) {
+		global $wgUser;
+		
+		if( isset($row->ar_deleted) && ($row->ar_deleted & $field) == $field ) {
+		// page revisions
+			$permission = ( $row->ar_deleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
+				? 'hiderevision'
+				: 'deleterevision';
+			wfDebug( "Checking for $permission due to $field match on $row->ar_deleted\n" );
+			return $wgUser->isAllowed( $permission );
+		} else if( isset($row->fa_deleted) && ($row->fa_deleted & $field) == $field ) {
+		// files
+			$permission = ( $row->fa_deleted & Image::DELETED_RESTRICTED ) == Image::DELETED_RESTRICTED
+				? 'hiderevision'
+				: 'deleterevision';
+			wfDebug( "Checking for $permission due to $field match on $row->fa_deleted\n" );
+			return $wgUser->isAllowed( $permission );
+		} else {
+			return true;
+		}
+	}
+
 	function undelete() {
 		global $wgOut, $wgUser;
 		if( !is_null( $this->mTargetObj ) ) {
@@ -838,16 +1092,22 @@
 			$ok = $archive->undelete(
 				$this->mTargetTimestamp,
 				$this->mComment,
-				$this->mFileVersions );
-			
+				$this->mFileVersions,
+				$this->mUnsuppress );
 			if( $ok ) {
 				$skin = $wgUser->getSkin();
 				$link = $skin->makeKnownLinkObj( $this->mTargetObj );
 				$wgOut->addHtml( wfMsgWikiHtml( 'undeletedpage', $link ) );
 				return true;
 			}
+			// Give user some idea of what is going on ...
+			// This can happen if the top revision would end up being deleted
+			$wgOut->addHtml( '<p>' . wfMsgHtml( "cannotundelete" ) . '</p>' );
+			$wgOut->addHtml( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' );
+			$wgOut->returnToMain( false, $this->mTargetObj );
+			return false;
 		}
-		$wgOut->showFatalError( wfMsg( "cannotundelete" ) );
+		$wgOut->showFatalError( wfMsgHtml( "cannotundelete" ) );
 		return false;
 	}
 }
Index: trunk/phase3/includes/DifferenceEngine.php
===================================================================
--- trunk/phase3/includes/DifferenceEngine.php	(revision 20445)
+++ trunk/phase3/includes/DifferenceEngine.php	(revision 20446)
@@ -174,14 +174,49 @@
 			$newminor = wfElement( 'span', array( 'class' => 'minor' ),
 			wfMsg( 'minoreditletter') ) . ' ';
 		}
+		
+		$rdel = ''; $ldel = '';
+		if( $wgUser->isAllowed( 'deleterevision' ) ) {
+			$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+			if( !$this->mOldRev->userCan( Revision::DELETED_RESTRICTED ) ) {
+			// If revision was hidden from sysops
+				$ldel = wfMsgHtml('rev-delundel');	
+			} else {
+				$ldel = $sk->makeKnownLinkObj( $revdel,
+					wfMsgHtml('rev-delundel'),
+					'target=' . urlencode( $this->mOldRev->mTitle->getPrefixedDbkey() ) .
+					'&oldid=' . urlencode( $this->mOldRev->getId() ) );
+				// Bolden oversighted content
+				if( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+					$ldel = "<strong>$ldel</strong>";
+			}
+			$ldel = "&nbsp;&nbsp;&nbsp;<tt>(<small>$ldel</small>)</tt> ";
+			// We don't currently handle well changing the top revision's settings
+			if( $this->mNewRev->isCurrent() ) {
+			// If revision was hidden from sysops
+				$rdel = wfMsgHtml('rev-delundel');	
+			} else if( !$this->mNewRev->userCan( Revision::DELETED_RESTRICTED ) ) {
+			// If revision was hidden from sysops
+				$rdel = wfMsgHtml('rev-delundel');	
+			} else {
+				$rdel = $sk->makeKnownLinkObj( $revdel,
+					wfMsgHtml('rev-delundel'),
+					'target=' . urlencode( $this->mNewRev->mTitle->getPrefixedDbkey() ) .
+					'&oldid=' . urlencode( $this->mNewRev->getId() ) );
+				// Bolden oversighted content
+				if( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+					$rdel = "<strong>$rdel</strong>";
+			}
+			$rdel = "&nbsp;&nbsp;&nbsp;<tt>(<small>$rdel</small>)</tt> ";
+		}
 
 		$oldHeader = "<strong>{$this->mOldtitle}</strong><br />" .
-			$sk->revUserTools( $this->mOldRev ) . "<br />" .
-			$oldminor . $sk->revComment( $this->mOldRev, !$diffOnly ) . "<br />" .
+			$sk->revUserTools( $this->mOldRev, true ) . "<br />" .
+			$oldminor . $sk->revComment( $this->mOldRev, !$diffOnly, true ) . "$ldel<br />" .
 			$prevlink;
 		$newHeader = "<strong>{$this->mNewtitle}</strong><br />" .
-			$sk->revUserTools( $this->mNewRev ) . " $rollback<br />" .
-			$newminor . $sk->revComment( $this->mNewRev, !$diffOnly ) . "<br />" .
+			$sk->revUserTools( $this->mNewRev, true ) . " $rollback<br />" .
+			$newminor . $sk->revComment( $this->mNewRev, !$diffOnly, true ) . "$rdel<br />" .
 			$nextlink . $patrol;
 
 		$this->showDiff( $oldHeader, $newHeader );
@@ -202,8 +237,10 @@
 
 		$wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
 		#add deleted rev tag if needed
-		if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
+		if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
 		  	$wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
+		} else if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+		  	$wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
 		}
 
 		if( !$this->mNewRev->isCurrent() ) {
@@ -332,20 +369,25 @@
 			}
 		}
 
-		#loadtext is permission safe, this just clears out the diff
+		// Loadtext is permission safe, this just clears out the diff
 		if ( !$this->loadText() ) {
 			wfProfileOut( $fname );
 			return false;
 		} else if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
-		  return '';
+		  	return '';
 		} else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
-		  return '';
+		  	return '';
 		}
 
 		$difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
 		
 		// Save to cache for 7 days
-		if ( $key !== false && $difftext !== false ) {
+		// Only do this for public revs, otherwise an admin can view the diff and a non-admin can nab it!
+		if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
+			wfIncrStats( 'diff_uncacheable' );
+		} else if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+			wfIncrStats( 'diff_uncacheable' );
+		} else if ( $key !== false && $difftext !== false ) {
 			wfIncrStats( 'diff_cache_miss' );
 			$wgMemc->set( $key, $difftext, 7*86400 );
 		} else {
@@ -479,13 +521,7 @@
 	 */
 	function addHeader( $diff, $otitle, $ntitle, $multi = '' ) {
 		global $wgOut;
-	
-		if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
-		   $otitle = '<span class="history-deleted">'.$otitle.'</span>';
-		}
-		if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
-		   $ntitle = '<span class="history-deleted">'.$ntitle.'</span>';
-		}
+
 		$header = "
 			<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>
 			<tr>
@@ -558,6 +594,11 @@
 			$this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>"
 				. " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
 		}
+		if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
+		  	$this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
+		} else if ( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+		  	$this->mNewtitle = '<span class="history-deleted">'.$this->mNewtitle.'</span>';
+		}
 
 		// Load the old revision object
 		$this->mOldRev = false;
@@ -585,11 +626,20 @@
 			$t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
 			$oldLink = $this->mOldPage->escapeLocalUrl( 'oldid=' . $this->mOldid );
 			$oldEdit = $this->mOldPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mOldid );
-			$this->mOldtitle = "<a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) )
-				. "</a> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
-			//now that we considered old rev, we can make undo link (bug 8133, multi-edit undo)
+			$this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t ) );
+			
+			$this->mOldtitle = "<a href='$oldLink'>{$this->mOldPagetitle}</a>"
+				. "(<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
+			// Now that we considered old rev, we can make undo link (bug 8133, multi-edit undo)
 			$newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undoafter=' . $this->mOldid . '&undoto=' . $this->mNewid);
-			$this->mNewtitle .= " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
+			if ( $this->mNewRev->userCan(Revision::DELETED_TEXT) )
+				$this->mNewtitle .= " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
+			
+			if ( !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
+		  		$this->mOldtitle = "<span class='history-deleted'>{$this->mOldPagetitle}</span>";
+			} else if ( $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
+		  		$this->mOldtitle = '<span class="history-deleted">'.$this->mOldtitle.'</span>';
+			}
 		}
 
 		return true;
@@ -610,7 +660,6 @@
 			return false;
 		}
 		if ( $this->mOldRev ) {
-			// FIXME: permission tests
 			$this->mOldtext = $this->mOldRev->revText();
 			if ( $this->mOldtext === false ) {
 				return false;
Index: trunk/phase3/includes/SpecialLog.php
===================================================================
--- trunk/phase3/includes/SpecialLog.php	(revision 20445)
+++ trunk/phase3/includes/SpecialLog.php	(revision 20446)
@@ -72,16 +72,70 @@
 
 		list( $this->limit, $this->offset ) = $request->getLimitOffset();
 	}
+	
+	function newFromTitle( $title, $logid=0 ) {
+		$fname = 'LogReader::newFromTitle';
 
+		$matchId = intval( $logid );
+		$dbr = wfGetDB( DB_SLAVE );
+		$res = $dbr->select( 'logging', array('*'),
+		array('log_id' => $matchId, 'log_namespace' => $title->getNamespace(), 'log_title' => $title->getDBkey() ), 
+		$fname );
+
+		if ( $res ) {
+		   $ret = $dbr->fetchObject( $res );
+		   if ( $ret ) {
+		   	  return $ret;
+			}
+		} 
+		return null;
+	}
+	
+	function newFromId( $logid ) {
+		$fname = 'LogReader::newFromId';
+
+		$matchId = intval( $logid );
+		$dbr = wfGetDB( DB_SLAVE );
+		$res = $dbr->select( 'logging', array('*'), array('log_id' => $matchId ), $fname );
+
+		if ( $res ) {
+		   $ret = $dbr->fetchObject( $res );
+		   if ( $ret ) {
+		   	  return $ret;
+			}
+		} 
+		return null;
+	}
+
 	/**
 	 * Set the log reader to return only entries of the given type.
+	 * Type restrictions enforced here
 	 * @param string $type A log type ('upload', 'delete', etc)
 	 * @private
 	 */
 	function limitType( $type ) {
+		global $wgLogRestrictions, $wgUser;
+		
+		// Restriction system
+		if ( isset($wgLogRestrictions) ) {
+			foreach ( $wgLogRestrictions as $logtype => $right ) {
+				if ( !$wgUser->isAllowed( $right ) ) {
+					$safetype = $this->db->strencode( $logtype );
+					$this->whereClauses[] = "log_type <> '$safetype'";
+				}
+			}
+		}
+		
 		if( empty( $type ) ) {
 			return false;
 		}
+		// Can user see this log?
+		if ( isset($wgLogRestrictions[$type]) ) {
+			if ( !$wgUser->isAllowed( $wgLogRestrictions[$type] ) ) {
+			return false;
+			}
+		}
+		
 		$this->type = $type;
 		$safetype = $this->db->strencode( $type );
 		$this->whereClauses[] = "log_type='$safetype'";
@@ -117,7 +171,7 @@
 	 * @private
 	 */
 	function limitTitle( $page , $pattern ) {
-		$title = Title::newFromText( $page );
+		$title = Title::newFromURL( $page, false );
 		if( empty( $page ) || is_null( $title )  ) {
 			return false;
 		}
@@ -157,6 +211,7 @@
 		$logging = $this->db->tableName( "logging" );
 		$sql = "SELECT /*! STRAIGHT_JOIN */ log_type, log_action, log_timestamp,
 			log_user, user_name,
+			log_id, log_deleted,
 			log_namespace, log_title, page_id,
 			log_comment, log_params FROM $logging ";
 		if( !empty( $this->joinClauses ) ) {
@@ -217,6 +272,11 @@
  * @addtogroup SpecialPage
  */
 class LogViewer {
+	const DELETED_ACTION = 1;
+	const DELETED_COMMENT = 2;
+	const DELETED_USER = 4;
+    const DELETED_RESTRICTED = 8;
+    
 	/**
 	 * @var LogReader $reader
 	 */
@@ -230,7 +290,21 @@
 		global $wgUser;
 		$this->skin = $wgUser->getSkin();
 		$this->reader =& $reader;
+		$this->preCacheMessages();
 	}
+	
+	/**
+	 * As we use the same small set of messages in various methods and that
+	 * they are called often, we call them once and save them in $this->message
+	 */
+	function preCacheMessages() {
+		// Precache various messages
+		if( !isset( $this->message ) ) {
+			foreach( explode(' ', 'viewpagelogs revhistory imghistory rev-delundel' ) as $msg ) {
+				$this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
+			}
+		}
+	}
 
 	/**
 	 * Take over the whole output page in $wgOut with the log display.
@@ -248,8 +322,125 @@
 			$this->showError( $wgOut );
 		}
 	}
+	
+	/**
+	 * Determine if the current user is allowed to view a particular
+	 * field of this event, if it's marked as deleted.
+	 * @param int $field
+	 * @return bool
+	 */
+	function userCan( $event, $field ) {
+		if( ( $event->log_deleted & $field ) == $field ) {
+			global $wgUser;
+			$permission = ( $event->log_deleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
+				? 'hiderevision'
+				: 'deleterevision';
+			wfDebug( "Checking for $permission due to $field match on $event->log_deleted\n" );
+			return $wgUser->isAllowed( $permission );
+		} else {
+			return true;
+		}
+	}
+	
+	/**
+	 * int $field one of DELETED_* bitfield constants
+	 * @return bool
+	 */
+	function isDeleted( $event, $field ) {
+		return ($event->log_deleted & $field) == $field;
+	}
+	
+		/**
+	 * Fetch event's user id if it's available to all users
+	 * @return int
+	 */
+	function getUser( $event ) {
+		if( $this->isDeleted( $event, Revision::DELETED_USER ) ) {
+			return 0;
+		} else {
+			return $event->log_user;
+		}
+	}
 
 	/**
+	 * Fetch event's user id without regard for the current user's permissions
+	 * @return string
+	 */
+	function getRawUser( $event ) {
+		return $event->log_user;
+	}
+
+	/**
+	 * Fetch event's username if it's available to all users
+	 * @return string
+	 */
+	function getUserText( $event ) {
+		if( $this->isDeleted( $event, Revision::DELETED_USER ) ) {
+			return "";
+		} else {
+		  	if ( isset($event->user_name) ) {
+		  	   return $event->user_name;
+		  	} else {
+			  return User::whoIs( $event->log_user );
+			}
+		}
+	}
+
+	/**
+	 * Fetch event's username without regard for view restrictions
+	 * @return string
+	 */
+	function getRawUserText( $event ) {
+		if ( isset($event->user_name) ) {
+			return $event->user_name;
+		} else {
+			return User::whoIs( $event->log_user );
+		}
+	}
+	
+	/**
+	 * Fetch event comment if it's available to all users
+	 * @return string
+	 */
+	function getComment( $event ) {
+		if( $this->isDeleted( $event, Revision::DELETED_COMMENT ) ) {
+			return "";
+		} else {
+			return $event->log_comment;
+		}
+	}
+
+	/**
+	 * Fetch event comment without regard for the current user's permissions
+	 * @return string
+	 */
+	function getRawComment( $event ) {
+		return $event->log_comment;
+	}
+	
+	/**
+	 * Returns the title of the page associated with this entry.
+	 * @return Title
+	 */
+	function getTitle( $event ) {
+		return Title::makeTitle( $event->log_namespace, $event->log_title );
+	}
+
+	/**
+	 * Return the log action if it's available to all users
+	 * default is deleted if not specified for security
+	 * @return Title
+	 */
+	function logActionText( $log_type, $log_action, $title, $skin, $paramArray, $log_deleted = LogViewer::DELETED_ACTION ) {
+		if( $log_deleted & LogViewer::DELETED_ACTION ) {
+			return '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+		} else {
+		  	$action = LogPage::actionText( $log_type, $log_action, $title, $this->skin, $paramArray, true, true );
+		  	return $action;
+		}
+	}
+
+	/**
 	 * Load the data from the linked LogReader
 	 * Preload the link cache
 	 * Initialise numResults
@@ -319,7 +510,8 @@
 	 * @private
 	 */
 	function logLine( $s ) {
-		global $wgLang, $wgUser;;
+		global $wgLang, $wgUser;
+		
 		$skin = $wgUser->getSkin();
 		$title = Title::makeTitle( $s->log_namespace, $s->log_title );
 		$time = $wgLang->timeanddate( wfTimestamp(TS_MW, $s->log_timestamp), true );
@@ -332,12 +524,64 @@
 		} else {
 			$linkCache->addBadLinkObj( $title );
 		}
+		// User links
+		$userLink = $this->skin->logUserTools( $s, true );
+		// Comment
+		$comment = $this->skin->logComment( $s, true );
 
-		$userLink = $this->skin->userLink( $s->log_user, $s->user_name ) . $this->skin->userToolLinksRedContribs( $s->log_user, $s->user_name );
-		$comment = $this->skin->commentBlock( $s->log_comment );
 		$paramArray = LogPage::extractParams( $s->log_params );
-		$revert = '';
-		// show revertmove link
+		$revert = ''; $del = '';
+		
+		// Some user can hide log items and have review links
+		if( $wgUser->isAllowed( 'deleterevision' ) ) {
+			$del = $this->showhideLinks( $s, $title );
+		}
+		
+		// Show restore/unprotect/unblock
+		$revert = $this->showReviewLinks( $s, $title, $paramArray );
+		
+		// Event description
+		$action = $this->logActionText( $s->log_type, $s->log_action, $title, $this->skin, $paramArray, $s->log_deleted );
+		
+		$out = "<li><tt>$del</tt> $time $userLink $action $comment <small>$revert</small></li>\n";
+		return $out;
+	}
+
+	/**
+	 * @param $s, row object
+	 * @param $s, title object
+	 * @private
+	 */
+	function showhideLinks( $s, $title ) {
+		$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+		if( !LogViewer::userCan( $s, Revision::DELETED_RESTRICTED ) ) {
+		// If event was hidden from sysops
+			$del = $this->message['rev-delundel'];
+		} else if( $s->log_type == 'oversight' ) {
+		// No one should be hiding from the oversight log
+			$del = $this->message['rev-delundel'];
+		} else {
+			$del = $this->skin->makeKnownLinkObj( $revdel,
+			$this->message['rev-delundel'],
+			'target=' . urlencode( $title->getPrefixedDbkey() ) . '&logid=' . $s->log_id );
+			// Bolden oversighted content
+			if( LogViewer::isDeleted( $s, Revision::DELETED_RESTRICTED ) )
+			$del = "<strong>$del</strong>";
+		}
+		return "(<small>$del</small>)";
+	}
+
+	/**
+	 * @param $s, row object
+	 * @param $title, title object
+	 * @param $s, param array
+	 * @private
+	 */
+	function showReviewLinks( $s, $title, $paramArray ) {
+		global $wgUser;
+		
+		$reviewlink='';
+		// Show revertmove link
 		if ( $s->log_type == 'move' && isset( $paramArray[0] ) ) {
 			$destTitle = Title::newFromText( $paramArray[0] );
 			if ( $destTitle ) {
@@ -349,26 +593,47 @@
 					'&wpMovetalk=0' ) . ')';
 			}
 		// show undelete link
-		} elseif ( $s->log_action == 'delete' && $wgUser->isAllowed( 'delete' ) ) {
-			$revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete' ),
+		} else if ( $s->log_action == 'delete' && $wgUser->isAllowed( 'delete' ) ) {
+			$reviewlink = $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete' ),
 				wfMsg( 'undeletebtn' ) ,
-				'target='. urlencode( $title->getPrefixedDBkey() ) ) . ')';
-		
+				'target='. urlencode( $title->getPrefixedDBkey() ) );
 		// show unblock link
 		} elseif ( $s->log_action == 'block' && $wgUser->isAllowed( 'block' ) ) {
-			$revert = '(' .  $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Ipblocklist' ),
+			$reviewlink = $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Ipblocklist' ),
 				wfMsg( 'unblocklink' ),
-				'action=unblock&ip=' . urlencode( $s->log_title ) ) . ')';
+				'action=unblock&ip=' . urlencode( $s->log_title ) );
 		// show change protection link
 		} elseif ( $s->log_action == 'protect' && $wgUser->isAllowed( 'protect' ) ) {
-			$revert = '(' .  $skin->makeKnownLink( $title->getPrefixedDBkey() ,
+			$reviewlink = $this->skin->makeKnownLink( $title->getPrefixedDBkey() ,
 				wfMsg( 'protect_change' ),
-				'action=unprotect' ) . ')';
+				'action=unprotect' );
 		}
-
-		$action = LogPage::actionText( $s->log_type, $s->log_action, $title, $this->skin, $paramArray, true, true );
-		$out = "<li>$time $userLink $action $comment $revert</li>\n";
-		return $out;
+		// If an edit was hidden from a page give a review link to the history
+		if ( $wgUser->isAllowed( 'deleterevision' ) && isset($paramArray[2]) ) {
+			$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+			if ( $s->log_action == 'revision' ) {
+				$reviewlink = $this->skin->makeKnownLinkObj( $title, $this->message['revhistory'],
+				wfArrayToCGI( array('action' => 'history' ) ) ) . ':';
+			} else if ( $s->log_action == 'file' ) {
+			// Currently, only deleted file versions can be hidden
+				$undelete = SpecialPage::getTitleFor( 'Undelete' );
+				$reviewlink = $this->skin->makeKnownLinkObj( $undelete, $this->message['imghistory'],
+				wfArrayToCGI( array('target' => $title->getPrefixedText() ) ) ) . ':';
+			} else if ( $s->log_action == 'event' && isset($paramArray[0]) ) {
+			// If this event was to a log, give a review link to logs for that page only
+				$reviewlink = $this->skin->makeKnownLinkObj( $title, $this->message['viewpagelogs'],
+				wfArrayToCGI( array('page' => $paramArray[0] ) ) ) . ':';
+			}
+			// Link to each hidden object ID
+			$IdType = $paramArray[1].'id';
+			$Ids = explode( ',', $paramArray[2] );
+			foreach ( $Ids as $id ) {
+				$reviewlink .= ' '.$this->skin->makeKnownLinkObj( $revdel, "#$id",
+				wfArrayToCGI( array('target' => $paramArray[0], $IdType => $id ) ) );
+			}
+		}
+		$reviewlink = ( $reviewlink=='' ) ? "" : "&nbsp;&nbsp;&nbsp;($reviewlink) ";
+		return $reviewlink;
 	}
 
 	/**
@@ -409,6 +674,8 @@
 	 * @private
 	 */
 	function getTypeMenu() {
+		global $wgLogRestrictions, $wgUser;
+	
 		$out = "<select name='type'>\n";
 
 		$validTypes = LogPage::validTypes();
@@ -426,7 +693,14 @@
 		// Third pass generates sorted XHTML content
 		foreach( $m as $text => $type ) {
 			$selected = ($type == $this->reader->queryType());
+			// Restricted types
+			if ( isset($wgLogRestrictions[$type]) ) {
+				if ( $wgUser->isAllowed( $wgLogRestrictions[$type] ) ) {
+				$out .= Xml::option( $text, $type, $selected ) . "\n";
+				}
+			} else {
 			$out .= Xml::option( $text, $type, $selected ) . "\n";
+			}
 		}
 
 		$out .= '</select>';
@@ -484,5 +758,12 @@
 	}
 }
 
+/**
+ * Aliases for backwards compatibility with 1.6
+ */
+define( 'MW_LOG_DELETED_ACTION', LogViewer::DELETED_ACTION );
+define( 'MW_LOG_DELETED_USER', LogViewer::DELETED_USER );
+define( 'MW_LOG_DELETED_COMMENT', LogViewer::DELETED_COMMENT );
+define( 'MW_LOG_DELETED_RESTRICTED', LogViewer::DELETED_RESTRICTED );
 
 ?>
Index: trunk/phase3/includes/DefaultSettings.php
===================================================================
--- trunk/phase3/includes/DefaultSettings.php	(revision 20445)
+++ trunk/phase3/includes/DefaultSettings.php	(revision 20446)
@@ -169,7 +169,7 @@
  * is writable to the web server but is not exposed to the internet.
  *
  * Set $wgSaveDeletedFiles to true and set up the save path in
- * $wgFileStore['deleted']['directory'].
+ * $wgFileStore['deleted']['directory']
  */
 $wgSaveDeletedFiles = false;
 
@@ -983,6 +983,7 @@
 $wgGroupPermissions['sysop']['block']           = true;
 $wgGroupPermissions['sysop']['createaccount']   = true;
 $wgGroupPermissions['sysop']['delete']          = true;
+$wgGroupPermissions['sysop']['browsearchive']	= true; // can see the deleted page list
 $wgGroupPermissions['sysop']['deletedhistory'] 	= true; // can view deleted history entries, but not see or restore the text
 $wgGroupPermissions['sysop']['editinterface']   = true;
 $wgGroupPermissions['sysop']['import']          = true;
@@ -1005,9 +1006,17 @@
 // Permission to change users' group assignments
 $wgGroupPermissions['bureaucrat']['userrights'] = true;
 
-// Experimental permissions, not ready for production use
+// Experimental permissions to enable revisiondelete:
+
 //$wgGroupPermissions['sysop']['deleterevision'] = true;
+//$wgGroupPermissions['sysop']['hideuser'] = true;
+// To see hidden revs
 //$wgGroupPermissions['bureaucrat']['hiderevision'] = true;
+// For private log access
+//$wgGroupPermissions['bureaucrat']['oversight'] = true;
+// Also, we may want titles to be effectively hidden
+//$wgGroupPermissions['sysop']['browsearchive'] = false;
+//$wgGroupPermissions['bureaucrat']['browsearchive'] = true;
 
 /**
  * The developer group is deprecated, but can be activated if need be
@@ -2023,9 +2032,20 @@
 	'move',
 	'import',
 	'patrol',
+	'oversight',
 );
 
 /**
+ * This restricts log access to those who have a certain right
+ * Users without this will not see it in the option menu and can not view it
+ * Restricted logs are not added to recent changes
+ * Logs should remain non-transcludable
+ */
+$wgLogRestrictions = array(
+'oversight' => 'oversight'
+);
+
+/**
  * Lists the message key string for each log type. The localized messages
  * will be listed in the user interface.
  *
@@ -2041,6 +2061,7 @@
 	'move'    => 'movelogpage',
 	'import'  => 'importlogpage',
 	'patrol'  => 'patrol-log-page',
+	'oversight'  => 'oversightlog',
 );
 
 /**
@@ -2059,6 +2080,7 @@
 	'move'    => 'movelogpagetext',
 	'import'  => 'importlogpagetext',
 	'patrol'  => 'patrol-log-header',
+	'oversight'  => 'overlogpagetext',
 );
 
 /**
@@ -2076,12 +2098,18 @@
 	'delete/delete'     => 'deletedarticle',
 	'delete/restore'    => 'undeletedarticle',
 	'delete/revision'   => 'revdelete-logentry',
+	'delete/event'   	=> 'logdelete-logentry',
 	'upload/upload'     => 'uploadedimage',
 	'upload/revert'     => 'uploadedimage',
 	'move/move'         => '1movedto2',
 	'move/move_redir'   => '1movedto2_redir',
 	'import/upload'     => 'import-logentry-upload',
 	'import/interwiki'  => 'import-logentry-interwiki',
+	'oversight/revision' => 'revdelete-logentry',
+	'oversight/file' => 'revdelete-logentry',
+	'oversight/event'   => 'logdelete-logentry',
+	'oversight/delete'  => 'deletedarticle',
+	'oversight/block'	=> 'blocklogentry',
 );
 
 /**
Index: trunk/phase3/includes/PageHistory.php
===================================================================
--- trunk/phase3/includes/PageHistory.php	(revision 20445)
+++ trunk/phase3/includes/PageHistory.php	(revision 20446)
@@ -38,7 +38,21 @@
 		$this->mTitle =& $article->mTitle;
 		$this->mNotificationTimestamp = NULL;
 		$this->mSkin = $wgUser->getSkin();
+		$this->preCacheMessages();
 	}
+	
+	/**
+	 * As we use the same small set of messages in various methods and that
+	 * they are called often, we call them once and save them in $this->message
+	 */
+	function preCacheMessages() {
+		// Precache various messages
+		if( !isset( $this->message ) ) {
+			foreach( explode(' ', 'cur last rev-delundel' ) as $msg ) {
+				$this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
+			}
+		}
+	}
 
 	/**
 	 * Print the history page for an article.
@@ -187,35 +201,31 @@
 		$arbitrary = $this->diffButtons( $rev, $firstInList, $counter );
 		$link = $this->revLink( $rev );
 		
-		$user = $this->mSkin->userLink( $rev->getUser(), $rev->getUserText() )
-				. $this->mSkin->userToolLinks( $rev->getUser(), $rev->getUserText() );
-		
 		$s .= "($curlink) ($lastlink) $arbitrary";
 		
 		if( $wgUser->isAllowed( 'deleterevision' ) ) {
 			$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
 			if( $firstInList ) {
-				// We don't currently handle well changing the top revision's settings
-				$del = wfMsgHtml( 'rev-delundel' );
+			// We don't currently handle well changing the top revision's settings
+				$del = $this->message['rev-delundel'];
 			} else if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
 			// If revision was hidden from sysops
-				$del = wfMsgHtml( 'rev-delundel' );			
+				$del = $this->message['rev-delundel'];	
 			} else {
 				$del = $this->mSkin->makeKnownLinkObj( $revdel,
-					wfMsg( 'rev-delundel' ),
+					$this->message['rev-delundel'],
 					'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) .
 					'&oldid=' . urlencode( $rev->getId() ) );
+				// Bolden oversighted content
+				if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) )
+					$del = "<strong>$del</strong>";
 			}
-			$s .= " (<small>$del</small>) ";
+			$s .= " <tt>(<small>$del</small>)</tt> ";
 		}
 		
 		$s .= " $link";
-		#getUser is safe, but this avoids making the invalid untargeted contribs links
-		if( $row->rev_deleted & Revision::DELETED_USER ) {
-			$user = '<span class="history-deleted">' . wfMsg('rev-deleted-user') . '</span>';
-		}
-		$s .= " <span class='history-user'>$user</span>";
-
+		$s .= $this->mSkin->revUserTools( $rev, true);
+		
 		if( $row->rev_minor_edit ) {
 			$s .= ' ' . wfElement( 'span', array( 'class' => 'minor' ), wfMsg( 'minoreditletter') );
 		}
@@ -241,7 +251,7 @@
 		}
 		#add blurb about text having been deleted
 		if( $row->rev_deleted & Revision::DELETED_TEXT ) {
-			$s .= ' ' . wfMsgHtml( 'deletedrev' );
+			$s .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
 		}
 		if( $wgUser->isAllowed( 'rollback' ) && $latest ) {
 			$s .= ' '.$this->mSkin->generateRollback( $rev );
@@ -269,7 +279,7 @@
 
 	/** @todo document */
 	function curLink( $rev, $latest ) {
-		$cur = wfMsgExt( 'cur', array( 'escape') );
+		$cur = $this->message['cur'];
 		if( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
 			return $cur;
 		} else {
@@ -282,7 +292,7 @@
 
 	/** @todo document */
 	function lastLink( $rev, $next, $counter ) {
-		$last = wfMsgExt( 'last', array( 'escape' ) );
+		$last = $this->message['last'];
 		if ( is_null( $next ) ) {
 			# Probably no next row
 			return $last;
Index: trunk/phase3/includes/LogPage.php
===================================================================
--- trunk/phase3/includes/LogPage.php	(revision 20445)
+++ trunk/phase3/includes/LogPage.php	(revision 20446)
@@ -50,13 +50,15 @@
 	function saveContent() {
 		if( wfReadOnly() ) return false;
 
-		global $wgUser;
+		global $wgUser, $wgLogRestrictions;
 		$fname = 'LogPage::saveContent';
 
 		$dbw = wfGetDB( DB_MASTER );
 		$uid = $wgUser->getID();
 
 		$this->timestamp = $now = wfTimestampNow();
+		
+		$log_id = $dbw->nextSequenceValue( 'log_log_id_seq' );
 		$dbw->insert( 'logging',
 			array(
 				'log_type' => $this->type,
@@ -69,20 +71,15 @@
 				'log_params' => $this->params
 			), $fname
 		);
+		$newId = $dbw->insertId();
 
 		# And update recentchanges
 		if ( $this->updateRecentChanges ) {
-			$titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
-			$rcComment = $this->actionText;
-			if( '' != $this->comment ) {
-				if ($rcComment == '')
-					$rcComment = $this->comment;
-				else
-					$rcComment .= ': ' . $this->comment;
+			# Don't add private logs to RC!!!
+			if ( !isset($wgLogRestrictions[$this->type]) || $wgLogRestrictions[$this->type]=='*' ) {
+				RecentChange::notifyLog( $now, $this->target, $wgUser, $this->actionText, '',
+				$this->type, $this->action, $this->target, $this->comment, $this->params, $newId );
 			}
-
-			RecentChange::notifyLog( $now, $titleObj, $wgUser, $rcComment, '',
-				$this->type, $this->action, $this->target, $this->comment, $this->params );
 		}
 		return true;
 	}
@@ -122,13 +119,13 @@
 	 */
 	function logHeader( $type ) {
 		global $wgLogHeaders;
-		return wfMsg( $wgLogHeaders[$type] );
+		return wfMsgHtml( $wgLogHeaders[$type] );
 	}
 
 	/**
 	 * @static
 	 */
-	function actionText( $type, $action, $title = NULL, $skin = NULL, $params = array(), $filterWikilinks=false, $translate=false ) {
+	function actionText( $type, $action, $title=NULL, $skin=NULL, $params = array(), $filterWikilinks=false, $translate=false, $forRC=false ) {
 		global $wgLang, $wgContLang, $wgLogActions;
 
 		$key = "$type/$action";
@@ -185,12 +182,17 @@
 					}
 				} else {
 					array_unshift( $params, $titleLink );
-					if ( $translate && $key == 'block/block' ) {
+					// Oversighted blocks show as normal
+					if ( $translate && ($key == 'block/block' || $key == 'oversight/block') ) {
 						$params[1] = $wgLang->translateBlockExpiry( $params[1] );
 						$params[2] = isset( $params[2] )
 										? self::formatBlockFlags( $params[2] )
 										: '';
 					}
+					if ( $forRC ) {
+						$params[1] = $wgLang->translateBlockExpiry( $params[1], true );
+						$params[2] = isset( $params[2] ) ? str_replace( ",", ", ", self::formatBlockFlags( $params[2] ) ) : '';
+					}
 					$rv = wfMsgReal( $wgLogActions[$key], $params, true, !$skin );
 				}
 			}
@@ -222,7 +224,7 @@
 		$this->comment = $comment;
 		$this->params = LogPage::makeParamBlob( $params );
 
-		$this->actionText = LogPage::actionText( $this->type, $action, $target, NULL, $params );
+		$this->actionText = LogPage::actionText( $this->type, $action, $target, NULL, $params, false, false, true );
 
 		return $this->saveContent();
 	}

Follow-up revisions

RevisionCommit summaryAuthorDate
r20506* Update German translations and messages.incraymond08:03, 16 March 2007
r20570Reverting SpecialRevisiondelete.php back to r17880, to fix bug 9336 ( "Special:R...nickj07:41, 21 March 2007
Views
Toolbox