Update to r32375 / bug 11874 -- !important may have whitespace between ! and important
[lhc/web/wiklou.git] / includes / Article.php
index 753e7df..26b6494 100644 (file)
@@ -41,7 +41,7 @@ class Article {
         * @param $title Reference to a Title object.
         * @param $oldId Integer revision ID, null to fetch from request, zero for current
         */
-       function __construct( &$title, $oldId = null ) {
+       function __construct( Title $title, $oldId = null ) {
                $this->mTitle =& $title;
                $this->mOldId = $oldId;
                $this->clear();
@@ -138,7 +138,7 @@ class Article {
         * @return Return the text of this revision
        */
        function getContent() {
-               global $wgUser, $wgOut;
+               global $wgUser, $wgOut, $wgMessageCache;
 
                wfProfileIn( __METHOD__ );
 
@@ -147,6 +147,7 @@ class Article {
                        $wgOut->setRobotpolicy( 'noindex,nofollow' );
 
                        if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+                               $wgMessageCache->loadAllMessages();
                                $ret = wfMsgWeirdKey ( $this->mTitle->getText() ) ;
                        } else {
                                $ret = wfMsg( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon' );
@@ -377,8 +378,7 @@ class Article {
 
                // FIXME: Horrible, horrible! This content-loading interface just plain sucks.
                // We should instead work with the Revision object when we need it...
-               $this->mContent = $revision->userCan( Revision::DELETED_TEXT ) ? $revision->getRawText() : "";
-               //$this->mContent   = $revision->getText();
+               $this->mContent   = $revision->revText(); // Loads if user is allowed
 
                $this->mUser      = $revision->getUser();
                $this->mUserText  = $revision->getUserText();
@@ -613,6 +613,7 @@ class Article {
                global $wgUser, $wgOut, $wgRequest, $wgContLang;
                global $wgEnableParserCache, $wgStylePath, $wgParser;
                global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies;
+               global $wgDefaultRobotPolicy;
                $sk = $wgUser->getSkin();
 
                wfProfileIn( __METHOD__ );
@@ -647,8 +648,7 @@ class Article {
                        # Honour customised robot policies for this namespace
                        $policy = $wgNamespaceRobotPolicies[$ns];
                } else {
-                       # Default to encourage indexing and following links
-                       $policy = 'index,follow';
+                       $policy = $wgDefaultRobotPolicy;
                }
                $wgOut->setRobotPolicy( $policy );
 
@@ -770,11 +770,11 @@ class Article {
                                        $this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid );
                                        if( $this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
                                                if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) {
-                                                       $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
+                                                       $wgOut->addWikiMsg( 'rev-deleted-text-permission' );
                                                        $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
                                                        return;
                                                } else {
-                                                       $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
+                                                       $wgOut->addWikiMsg( 'rev-deleted-text-view' );
                                                        // and we are allowed to see...
                                                }
                                        }
@@ -852,7 +852,7 @@ class Article {
                # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
                if( $ns == NS_USER_TALK &&
                        User::isIP( $this->mTitle->getText() ) ) {
-                       $wgOut->addWikiText( wfMsg('anontalkpagetext') );
+                       $wgOut->addWikiMsg('anontalkpagetext');
                }
 
                # If we have been passed an &rcid= parameter, we want to give the user a
@@ -904,14 +904,14 @@ class Article {
                                        $o->tb_name,
                                        $rmvtxt);
                }
-               $wgOut->addWikitext(wfMsg('trackbackbox', $tbtext));
+               $wgOut->addWikiMsg( 'trackbackbox', $tbtext );
        }
 
        function deletetrackback() {
                global $wgUser, $wgRequest, $wgOut, $wgTitle;
 
                if (!$wgUser->matchEditToken($wgRequest->getVal('token'))) {
-                       $wgOut->addWikitext(wfMsg('sessionfailure'));
+                       $wgOut->addWikiMsg( 'sessionfailure' );
                        return;
                }
 
@@ -926,7 +926,7 @@ class Article {
                $db = wfGetDB(DB_MASTER);
                $db->delete('trackbacks', array('tb_id' => $wgRequest->getInt('tbid')));
                $wgTitle->invalidateCache();
-               $wgOut->addWikiText(wfMsg('trackbackdeleteok'));
+               $wgOut->addWikiMsg('trackbackdeleteok');
        }
 
        function render() {
@@ -978,6 +978,15 @@ class Article {
                        $update = SquidUpdate::newSimplePurge( $this->mTitle );
                        $update->doUpdate();
                }
+               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+                       global $wgMessageCache;
+                       if ( $this->getID() == 0 ) {
+                               $text = false;
+                       } else {
+                               $text = $this->getContent();
+                       }
+                       $wgMessageCache->replace( $this->mTitle->getDBkey(), $text );
+               }
                $this->view();
        }
 
@@ -1058,7 +1067,6 @@ class Article {
                $result = $dbw->affectedRows() != 0;
 
                if ($result) {
-                       // FIXME: Should the result from updateRedirectOn() be returned instead?
                        $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
                }
 
@@ -1486,6 +1494,7 @@ class Article {
         *
         * @param boolean $noRedir Add redirect=no
         * @param string $sectionAnchor section to redirect to, including "#"
+        * @param string $extraq, extra query params
         */
        function doRedirect( $noRedir = false, $sectionAnchor = '', $extraq = '' ) {
                global $wgOut;
@@ -1509,7 +1518,7 @@ class Article {
                # Check patrol config options
 
                if ( !($wgUseNPPatrol || $wgUseRCPatrol)) {
-                       $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
+                       $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
                        return;         
                }
 
@@ -1518,15 +1527,15 @@ class Article {
                $rc = $rcid ? RecentChange::newFromId($rcid) : null;
                if ( is_null ( $rc ) )
                {
-                       $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
+                       $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
                        return;
                }
 
-               if ( !$wgUseRCPatrol && $rc->mAttribs['rc_type'] != RC_NEW) {
+               if ( !$wgUseRCPatrol && $rc->getAttribute( 'rc_type' ) != RC_NEW) {
                        // Only new pages can be patrolled if the general patrolling is off....???
                        // @fixme -- is this necessary? Shouldn't we only bother controlling the
                        // front end here?
-                       $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
+                       $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
                        return;
                }
                
@@ -1545,7 +1554,7 @@ class Article {
                }
 
                #It would be nice to see where the user had actually come from, but for now just guess
-               $returnto = $rc->mAttribs['rc_type'] == RC_NEW ? 'Newpages' : 'Recentchanges';
+               $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges';
                $return = Title::makeTitle( NS_SPECIAL, $returnto );
 
                # If it's left up to us, check that the user is allowed to patrol this edit
@@ -1560,20 +1569,24 @@ class Article {
                                # The user made this edit, and can't patrol it
                                # Tell them so, and then back off
                                $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) );
-                               $wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrollederror-noautopatrol' ) );
+                               $wgOut->addWikiMsg( 'markedaspatrollederror-noautopatrol' );
                                $wgOut->returnToMain( false, $return );
                                return;
                        }
                }
 
-               # Mark the edit as patrolled
-               RecentChange::markPatrolled( $rcid );
-               PatrolLog::record( $rcid );
-               wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) );
+               # Check that the revision isn't patrolled already
+               # Prevents duplicate log entries
+               if( !$rc->getAttribute( 'rc_patrolled' ) ) {
+                       # Mark the edit as patrolled
+                       RecentChange::markPatrolled( $rcid );
+                       PatrolLog::record( $rcid );
+                       wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) );
+               }
 
                # Inform the user
                $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) );
-               $wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrolledtext' ) );
+               $wgOut->addWikiMsg( 'markedaspatrolledtext' );
                $wgOut->returnToMain( false, $return );
        }
 
@@ -1598,9 +1611,7 @@ class Article {
                        $wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
                        $wgOut->setRobotpolicy( 'noindex,nofollow' );
 
-                       $link = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
-                       $text = wfMsg( 'addedwatchtext', $link );
-                       $wgOut->addWikiText( $text );
+                       $wgOut->addWikiMsg( 'addedwatchtext', $this->mTitle->getPrefixedText() );
                }
 
                $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
@@ -1645,9 +1656,7 @@ class Article {
                        $wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
                        $wgOut->setRobotpolicy( 'noindex,nofollow' );
 
-                       $link = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
-                       $text = wfMsg( 'removedwatchtext', $link );
-                       $wgOut->addWikiText( $text );
+                       $wgOut->addWikiMsg( 'removedwatchtext', $this->mTitle->getPrefixedText() );
                }
 
                $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
@@ -1734,7 +1743,7 @@ class Article {
 
                                $expiry_description = '';
                                if ( $encodedExpiry != 'infinity' ) {
-                                       $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ) ).')';
+                                       $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry, false, false ) ).')';
                                }
 
                                # Prepare a null revision to be added to the history
