*Merge in phase3_rev_deleted/includes
authorAaron Schulz <aaron@users.mediawiki.org>
Wed, 14 Mar 2007 15:50:06 +0000 (15:50 +0000)
committerAaron Schulz <aaron@users.mediawiki.org>
Wed, 14 Mar 2007 15:50:06 +0000 (15:50 +0000)
15 files changed:
includes/Article.php
includes/ChangesList.php
includes/DefaultSettings.php
includes/DifferenceEngine.php
includes/Export.php
includes/ImagePage.php
includes/Linker.php
includes/LogPage.php
includes/PageHistory.php
includes/RecentChange.php
includes/SpecialBlockip.php
includes/SpecialLog.php
includes/SpecialRecentchanges.php
includes/SpecialRevisiondelete.php
includes/SpecialUndelete.php

index 3c8d10e..e5a04e8 100644 (file)
@@ -1782,6 +1782,8 @@ class Article {
                $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 @@ class Article {
                }
 
                if( $confirm ) {
-                       $this->doDelete( $reason );
+                       $this->doDelete( $reason, $suppress );
                        if( $wgRequest->getCheck( 'wpWatch' ) ) {
                                $this->doWatch();
                        } elseif( $this->mTitle->userIsWatching() ) {
@@ -1959,7 +1961,14 @@ class Article {
                $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 @@ class Article {
                                <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 @@ class Article {
        /**
         * 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 @@ class Article {
         * 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 @@ class Article {
                $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 @@ class Article {
                                '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 @@ class Article {
                # 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 @@ class Article {
                        );
                }
 
-               # Get the edit summary
                $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
                $newComment = wfMsgForContent( 'revertpage', $target->getUserText(), $from );
                $newComment = $wgRequest->getText( 'summary', $newComment );
 
@@ -2405,10 +2432,30 @@ class Article {
                        ? 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>)";
+               }
+
+               $userlinks = $sk->revUserTools( $revision, true );
 
-               $r = "\n\t\t\t\t<div id=\"mw-revision-info\">" . wfMsg( 'revision-info', $td, $userlinks ) . "</div>\n" .
+               $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 43fd543..d909d63 100644 (file)
@@ -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 @@ class RCCacheEntry extends RecentChange
 } ;
 
 /**
+ * @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 @@ class ChangesList {
         * @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 @@ class ChangesList {
                                : $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 @@ class ChangesList {
                }
        }
 
+       /**
+        * 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,10 +168,11 @@ class ChangesList {
                $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
@@ -172,7 +203,12 @@ class ChangesList {
                $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 @@ class ChangesList {
 
        /** 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 @@ class OldChangesList extends ChangesList {
                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 @@ class OldChangesList extends ChangesList {
 
                $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,9 +346,15 @@ class OldChangesList extends ChangesList {
                }
 
                $this->insertUserRelatedLinks($s,$rc);
+               $this->insertAction($s, $rc);
                $this->insertComment($s, $rc);
-
-               $s .=  rtrim(' ' . $this->numberofWatchingusers($rc->numberofWatchingusers));
+               
+               # 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 .= "</li>\n";
 
@@ -313,7 +380,6 @@ class EnhancedChangesList extends ChangesList {
                $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 @@ class EnhancedChangesList extends ChangesList {
                        $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 @@ class EnhancedChangesList extends ChangesList {
                                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 @@ class EnhancedChangesList extends ChangesList {
                $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 @@ class EnhancedChangesList extends ChangesList {
                }
 
                # 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 @@ class EnhancedChangesList extends ChangesList {
         */
        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 @@ class EnhancedChangesList extends ChangesList {
                        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 @@ class EnhancedChangesList extends ChangesList {
                $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 @@ class EnhancedChangesList extends ChangesList {
 
                        $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 @@ class EnhancedChangesList extends ChangesList {
         * @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 @@ class EnhancedChangesList extends ChangesList {
                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 = '';
-
-               # Spacer image
-               $r .= $this->spacerArrow();
+               $r = '<table cellspacing="0" cellpadding="0"><tr><td>';
 
+               # 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 7b8df3f..65a4d02 100644 (file)
@@ -169,7 +169,7 @@ $wgUploadBaseUrl    = "";
  * 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['bot'  ]['nominornewtalk']  = true;
 $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 @@ $wgGroupPermissions['sysop']['ipblock-exempt']   = true;
 // 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,6 +2032,17 @@ $wgLogTypes = array( '',
        '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'
 );
 
 /**
@@ -2041,6 +2061,7 @@ $wgLogNames = array(
        'move'    => 'movelogpage',
        'import'  => 'importlogpage',
        'patrol'  => 'patrol-log-page',
+       'oversight'  => 'oversightlog',
 );
 
 /**
@@ -2059,6 +2080,7 @@ $wgLogHeaders = array(
        'move'    => 'movelogpagetext',
        'import'  => 'importlogpagetext',
        'patrol'  => 'patrol-log-header',
+       'oversight'  => 'overlogpagetext',
 );
 
 /**
@@ -2076,12 +2098,18 @@ $wgLogActions = array(
        '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 884e82a..31fab65 100644 (file)
@@ -174,14 +174,49 @@ CONTROL;
                        $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 @@ CONTROL;
 
                $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 @@ CONTROL;
                        }
                }
 
-               #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 @@ CONTROL;
         */
        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 @@ CONTROL;
                        $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 @@ CONTROL;
                        $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 @@ CONTROL;
                        return false;
                }
                if ( $this->mOldRev ) {
-                       // FIXME: permission tests
                        $this->mOldtext = $this->mOldRev->revText();
                        if ( $this->mOldtext === false ) {
                                return false;
index f15c778..3c24554 100644 (file)
@@ -139,7 +139,10 @@ class WikiExporter {
                $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 3ddf192..fb5bf8e 100644 (file)
@@ -508,7 +508,9 @@ END
                $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 @@ END
                # 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 @@ END
         * 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 @@ END
                                $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 @@ END
                        # 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 @@ END
        /**
         * @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 5662252..5bd8123 100644 (file)
@@ -830,10 +830,13 @@ class Linker {
        /**
         * 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,26 +846,121 @@ class Linker {
                }
                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
@@ -985,20 +1083,64 @@ class Linker {
         *
         * @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() {
index 65dbbe4..a1e4638 100644 (file)
@@ -50,13 +50,15 @@ class LogPage {
        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 @@ class LogPage {
                                '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 @@ class LogPage {
         */
        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 @@ class LogPage {
                                        }
                                } 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 @@ class LogPage {
                $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();
        }
index 94b55ea..1622a6e 100644 (file)
@@ -38,6 +38,20 @@ class PageHistory {
                $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') );
+                       }
+               }
        }
 
        /**
@@ -187,35 +201,31 @@ class PageHistory {
                $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 @@ class PageHistory {
                }
                #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 @@ class PageHistory {
 
        /** @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 @@ class PageHistory {
 
        /** @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 fced434..ceb0717 100644 (file)
  *     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 @@ class RecentChange
 
        # 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 @@ class RecentChange
                        '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 @@ class RecentChange
         * @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 @@ class RecentChange
                                $ip = '';
                        }
                }
+                               
                if ( $bot == 'default' ) {
                        $bot = $user->isAllowed( 'bot' );
                }
@@ -315,9 +325,14 @@ class RecentChange
                        '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 @@ class RecentChange
                                $ip = '';
                        }
                }
-
+               
                $rc = new RecentChange;
                $rc->mAttribs = array(
                        'rc_timestamp'  => $timestamp,
@@ -362,6 +377,11 @@ class RecentChange
                        '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 @@ class RecentChange
                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 @@ class RecentChange
                        '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 @@ class RecentChange
                        '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 @@ class RecentChange
                        '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 25a0727..97ac77f 100644 (file)
@@ -45,7 +45,7 @@ class IPBlockForm {
        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 @@ class IPBlockForm {
                $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 @@ class IPBlockForm {
                        </td>
                        ");
                }
+               
                $wgOut->addHTML("
                </tr>
                <tr id='wpBlockOther'>
@@ -150,31 +153,46 @@ class IPBlockForm {
                <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 @@ class IPBlockForm {
                        $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 @@ class IPBlockForm {
                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 @@ class IPBlockForm {
 
                $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 @@ class IPBlockForm {
                        $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 9194a53..90aa906 100644 (file)
@@ -72,16 +72,70 @@ class LogReader {
 
                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 @@ class LogReader {
         * @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 @@ class LogReader {
                $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 @@ class LogReader {
  * @addtogroup SpecialPage
  */
 class LogViewer {
+       const DELETED_ACTION = 1;
+       const DELETED_COMMENT = 2;
+       const DELETED_USER = 4;
+    const DELETED_RESTRICTED = 8;
+    
        /**
         * @var LogReader $reader
         */
@@ -230,6 +290,20 @@ class LogViewer {
                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') );
+                       }
+               }
        }
 
        /**
@@ -248,6 +322,123 @@ class LogViewer {
                        $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
@@ -319,7 +510,8 @@ class LogViewer {
         * @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 @@ class LogViewer {
                } 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 @@ class LogViewer {
                                        '&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 @@ class LogViewer {
         * @private
         */
        function getTypeMenu() {
+               global $wgLogRestrictions, $wgUser;
+       
                $out = "<select name='type'>\n";
 
                $validTypes = LogPage::validTypes();
@@ -426,7 +693,14 @@ class LogViewer {
                // 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 @@ class LogViewer {
        }
 }
 
+/**
+ * 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 13d5ebf..5d68b92 100644 (file)
@@ -404,7 +404,7 @@ function rcDoOutputFeed( $rows, &$feed ) {
                        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 @@ function rcFormatDiff( $row ) {
        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 fb5e9ec..b3179d2 100644 (file)
@@ -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 @@ class RevisionDeleteForm {
         * @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 @@ class RevisionDeleteForm {
                        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,14 +332,83 @@ class RevisionDeleteForm {
        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> $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>" .
-                       $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ) .
-                       " " .
-                       $this->skin->revUserLink( $rev ) .
-                       " " .
-                       $this->skin->revComment( $rev ) .
-                       "</li>";
+                       "<li>$date" . " " . $this->skin->logUserLink( $event ) . " $action " . $this->skin->logComment( $event ) . "</li>";
        }
        
        /**
@@ -135,16 +417,51 @@ class RevisionDeleteForm {
        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 @@ class RevisionDeleteForm {
                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,41 +503,191 @@ class RevisionDeleter {
        }
        
        /**
+        * @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 );
-                       
-                       // For logging, maintain a count of revisions per page
                        $pageid = $rev->getPage();
-                       if( isset( $pages[$pageid] ) ) {
-                               $pages[$pageid]++;
-                       } else {
-                               $pages[$pageid] = 1;
+                       // For logging, maintain a count of revisions per page
+                       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
@@ -224,20 +700,66 @@ class RevisionDeleter {
                        'RevisionDeleter::updateRevision' );
        }
        
+       /**
+        * 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 @@ class RevisionDeleter {
         * @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 8aaf785..afe49c9 100644 (file)
@@ -99,7 +99,7 @@ class PageArchive {
        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 @@ class PageArchive {
         * @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 @@ class PageArchive {
                                        '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 @@ class PageArchive {
                $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 @@ class PageArchive {
                                '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 @@ class PageArchive {
                                '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 @@ class PageArchive {
         *
         * @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 @@ class PageArchive {
                $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 @@ class PageArchive {
                $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 @@ class PageArchive {
         * @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 @@ class PageArchive {
                }
 
                /**
-                * Restore each revision...
+                * Select each archived revision...
                 */
                $result = $dbw->select( 'archive',
                        /* fields */ array(
@@ -375,6 +394,7 @@ class PageArchive {
                                'ar_minor_edit',
                                'ar_flags',
                                'ar_text_id',
+                               'ar_deleted',
                                'ar_len' ),
                        /* WHERE */ array(
                                'ar_namespace' => $this->title->getNamespace(),
@@ -384,15 +404,30 @@ class PageArchive {
                        /* 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 @@ class PageArchive {
                                '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 @@ class UndeleteForm {
                $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 @@ class UndeleteForm {
        }
 
        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 @@ class UndeleteForm {
                        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 @@ class UndeleteForm {
 
        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 @@ class UndeleteForm {
                        '</form>' );
        }
 
+       // Generic list of deleted pages
        /* private */ function showList( $result ) {
                global $wgLang, $wgContLang, $wgUser, $wgOut;
                
@@ -612,11 +661,22 @@ class UndeleteForm {
                        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 @@ class UndeleteForm {
                                        '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 @@ class UndeleteForm {
        /* 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 @@ class UndeleteForm {
                }
                */
                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 @@ class UndeleteForm {
                                        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 @@ class UndeleteForm {
                        $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 @@ class UndeleteForm {
                        $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 @@ class UndeleteForm {
                        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 @@ class UndeleteForm {
                                        ' (' .
                                        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 @@ class UndeleteForm {
                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 @@ class UndeleteForm {
                        $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;
        }
 }