@@ -1749,7 +1758,8 @@ class Article {
                                foreach( $limit as $action => $restrictions ) {
                                        # Check if the group level required to edit also can protect pages
                                        # Otherwise, people who cannot normally protect can "protect" pages via transclusion
-                                       $cascade = ( $cascade && isset($wgGroupPermissions[$restrictions]['protect']) && $wgGroupPermissions[$restrictions]['protect'] );       
+                                       $cascade = ( $cascade && isset($wgGroupPermissions[$restrictions]['protect']) && 
+                                               $wgGroupPermissions[$restrictions]['protect'] );        
                                }
                                
                                $cascade_description = '';
@@ -1766,7 +1776,6 @@ class Article {
                                if ( $cascade )
                                        $comment .= "$cascade_description";
                                
-                               $rowsAffected = false;
                                # Update restrictions table
                                foreach( $limit as $action => $restrictions ) {
                                        if ($restrictions != '' ) {
@@ -1774,18 +1783,11 @@ class Article {
                                                        array( 'pr_page' => $id, 'pr_type' => $action
                                                                , 'pr_level' => $restrictions, 'pr_cascade' => $cascade ? 1 : 0
                                                                , 'pr_expiry' => $encodedExpiry ), __METHOD__  );
-                                               if($dbw->affectedRows() != 0)
-                                                       $rowsAffected = true;
                                        } else {
                                                $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
                                                        'pr_type' => $action ), __METHOD__ );
-                                               if($dbw->affectedRows() != 0)
-                                                       $rowsAffected = true;
                                        }
                                }
-                               if(!$rowsAffected)
-                                       // No change
-                                       return true;
 
                                # Insert a null revision
                                $nullRevision = Revision::newNullRevision( $dbw, $id, $comment, true );
@@ -1937,6 +1939,8 @@ class Article {
                } elseif ( $reason == 'other' ) {
                        $reason = $this->DeleteReason;
                }
+               # Flag to hide all contents of the archived revisions
+               $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('deleterevision');
 
                # This code desperately needs to be totally rewritten
 
@@ -1969,15 +1973,13 @@ class Article {
                $bigHistory = $this->isBigDeletion();
                if( $bigHistory && !$this->mTitle->userCan( 'bigdelete' ) ) {
                        global $wgLang, $wgDeleteRevisionsLimit;
-                       $wgOut->addWikiText( "<div class='error'>\n" .
-                               wfMsg( 'delete-toobig',
-                                       $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) .
-                               "</div>\n" );
+                       $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n",
+                               array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
                        return;
                }
 
                if( $confirm ) {
-                       $this->doDelete( $reason );
+                       $this->doDelete( $reason, $suppress );
                        if( $wgRequest->getCheck( 'wpWatch' ) ) {
                                $this->doWatch();
                        } elseif( $this->mTitle->userIsWatching() ) {
@@ -1996,10 +1998,8 @@ class Article {
                        $wgOut->addHTML( '<strong>' . wfMsg( 'historywarning' ) . ' ' . $skin->historyLink() . '</strong>' );
                        if( $bigHistory ) {
                                global $wgLang, $wgDeleteRevisionsLimit;
-                               $wgOut->addWikiText( "<div class='error'>\n" .
-                                       wfMsg( 'delete-warning-toobig',
-                                               $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) .
-                                       "</div>\n" );
+                               $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n",
+                                       array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
                        }
                }
                
@@ -2089,13 +2089,21 @@ class Article {
 
                $wgOut->setSubtitle( wfMsg( 'delete-backlink', $wgUser->getSkin()->makeKnownLinkObj( $this->mTitle ) ) );
                $wgOut->setRobotpolicy( 'noindex,nofollow' );
-               $wgOut->addWikiText( wfMsg( 'confirmdeletetext' ) );
+               $wgOut->addWikiMsg( 'confirmdeletetext' );
+
+               if( $wgUser->isAllowed( 'deleterevision' ) ) {
+                       $suppress = "<tr id=\"wpDeleteSuppressRow\" name=\"wpDeleteSuppressRow\"><td></td><td>";
+                       $suppress .= Xml::checkLabel( wfMsg( 'revdelete-suppress' ), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '2' ) );
+                       $suppress .= "</td></tr>";
+               } else {
+                       $suppress = '';
+               }
 
                $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=delete' . $par ), 'id' => 'deleteconfirm' ) ) .
-                       Xml::openElement( 'fieldset' ) .
-                       Xml::element( 'legend', array(), wfMsg( 'delete-legend' ) ) .
+                       Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
+                       Xml::element( 'legend', null, wfMsg( 'delete-legend' ) ) .
                        Xml::openElement( 'table' ) .
-                       "<tr id=\"wpDeleteReasonListRow\" name=\"wpDeleteReasonListRow\">
+                       "<tr id=\"wpDeleteReasonListRow\">
                                <td align='$align'>" .
                                        Xml::label( wfMsg( 'deletecomment' ), 'wpDeleteReasonList' ) .
                                "</td>
@@ -2105,7 +2113,7 @@ class Article {
                                                wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) .
                                "</td>
                        </tr>
-                       <tr id=\"wpDeleteReasonRow\" name=\"wpDeleteReasonRow\">
+                       <tr id=\"wpDeleteReasonRow\">
                                <td align='$align'>" .
                                        Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) .
                                "</td>
@@ -2119,6 +2127,7 @@ class Article {
                                        Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '3' ) ) .
                                "</td>
                        </tr>
+                       $suppress
                        <tr>
                                <td></td>
                                <td>" .
@@ -2130,6 +2139,12 @@ class Article {
                        Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
                        Xml::closeElement( 'form' );
 
+                       if ( $wgUser->isAllowed( 'editinterface' ) ) {
+                               $skin = $wgUser->getSkin();
+                               $link = $skin->makeLink ( 'MediaWiki:Deletereason-dropdown', wfMsgHtml( 'delete-edit-reasonlist' ) );
+                               $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
+                       }
+
                $wgOut->addHTML( $form );
                $this->showLogExtract( $wgOut );
        }
@@ -2139,7 +2154,7 @@ class Article {
         * Show relevant lines from the deletion log
         */
        function showLogExtract( $out ) {
-               $out->addHtml( '<h2>' . htmlspecialchars( LogPage::logName( 'delete' ) ) . '</h2>' );
+               $out->addHtml( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
                $logViewer = new LogViewer(
                        new LogReader(
                                new FauxRequest(
@@ -2152,25 +2167,24 @@ 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 ) ) {
-                               $deleted = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
+                       if ( $this->doDeleteArticle( $reason, $suppress ) ) {
+                               $deleted = $this->mTitle->getPrefixedText();
 
                                $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
                                $wgOut->setRobotpolicy( 'noindex,nofollow' );
 
-                               $loglink = '[[Special:Log/delete|' . wfMsg( 'deletionlog' ) . ']]';
-                               $text = wfMsg( 'deletedtext', $deleted, $loglink );
+                               $loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
 
-                               $wgOut->addWikiText( $text );
+                               $wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink );
                                $wgOut->returnToMain( false );
                                wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason));
                        } else {
-                               $wgOut->showFatalError( wfMsg( 'cannotdelete' ) );
+                               $wgOut->showFatalError( wfMsg( 'cannotdelete' ).'<br/>'.wfMsg('cannotdelete-merge') );
                        }
                }
        }
@@ -2180,7 +2194,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;
 
@@ -2198,6 +2212,18 @@ class Article {
                $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 );
                array_push( $wgDeferredUpdateList, $u );
 
+               // Bitfields to further suppress the content
+               if ( $suppress ) {
+                       $bitfield = 0;
+                       // This should be 15...
+                       $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
@@ -2221,8 +2247,9 @@ 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_len'        => 'rev_len',
                                'ar_page_id'    => 'page_id',
+                               'ar_deleted'    => $bitfield
                        ), array(
                                'page_id' => $id,
                                'page_id = rev_page'
@@ -2232,12 +2259,20 @@ class Article {
                # Delete restrictions for it
                $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
 
+               # Fix category table counts
+               $cats = array();
+               $res = $dbw->select( 'categorylinks', 'cl_to',
+                       array( 'cl_from' => $id ), __METHOD__ );
+               foreach( $res as $row ) {
+                       $cats []= $row->cl_to;
+               }
+               $this->updateCategoryCounts( array(), $cats );
+
                # Now that it's safely backed up, delete it
                $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__);
 
                # If using cascading deletes, we can skip some explicit deletes
                if ( !$dbw->cascadingDeletes() ) {
-
                        $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
 
                        if ($wgUseTrackbacks)
@@ -2263,8 +2298,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
@@ -2352,7 +2388,7 @@ class Article {
                $user = intval( $current->getUser() );
                $user_text = $dbw->addQuotes( $current->getUserText() );
                $s = $dbw->selectRow( 'revision',
-                       array( 'rev_id', 'rev_timestamp' ),
+                       array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
                        array(  'rev_page' => $current->getPage(),
                                "rev_user <> {$user} OR rev_user_text <> {$user_text}"
                        ), __METHOD__,
@@ -2362,6 +2398,9 @@ class Article {
                if( $s === false ) {
                        # No one else ever edited this page
                        return array(array('cantrollback'));
+               } else if( $s->rev_deleted & REVISION::DELETED_TEXT || $s->rev_deleted & REVISION::DELETED_USER ) {
+                       # Only admins can see this text
+                       return array(array('notvisiblerev'));
                }
        
                $set = array();
@@ -2439,6 +2478,19 @@ class Article {
                        $wgOut->rateLimited();
                        return;
                }
+               if( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ){
+                       $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
+                       $errArray = $result[0];
+                       $errMsg = array_shift( $errArray );
+                       $wgOut->addWikiMsgArray( $errMsg, $errArray );
+                       if( isset( $details['current'] ) ){
+                               $current = $details['current'];
+                               if( $current->getComment() != '' ) {
+                                       $wgOut->addWikiMsgArray( 'editcomment', array( $wgUser->getSkin()->formatComment( $current->getComment() ) ), array( 'replaceafter' ) );
+                               }
+                       }
+                       return;
+               }
                # Display permissions errors before read-only message -- there's no
                # point in misleading the user into thinking the inability to rollback
                # is only temporary.
@@ -2646,7 +2698,7 @@ class Article {
                $sk = $wgUser->getSkin();
                $lnk = $current
                        ? wfMsg( 'currentrevisionlink' )
-                       : $lnk = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) );
+                       : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) );
                $curdiff = $current
                        ? wfMsg( 'diff' )
                        : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=cur&oldid='.$oldid );
@@ -2664,8 +2716,29 @@ 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>) ";
+               }
+               # Show user links if allowed to see them. Normally they
+               # are hidden regardless, but since we can already see the text here...
+               $userlinks = $sk->revUserTools( $revision, false );
 
                $m = wfMsg( 'revision-info-current' );
                $infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-'
@@ -2673,7 +2746,8 @@ class Article {
                        : 'revision-info';
                        
                $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsg( $infomsg, $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";
+
+                    "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
                $wgOut->setSubtitle( $r );
        }
 
@@ -2953,6 +3027,15 @@ class Article {
 
        /**#@-*/
 
+       /**
+        * Overriden by ImagePage class, only present here to avoid a fatal error
+        * Called for ?action=revert
+        */
+       public function revert(){
+               global $wgOut;
+               $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
+       }
+
        /**
         * Info about this page
         * Called for ?action=info when $wgAllowPageInfo is on.
@@ -3079,6 +3162,36 @@ class Article {
                return $result;
        }
 
+       /**
+        * Returns a list of hidden categories this page is a member of.
+        * Uses the page_props and categorylinks tables.
+        *
+        * @return array Array of Title objects
+        */
+       function getHiddenCategories() {
+               $result = array();
+               $id = $this->mTitle->getArticleID();
+               if( $id == 0 ) {
+                       return array();
+               }
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
+                       array( 'cl_to' ),
+                       array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat', 
+                               'page_namespace' => NS_CATEGORY, 'page_title=cl_to'),
+                       'Article:getHiddenCategories' );
+               if ( false !== $res ) {
+                       if ( $dbr->numRows( $res ) ) {
+                               while ( $row = $dbr->fetchObject( $res ) ) {
+                                       $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
+                               }
+                       }
+               }
+               $dbr->freeResult( $res );
+               return $result;
+       }
+
        /**
         * Return an auto-generated summary if the text provided is a redirect.
         *
@@ -3235,4 +3348,60 @@ class Article {
                $wgOut->addParserOutput( $parserOutput );
        }
 
+       /**
+        * Update all the appropriate counts in the category table, given that
+        * we've added the categories $added and deleted the categories $deleted.
+        *
+        * @param $added array   The names of categories that were added
+        * @param $deleted array The names of categories that were deleted
+        * @return null
+        */
+       public function updateCategoryCounts( $added, $deleted ) {
+               $ns = $this->mTitle->getNamespace();
+               $dbw = wfGetDB( DB_MASTER );
+
+               # First make sure the rows exist.  If one of the "deleted" ones didn't
+               # exist, we might legitimately not create it, but it's simpler to just
+               # create it and then give it a negative value, since the value is bogus
+               # anyway.
+               #
+               # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
+               $insertCats = array_merge( $added, $deleted );
+               if( !$insertCats ) {
+                       # Okay, nothing to do
+                       return;
+               }
+               $insertRows = array();
+               foreach( $insertCats as $cat ) {
+                       $insertRows[] = array( 'cat_title' => $cat );
+               }
+               $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
+
+               $addFields    = array( 'cat_pages = cat_pages + 1' );
+               $removeFields = array( 'cat_pages = cat_pages - 1' );
+               if( $ns == NS_CATEGORY ) {
+                       $addFields[]    = 'cat_subcats = cat_subcats + 1';
+                       $removeFields[] = 'cat_subcats = cat_subcats - 1';
+               } elseif( $ns == NS_IMAGE ) {
+                       $addFields[]    = 'cat_files = cat_files + 1';
+                       $removeFields[] = 'cat_files = cat_files - 1';
+               }
+
+               if ( $added ) {
+                       $dbw->update(
+                               'category',
+                               $addFields,
+                               array( 'cat_title' => $added ),
+                               __METHOD__
+                       );
+               }
+               if ( $deleted ) {
+                       $dbw->update(
+                               'category',
+                               $removeFields,
+                               array( 'cat_title' => $deleted ),
+                               __METHOD__
+                       );
+               }
+       }
 }