Cleanup
[lhc/web/wiklou.git] / includes / Article.php
index 7f9e440..68e561a 100644 (file)
@@ -36,24 +36,12 @@ class Article {
        var $mUserText;                 //!<
        /**@}}*/
 
-       /**
-        * Constants used by internal components to get rollback results
-        */
-       const SUCCESS = 0;                      // Operation successful
-       const PERM_DENIED = 1;          // Permission denied
-       const BLOCKED = 2;                      // User has been blocked
-       const READONLY = 3;                     // Wiki is in read-only mode
-       const BAD_TOKEN = 4;            // Invalid token specified
-       const BAD_TITLE = 5;            // $this is not a valid Article
-       const ALREADY_ROLLED = 6;       // Someone else already rolled this back. $from and $summary will be set
-       const ONLY_AUTHOR = 7;          // User is the only author of the page
        /**
         * Constructor and clear the 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();
@@ -135,6 +123,7 @@ class Article {
                $this->mRevIdFetched = 0;
                $this->mRedirectUrl = false;
                $this->mLatest = false;
+               $this->mPreparedEdit = false;
        }
 
        /**
@@ -622,8 +611,9 @@ class Article {
        */
        function view() {
                global $wgUser, $wgOut, $wgRequest, $wgContLang;
-               global $wgEnableParserCache, $wgStylePath, $wgUseRCPatrol, $wgParser;
+               global $wgEnableParserCache, $wgStylePath, $wgParser;
                global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies;
+               global $wgDefaultRobotPolicy;
                $sk = $wgUser->getSkin();
 
                wfProfileIn( __METHOD__ );
@@ -645,6 +635,7 @@ class Article {
                $rcid = $wgRequest->getVal( 'rcid' );
                $rdfrom = $wgRequest->getVal( 'rdfrom' );
                $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
+               $purge = $wgRequest->getVal( 'action' ) == 'purge';
 
                $wgOut->setArticleFlag( true );
 
@@ -657,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 );
 
@@ -668,7 +658,7 @@ class Article {
                if ( !is_null( $diff ) ) {
                        $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
 
-                       $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid );
+                       $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge );
                        // DifferenceEngine directly fetched the revision:
                        $this->mRevIdFetched = $de->mNewid;
                        $de->showDiffPage( $diffOnly );
@@ -740,12 +730,6 @@ class Article {
                        }
                }
 
-               if ( $this->mTitle->isProtected() ) {
-                       $editrestr = $this->mTitle->getRestrictions('edit');
-                       $moverestr = $this->mTitle->getRestrictions('move');
-                       $this->addProtectionNotice( $editrestr, $moverestr );
-               }
-
                $outputDone = false;
                wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$pcache ) );
                if ( $pcache ) {
@@ -786,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...
                                                }
                                        }
@@ -868,12 +852,12 @@ 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
                # chance to mark this new article as patrolled.
-               if ( $wgUseRCPatrol && !is_null( $rcid ) && $rcid != 0 && $wgUser->isAllowed( 'patrol' ) ) {
+               if( !is_null( $rcid ) && $rcid != 0 && $wgUser->isAllowed( 'patrol' ) && $this->mTitle->exists() ) {
                        $wgOut->addHTML(
                                "<div class='patrollink'>" .
                                        wfMsgHtml( 'markaspatrolledlink',
@@ -892,40 +876,6 @@ class Article {
                wfProfileOut( __METHOD__ );
        }
 
-       /*
-       * Output a notice that a page is protected. Only give details for move/edit
-       * restrictions. Cares only about the first permission in the arrays, which is
-       * part of a larger shitty inconsistency about requiring several permissions...
-       * @param Array $editrestr, edit restrictions
-       * @param Array $moverestr, move restrictions
-       */
-       function addProtectionNotice( $editrestr, $moverestr ) {        
-               global $wgOut;
-               
-               $editGroups = $moveGroups = wfMsg('protected-anyone');
-               # Get groups that have each right
-               if( !empty( $editrestr ) ) {
-                       $permission = ($editrestr[0]=='sysop') ? 'protect' : $editrestr[0];
-                       $editGroups = $wgOut->getGroupsWithPermission( $permission );
-                       $editGroups = implode( ', ', $editGroups );
-               }
-               if( !empty( $moverestr ) ) {
-                       $permission = ($moverestr[0]=='sysop') ? 'protect' : $moverestr[0];
-                       $moveGroups = $wgOut->getGroupsWithPermission( $permission );
-                       $moveGroups = implode( ', ', $moveGroups );
-               }
-               # Use general messages if no groups found for a type
-               if( !$editGroups || !$moveGroups ) {
-                       $msg = wfMsg( 'protected-subtitle' );
-               } else if( $editGroups == $moveGroups ) {
-                       $msg = wfMsg( 'protected-subtitle-same', $editGroups, $moveGroups );
-               } else {
-                       $msg = wfMsg( 'protected-subtitle-diff', $editGroups, $moveGroups );
-               }
-               
-               $wgOut->setSubtitle( $wgOut->getSubtitle() . $msg );
-       }
-
        function addTrackbacks() {
                global $wgOut, $wgUser;
 
@@ -954,31 +904,29 @@ 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;
                }
 
-               if ((!$wgUser->isAllowed('delete'))) {
-                       $wgOut->permissionRequired( 'delete' );
-                       return;
-               }
+               $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
 
-               if (wfReadOnly()) {
-                       $wgOut->readOnlyPage();
+               if (count($permission_errors)>0)
+               {
+                       $wgOut->showPermissionsErrorPage( $permission_errors );
                        return;
                }
 
                $db = wfGetDB(DB_MASTER);
                $db->delete('trackbacks', array('tb_id' => $wgRequest->getInt('tbid')));
                $wgTitle->invalidateCache();
-               $wgOut->addWikiText(wfMsg('trackbackdeleteok'));
+               $wgOut->addWikiMsg('trackbackdeleteok');
        }
 
        function render() {
@@ -1000,7 +948,7 @@ class Article {
                        }
                } else {
                        $msg = $wgOut->parse( wfMsg( 'confirm_purge' ) );
-                       $action = $this->mTitle->escapeLocalURL( 'action=purge' );
+                       $action = htmlspecialchars( $_SERVER['REQUEST_URI'] );
                        $button = htmlspecialchars( wfMsg( 'confirm_purge_button' ) );
                        $msg = str_replace( '$1',
                                "<form method=\"post\" action=\"$action\">\n" .
@@ -1030,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();
        }
 
@@ -1221,7 +1178,7 @@ class Article {
 
                        if( $section == 'new' ) {
                                # Inserting a new section
-                               $subject = $summary ? "== {$summary} ==\n\n" : '';
+                               $subject = $summary ? wfMsgForContent('newsectionheaderdefaultlevel',$summary) . "\n\n" : '';
                                $text = strlen( trim( $oldtext ) ) > 0
                                                ? "{$oldtext}\n\n{$subject}{$text}"
                                                : "{$subject}{$text}";
@@ -1240,14 +1197,15 @@ class Article {
        /**
         * @deprecated use Article::doEdit()
         */
-       function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) {
+       function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false, $bot=false ) {
                $flags = EDIT_NEW | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
                        ( $isminor ? EDIT_MINOR : 0 ) |
-                       ( $suppressRC ? EDIT_SUPPRESS_RC : 0 );
+                       ( $suppressRC ? EDIT_SUPPRESS_RC : 0 ) |
+                       ( $bot ? EDIT_FORCE_BOT : 0 );
 
                # If this is a comment, add the summary as headline
                if ( $comment && $summary != "" ) {
-                       $text = "== {$summary} ==\n\n".$text;
+                       $text = wfMsgForContent('newsectionheaderdefaultlevel',$summary) . "\n\n".$text;
                }
 
                $this->doEdit( $text, $summary, $flags );
@@ -1362,7 +1320,7 @@ class Article {
 
                # Silently ignore EDIT_MINOR if not allowed
                $isminor = ( $flags & EDIT_MINOR ) && $wgUser->isAllowed('minoredit');
-               $bot = $wgUser->isAllowed( 'bot' ) || ( $flags & EDIT_FORCE_BOT );
+               $bot = $flags & EDIT_FORCE_BOT;
 
                $oldtext = $this->getContent();
                $oldsize = strlen( $oldtext );
@@ -1371,7 +1329,8 @@ class Article {
                if ($flags & EDIT_AUTOSUMMARY && $summary == '')
                        $summary = $this->getAutosummary( $oldtext, $text, $flags );
 
-               $text = $this->preSaveTransform( $text );
+               $editInfo = $this->prepareTextForEdit( $text );
+               $text = $editInfo->pst;
                $newsize = strlen( $text );
 
                $dbw = wfGetDB( DB_MASTER );
@@ -1387,8 +1346,10 @@ class Article {
 
                        $lastRevision = 0;
                        $revisionId = 0;
+                       
+                       $changed = ( strcmp( $text, $oldtext ) != 0 );
 
-                       if ( 0 != strcmp( $text, $oldtext ) ) {
+                       if ( $changed ) {
                                $this->mGoodAdjustment = (int)$this->isCountable( $text )
                                  - (int)$this->isCountable( $oldtext );
                                $this->mTotalAdjustment = 0;
@@ -1453,9 +1414,8 @@ class Article {
                                # Invalidate cache of this article and all pages using this article
                                # as a template. Partly deferred.
                                Article::onArticleEdit( $this->mTitle );
-
+                               
                                # Update links tables, site stats, etc.
-                               $changed = ( strcmp( $oldtext, $text ) != 0 );
                                $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
                        }
                } else {
@@ -1491,7 +1451,7 @@ class Article {
                                $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot,
                                  '', strlen( $text ), $revisionId );
                                # Mark as patrolled if the user can
-                               if( $GLOBALS['wgUseRCPatrol'] && $wgUser->isAllowed( 'autopatrol' ) ) {
+                               if( ($GLOBALS['wgUseRCPatrol'] || $GLOBALS['wgUseNPPatrol']) && $wgUser->isAllowed( 'autopatrol' ) ) {
                                        RecentChange::markPatrolled( $rcid );
                                        PatrolLog::record( $rcid, true );
                                }
@@ -1513,8 +1473,10 @@ class Article {
                        wfDoUpdates();
                }
 
-               wfRunHooks( 'ArticleSaveComplete', array( &$this, &$wgUser, $text, $summary,
-                       $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+               if ( $good ) {
+                       wfRunHooks( 'ArticleSaveComplete', array( &$this, &$wgUser, $text, $summary,
+                               $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+               }
 
                wfProfileOut( __METHOD__ );
                return $good;
@@ -1547,28 +1509,42 @@ class Article {
        }
 
        /**
-        * Mark this particular edit as patrolled
+        * Mark this particular edit/page as patrolled
         */
        function markpatrolled() {
-               global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUser;
+               global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUseNPPatrol, $wgUser;
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
 
-               # Check RC patrol config. option
-               if( !$wgUseRCPatrol ) {
+               # Check patrol config options
+
+               if ( !($wgUseNPPatrol || $wgUseRCPatrol)) {
                        $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
+                       return;         
+               }
+
+               # If we haven't been given an rc_id value, we can't do anything
+               $rcid = (int) $wgRequest->getVal('rcid');
+               $rc = $rcid ? RecentChange::newFromId($rcid) : null;
+               if ( is_null ( $rc ) )
+               {
+                       $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
                        return;
                }
 
-               # Check permissions
-               if( !$wgUser->isAllowed( 'patrol' ) ) {
-                       $wgOut->permissionRequired( 'patrol' );
+               if ( !$wgUseRCPatrol && $rc->mAttribs['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' );
                        return;
                }
+               
+               # Check permissions
+               $permission_errors = $this->mTitle->getUserPermissionsErrors( 'patrol', $wgUser );
 
-               # If we haven't been given an rc_id value, we can't do anything
-               $rcid = $wgRequest->getVal( 'rcid' );
-               if( !$rcid ) {
-                       $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
+               if (count($permission_errors)>0)
+               {
+                       $wgOut->showPermissionsErrorPage( $permission_errors );
                        return;
                }
 
@@ -1577,7 +1553,10 @@ class Article {
                        return;
                }
 
-               $return = SpecialPage::getTitleFor( 'Recentchanges' );
+               #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';
+               $return = Title::makeTitle( NS_SPECIAL, $returnto );
+
                # If it's left up to us, check that the user is allowed to patrol this edit
                # If the user has the "autopatrol" right, then we'll assume there are no
                # other conditions stopping them doing so
@@ -1590,7 +1569,7 @@ 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;
                        }
@@ -1603,7 +1582,7 @@ class Article {
 
                # Inform the user
                $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) );
-               $wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrolledtext' ) );
+               $wgOut->addWikiMsg( 'markedaspatrolledtext' );
                $wgOut->returnToMain( false, $return );
        }
 
@@ -1628,9 +1607,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() );
@@ -1675,9 +1652,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() );
@@ -1728,7 +1703,7 @@ class Article {
                global $wgUser, $wgRestrictionTypes, $wgContLang;
 
                $id = $this->mTitle->getArticleID();
-               if( !$wgUser->isAllowed( 'protect' ) || wfReadOnly() || $id == 0 ) {
+               if( array() != $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser ) || wfReadOnly() || $id == 0 ) {
                        return false;
                }
 
@@ -1764,7 +1739,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
@@ -1795,10 +1770,7 @@ class Article {
                                        $comment .= "$expiry_description";
                                if ( $cascade )
                                        $comment .= "$cascade_description";
-
-                               $nullRevision = Revision::newNullRevision( $dbw, $id, $comment, true );
-                               $nullRevId = $nullRevision->insertOn( $dbw );
-
+                               
                                # Update restrictions table
                                foreach( $limit as $action => $restrictions ) {
                                        if ($restrictions != '' ) {
@@ -1812,6 +1784,10 @@ class Article {
                                        }
                                }
 
+                               # Insert a null revision
+                               $nullRevision = Revision::newNullRevision( $dbw, $id, $comment, true );
+                               $nullRevId = $nullRevision->insertOn( $dbw );
+
                                # Update page record
                                $dbw->update( 'page',
                                        array( /* SET */
@@ -1826,6 +1802,8 @@ class Article {
 
                                # Update the protection log
                                $log = new LogPage( 'protect' );
+                               
+                               
 
                                if( $protect ) {
                                        $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason . " [$updated]$cascade_description$expiry_description" ) );
@@ -1859,35 +1837,121 @@ class Article {
                }
                return implode( ':', $bits );
        }
+       
+       /**
+        * Auto-generates a deletion reason
+        * @param bool &$hasHistory Whether the page has a history
+        */
+       public function generateReason(&$hasHistory)
+       {
+               global $wgContLang;
+               $dbw = wfGetDB(DB_MASTER);
+               // Get the last revision
+               $rev = Revision::newFromTitle($this->mTitle);
+               if(is_null($rev))
+                       return false;
+               // Get the article's contents
+               $contents = $rev->getText();
+               $blank = false;
+               // If the page is blank, use the text from the previous revision,
+               // which can only be blank if there's a move/import/protect dummy revision involved
+               if($contents == '')
+               {
+                       $prev = $rev->getPrevious();
+                       if($prev)
+                       {
+                               $contents = $prev->getText();
+                               $blank = true;
+                       }
+               }
+
+               // Find out if there was only one contributor
+               // Only scan the last 20 revisions
+               $limit = 20;
+               $res = $dbw->select('revision', 'rev_user_text', array('rev_page' => $this->getID()), __METHOD__,
+                               array('LIMIT' => $limit));
+               if($res === false)
+                       // This page has no revisions, which is very weird
+                       return false;
+               if($res->numRows() > 1)
+                               $hasHistory = true;
+               else
+                               $hasHistory = false;
+               $row = $dbw->fetchObject($res);
+               $onlyAuthor = $row->rev_user_text;
+               // Try to find a second contributor
+               while( $row = $dbw->fetchObject($res) ) {
+                       if($row->rev_user_text != $onlyAuthor) {
+                               $onlyAuthor = false;
+                               break;
+                       }
+               }
+               $dbw->freeResult($res);
+
+               // Generate the summary with a '$1' placeholder
+               if($blank) {
+                       // The current revision is blank and the one before is also
+                       // blank. It's just not our lucky day
+                       $reason = wfMsgForContent('exbeforeblank', '$1');
+               } else {
+                       if($onlyAuthor)
+                               $reason = wfMsgForContent('excontentauthor', '$1', $onlyAuthor);
+                       else
+                               $reason = wfMsgForContent('excontent', '$1');
+               }
+               
+               // Replace newlines with spaces to prevent uglyness
+               $contents = preg_replace("/[\n\r]/", ' ', $contents);
+               // Calculate the maximum amount of chars to get
+               // Max content length = max comment length - length of the comment (excl. $1) - '...'
+               $maxLength = 255 - (strlen($reason) - 2) - 3;
+               $contents = $wgContLang->truncate($contents, $maxLength, '...');
+               // Remove possible unfinished links
+               $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
+               // Now replace the '$1' placeholder
+               $reason = str_replace( '$1', $contents, $reason );
+               return $reason;
+       }
+
 
        /*
         * UI entry point for page deletion
         */
        function delete() {
                global $wgUser, $wgOut, $wgRequest;
+
                $confirm = $wgRequest->wasPosted() &&
-                       $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
-               $reason = $wgRequest->getText( 'wpReason' );
+                               $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
+               
+               $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
+               $this->DeleteReason = $wgRequest->getText( 'wpReason' );
+               
+               $reason = $this->DeleteReasonList;
+               
+               if ( $reason != 'other' && $this->DeleteReason != '') {
+                       // Entry from drop down menu + additional comment
+                       $reason .= ': ' . $this->DeleteReason;
+               } elseif ( $reason == 'other' ) {
+                       $reason = $this->DeleteReason;
+               }
 
                # This code desperately needs to be totally rewritten
 
-               # Check permissions
-               if( $wgUser->isAllowed( 'delete' ) ) {
-                       if( $wgUser->isBlocked( !$confirm ) ) {
-                               $wgOut->blockedPage();
-                               return;
-                       }
-               } else {
-                       $wgOut->permissionRequired( 'delete' );
+               # Read-only check...
+               if ( wfReadOnly() ) {
+                       $wgOut->readOnlyPage();
                        return;
                }
+               
+               # Check permissions
+               $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
 
-               if( wfReadOnly() ) {
-                       $wgOut->readOnlyPage();
+               if (count($permission_errors)>0) {
+                       $wgOut->showPermissionsErrorPage( $permission_errors );
                        return;
                }
 
-               $wgOut->setPagetitle( wfMsg( 'confirmdelete' ) );
+               $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->mTitle->getPrefixedText() ) );
 
                # Better double-check that it hasn't been deleted yet!
                $dbw = wfGetDB( DB_MASTER );
@@ -1898,6 +1962,15 @@ class Article {
                        return;
                }
 
+               # Hack for big sites
+               $bigHistory = $this->isBigDeletion();
+               if( $bigHistory && !$this->mTitle->userCan( 'bigdelete' ) ) {
+                       global $wgLang, $wgDeleteRevisionsLimit;
+                       $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n",
+                               array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
+                       return;
+               }
+
                if( $confirm ) {
                        $this->doDelete( $reason );
                        if( $wgRequest->getCheck( 'wpWatch' ) ) {
@@ -1908,77 +1981,47 @@ class Article {
                        return;
                }
 
-               # determine whether this page has earlier revisions
-               # and insert a warning if it does
-               $maxRevisions = 20;
-               $authors = $this->getLastNAuthors( $maxRevisions, $latest );
+               // Generate deletion reason
+               $hasHistory = false;
+               if ( !$reason ) $reason = $this->generateReason($hasHistory);
 
-               if( count( $authors ) > 1 && !$confirm ) {
+               // If the page has a history, insert a warning
+               if( $hasHistory && !$confirm ) {
                        $skin=$wgUser->getSkin();
                        $wgOut->addHTML( '<strong>' . wfMsg( 'historywarning' ) . ' ' . $skin->historyLink() . '</strong>' );
-               }
-
-               # If a single user is responsible for all revisions, find out who they are
-               if ( count( $authors ) == $maxRevisions ) {
-                       // Query bailed out, too many revisions to find out if they're all the same
-                       $authorOfAll = false;
-               } else {
-                       $authorOfAll = reset( $authors );
-                       foreach ( $authors as $author ) {
-                               if ( $authorOfAll != $author ) {
-                                       $authorOfAll = false;
-                                       break;
-                               }
-                       }
-               }
-               # Fetch article text
-               $rev = Revision::newFromTitle( $this->mTitle );
-
-               if( !is_null( $rev ) ) {
-                       # if this is a mini-text, we can paste part of it into the deletion reason
-                       $text = $rev->getText();
-
-                       #if this is empty, an earlier revision may contain "useful" text
-                       $blanked = false;
-                       if( $text == '' ) {
-                               $prev = $rev->getPrevious();
-                               if( $prev ) {
-                                       $text = $prev->getText();
-                                       $blanked = true;
-                               }
-                       }
-
-                       $length = strlen( $text );
-
-                       # this should not happen, since it is not possible to store an empty, new
-                       # page. Let's insert a standard text in case it does, though
-                       if( $length == 0 && $reason === '' ) {
-                               $reason = wfMsgForContent( 'exblank' );
-                       }
-
-                       if( $reason === '' ) {
-                               # comment field=255, let's grep the first 150 to have some user
-                               # space left
-                               global $wgContLang;
-                               $text = $wgContLang->truncate( $text, 150, '...' );
-
-                               # let's strip out newlines
-                               $text = preg_replace( "/[\n\r]/", '', $text );
-
-                               if( !$blanked ) {
-                                       if( $authorOfAll === false ) {
-                                               $reason = wfMsgForContent( 'excontent', $text );
-                                       } else {
-                                               $reason = wfMsgForContent( 'excontentauthor', $text, $authorOfAll );
-                                       }
-                               } else {
-                                       $reason = wfMsgForContent( 'exbeforeblank', $text );
-                               }
+                       if( $bigHistory ) {
+                               global $wgLang, $wgDeleteRevisionsLimit;
+                               $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n",
+                                       array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
                        }
                }
-
+               
                return $this->confirmDelete( '', $reason );
        }
+       
+       /**
+        * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
+        */
+       function isBigDeletion() {
+               global $wgDeleteRevisionsLimit;
+               if( $wgDeleteRevisionsLimit ) {
+                       $revCount = $this->estimateRevisionCount();
+                       return $revCount > $wgDeleteRevisionsLimit;
+               }
+               return false;
+       }
+       
+       /**
+        * @return int approximate revision count
+        */
+       function estimateRevisionCount() {
+               $dbr = wfGetDB();
+               // For an exact count...
+               //return $dbr->selectField( 'revision', 'COUNT(*)',
+               //      array( 'rev_page' => $this->getId() ), __METHOD__ );
+               return $dbr->estimateRowCount( 'revision', '*',
+                       array( 'rev_page' => $this->getId() ), __METHOD__ );
+       }
 
        /**
         * Get the last N authors
@@ -2028,51 +2071,59 @@ class Article {
 
        /**
         * Output deletion confirmation dialog
+        * @param $par string FIXME: do we need this parameter? One Call from Article::delete with '' only.
+        * @param $reason string Prefilled reason
         */
        function confirmDelete( $par, $reason ) {
-               global $wgOut, $wgUser;
+               global $wgOut, $wgUser, $wgContLang;
+               $align = $wgContLang->isRtl() ? 'left' : 'right';
 
                wfDebug( "Article::confirmDelete\n" );
 
-               $sub = htmlspecialchars( $this->mTitle->getPrefixedText() );
-               $wgOut->setSubtitle( wfMsg( 'deletesub', $sub ) );
+               $wgOut->setSubtitle( wfMsg( 'delete-backlink', $wgUser->getSkin()->makeKnownLinkObj( $this->mTitle ) ) );
                $wgOut->setRobotpolicy( 'noindex,nofollow' );
-               $wgOut->addWikiText( wfMsg( 'confirmdeletetext' ) );
-
-               $formaction = $this->mTitle->escapeLocalURL( 'action=delete' . $par );
-
-               $confirm = htmlspecialchars( wfMsg( 'deletepage' ) );
-               $delcom = htmlspecialchars( wfMsg( 'deletecomment' ) );
-               $token = htmlspecialchars( $wgUser->editToken() );
-               $watch = Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '2' ) );
-
-               $wgOut->addHTML( "
-<form id='deleteconfirm' method='post' action=\"{$formaction}\">
-       <table border='0'>
-               <tr>
-                       <td align='right'>
-                               <label for='wpReason'>{$delcom}:</label>
-                       </td>
-                       <td align='left'>
-                               <input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" tabindex=\"1\" />
-                       </td>
-               </tr>
-               <tr>
-                       <td>&nbsp;</td>
-                       <td>$watch</td>
-               </tr>
-               <tr>
-                       <td>&nbsp;</td>
-                       <td>
-                               <input type='submit' name='wpConfirmB' id='wpConfirmB' value=\"{$confirm}\" tabindex=\"3\" />
-                       </td>
-               </tr>
-       </table>
-       <input type='hidden' name='wpEditToken' value=\"{$token}\" />
-</form>\n" );
-
-               $wgOut->returnToMain( false );
-
+               $wgOut->addWikiMsg( 'confirmdeletetext' );
+
+               $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( 'table' ) .
+                       "<tr id=\"wpDeleteReasonListRow\">
+                               <td align='$align'>" .
+                                       Xml::label( wfMsg( 'deletecomment' ), 'wpDeleteReasonList' ) .
+                               "</td>
+                               <td>" .
+                                       Xml::listDropDown( 'wpDeleteReasonList',
+                                               wfMsgForContent( 'deletereason-dropdown' ), 
+                                               wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) .
+                               "</td>
+                       </tr>
+                       <tr id=\"wpDeleteReasonRow\">
+                               <td align='$align'>" .
+                                       Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) .
+                               "</td>
+                               <td>" .
+                                       Xml::input( 'wpReason', 60, $reason, array( 'type' => 'text', 'maxlength' => '255', 'tabindex' => '2', 'id' => 'wpReason' ) ) .
+                               "</td>
+                       </tr>
+                       <tr>
+                               <td></td>
+                               <td>" .
+                                       Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '3' ) ) .
+                               "</td>
+                       </tr>
+                       <tr>
+                               <td></td>
+                               <td>" .
+                                       Xml::submitButton( wfMsg( 'deletepage' ), array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '4' ) ) .
+                               "</td>
+                       </tr>" .
+                       Xml::closeElement( 'table' ) .
+                       Xml::closeElement( 'fieldset' ) .
+                       Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
+                       Xml::closeElement( 'form' );
+
+               $wgOut->addHTML( $form );
                $this->showLogExtract( $wgOut );
        }
 
@@ -2100,15 +2151,14 @@ class Article {
 
                if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
                        if ( $this->doDeleteArticle( $reason ) ) {
-                               $deleted = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
+                               $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 {
@@ -2218,50 +2268,76 @@ class Article {
        /**
         * Roll back the most recent consecutive set of edits to a page
         * from the same user; fails if there are no eligible edits to
-        * roll back to, e.g. user is the sole contributor
+        * roll back to, e.g. user is the sole contributor. This function
+        * performs permissions checks on $wgUser, then calls commitRollback()
+        * to do the dirty work
         *
         * @param string $fromP - Name of the user whose edits to rollback. 
         * @param string $summary - Custom summary. Set to default summary if empty.
         * @param string $token - Rollback token.
-        * @param bool $bot - If true, mark all reverted edits as bot.
+        * @param bool   $bot - If true, mark all reverted edits as bot.
         * 
-        * @param array $resultDetails contains result-specific dict of additional values
-        *    ALREADY_ROLLED : 'current' (rev)
-        *    SUCCESS        : 'summary' (str), 'current' (rev), 'target' (rev)
+        * @param array $resultDetails contains result-specific array of additional values
+        *    'alreadyrolled' : 'current' (rev)
+        *    success        : 'summary' (str), 'current' (rev), 'target' (rev)
         * 
-        * @return self::SUCCESS on succes, self::* on failure
+        * @return array of errors, each error formatted as
+        *   array(messagekey, param1, param2, ...).
+        * On success, the array is empty.  This array can also be passed to
+        * OutputPage::showPermissionsErrorPage().
         */
        public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
-               global $wgUser, $wgUseRCPatrol;
+               global $wgUser;
                $resultDetails = null;
-               
-               if( $wgUser->isAllowed( 'rollback' ) ) {
-                       if( $wgUser->isBlocked() ) {
-                               return self::BLOCKED;
-                       }
-               } else {
-                       return self::PERM_DENIED;
-               }
-                       
-               if ( wfReadOnly() ) {
-                       return self::READONLY;
-               }
+
+               # Check permissions
+               $errors = array_merge( $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser ),
+                                               $this->mTitle->getUserPermissionsErrors( 'rollback', $wgUser ) );
                if( !$wgUser->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) )
-                       return self::BAD_TOKEN;
+                       $errors[] = array( 'sessionfailure' );
 
+               if ( $wgUser->pingLimiter('rollback') || $wgUser->pingLimiter() ) {
+                       $errors[] = array( 'actionthrottledtext' );
+               }
+               # If there were errors, bail out now
+               if(!empty($errors))
+                       return $errors;
+               
+               return $this->commitRollback($fromP, $summary, $bot, $resultDetails);
+       }
+       
+       /**
+        * Backend implementation of doRollback(), please refer there for parameter
+        * and return value documentation
+        *
+        * NOTE: This function does NOT check ANY permissions, it just commits the
+        * rollback to the DB Therefore, you should only call this function direct-
+        * ly if you want to use custom permissions checks. If you don't, use
+        * doRollback() instead.
+        */     
+       public function commitRollback($fromP, $summary, $bot, &$resultDetails) {
+               global $wgUseRCPatrol, $wgUser;
                $dbw = wfGetDB( DB_MASTER );
 
+               if( wfReadOnly() ) {
+                       return array( array( 'readonlytext' ) );
+               }
+
                # Get the last editor
                $current = Revision::newFromTitle( $this->mTitle );
                if( is_null( $current ) ) {
                        # Something wrong... no page?
-                       return self::BAD_TITLE;
+                       return array(array('notanarticle'));
                }
 
                $from = str_replace( '_', ' ', $fromP );
                if( $from != $current->getUserText() ) {
                        $resultDetails = array( 'current' => $current );
-                       return self::ALREADY_ROLLED;
+                       return array(array('alreadyrolled',
+                               htmlspecialchars($this->mTitle->getPrefixedText()),
+                               htmlspecialchars($fromP),
+                               htmlspecialchars($current->getUserText())
+                       ));
                }
 
                # Get the last edit not by this guy
@@ -2269,21 +2345,19 @@ class Article {
                $user_text = $dbw->addQuotes( $current->getUserText() );
                $s = $dbw->selectRow( 'revision',
                        array( 'rev_id', 'rev_timestamp' ),
-                       array(
-                               'rev_page' => $current->getPage(),
+                       array(  'rev_page' => $current->getPage(),
                                "rev_user <> {$user} OR rev_user_text <> {$user_text}"
                        ), __METHOD__,
-                       array(
-                               'USE INDEX' => 'page_timestamp',
+                       array(  'USE INDEX' => 'page_timestamp',
                                'ORDER BY'  => 'rev_timestamp DESC' )
                        );
                if( $s === false ) {
-                       # Something wrong
-                       return self::ONLY_AUTHOR;
+                       # No one else ever edited this page
+                       return array(array('cantrollback'));
                }
        
                $set = array();
-               if ( $bot ) {
+               if ( $bot && $wgUser->isAllowed('markbotedits') ) {
                        # Mark all reverted edits as bot
                        $set['rc_bot'] = 1;
                }
@@ -2302,23 +2376,36 @@ class Article {
                                );
                }
 
-               # Get the edit summary
+               # Generate the edit summary if necessary
                $target = Revision::newFromId( $s->rev_id );
                if( empty( $summary ) )
-                       $summary = wfMsgForContent( 'revertpage', $target->getUserText(), $from );
+               {
+                       global $wgLang;
+                       $summary = wfMsgForContent( 'revertpage',
+                                        $target->getUserText(), $from,
+                                        $s->rev_id, $wgLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp), true),
+                                        $current->getId(), $wgLang->timeanddate($current->getTimestamp())
+                       );
+               }
 
                # Save
-               $flags = EDIT_UPDATE | EDIT_MINOR;
-               if( $bot )
+               $flags = EDIT_UPDATE;
+
+               if ($wgUser->isAllowed('minoredit'))
+                       $flags |= EDIT_MINOR;
+
+               if( $bot && ($wgUser->isAllowed('markbotedits') || $wgUser->isAllowed('bot')) )
                        $flags |= EDIT_FORCE_BOT;
                $this->doEdit( $target->getText(), $summary, $flags );
 
+               wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target ) );
+
                $resultDetails = array(
                        'summary' => $summary,
                        'current' => $current,
                        'target' => $target,
                );
-               return self::SUCCESS;
+               return array();
        }
 
        /**
@@ -2326,8 +2413,8 @@ class Article {
         */
        function rollback() {
                global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
-
                $details = null;
+
                $result = $this->doRollback(
                        $wgRequest->getVal( 'from' ),
                        $wgRequest->getText( 'summary' ),
@@ -2336,58 +2423,44 @@ class Article {
                        $details
                );
 
-               switch( $result ) {
-                       case self::BLOCKED:
-                               $wgOut->blockedPage();
-                               break;
-                       case self::PERM_DENIED:
-                               $wgOut->permissionRequired( 'rollback' );
-                               break;
-                       case self::READONLY:
-                               $wgOut->readOnlyPage( $this->getContent() );
-                               break;
-                       case self::BAD_TOKEN:
-                               $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
-                               $wgOut->addWikiText( wfMsg( 'sessionfailure' ) );
-                               break;
-                       case self::BAD_TITLE:
-                               $wgOut->addHtml( wfMsg( 'notanarticle' ) );
-                               break;
-                       case self::ALREADY_ROLLED:
-                               $current = $details['current'];
-                               $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
-                               $wgOut->addWikiText(
-                                       wfMsg( 'alreadyrolled',
-                                               htmlspecialchars( $this->mTitle->getPrefixedText() ),
-                                               htmlspecialchars( $wgRequest->getVal( 'from' ) ),
-                                               htmlspecialchars( $current->getUserText() )
-                                       )
-                               );
-                               if( $current->getComment() != '' ) {
-                                       $wgOut->addHtml( wfMsg( 'editcomment',
-                                               $wgUser->getSkin()->formatComment( $current->getComment() ) ) );
+               if( in_array( array( 'blocked' ), $result ) ) {
+                       $wgOut->blockedPage();
+                       return;
+               }
+               if( in_array( array( 'actionthrottledtext' ), $result ) ) {
+                       $wgOut->rateLimited();
+                       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.
+               if( !empty($result) && $result !== array( array('readonlytext') ) ) {
+                       # array_diff is completely broken for arrays of arrays, sigh.  Re-
+                       # move any 'readonlytext' error manually.
+                       $out = array();
+                       foreach( $result as $error ) {
+                               if( $error != array( 'readonlytext' ) ) {
+                                       $out []= $error;
                                }
-                               break;
-                       case self::ONLY_AUTHOR:
-                               $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
-                               $wgOut->addHtml( wfMsg( 'cantrollback' ) );
-                               break;
-                       case self::SUCCESS:
-                               $current = $details['current'];
-                               $target = $details['target'];
-                               $wgOut->setPageTitle( wfMsg( 'actioncomplete' ) );
-                               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-                               $old = $wgUser->getSkin()->userLink( $current->getUser(), $current->getUserText() )
-                                       . $wgUser->getSkin()->userToolLinks( $current->getUser(), $current->getUserText() );
-                               $new = $wgUser->getSkin()->userLink( $target->getUser(), $target->getUserText() )
-                                       . $wgUser->getSkin()->userToolLinks( $target->getUser(), $target->getUserText() );
-                               $wgOut->addHtml( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
-                               $wgOut->returnToMain( false, $this->mTitle );
-                               break;
-                       default:
-                               throw new MWException( __METHOD__ . ": Unknown return value `{$result}`" );
+                       }
+                       $wgOut->showPermissionsErrorPage( $out );
+                       return;
+               }
+               if( $result == array( array('readonlytext') ) ) {
+                       $wgOut->readOnlyPage();
+                       return;
                }
 
+               $current = $details['current'];
+               $target = $details['target'];
+               $wgOut->setPageTitle( wfMsg( 'actioncomplete' ) );
+               $wgOut->setRobotPolicy( 'noindex,nofollow' );
+               $old = $wgUser->getSkin()->userLink( $current->getUser(), $current->getUserText() )
+                       . $wgUser->getSkin()->userToolLinks( $current->getUser(), $current->getUserText() );
+               $new = $wgUser->getSkin()->userLink( $target->getUser(), $target->getUserText() )
+                       . $wgUser->getSkin()->userToolLinks( $target->getUser(), $target->getUserText() );
+               $wgOut->addHtml( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
+               $wgOut->returnToMain( false, $this->mTitle );
        }
 
 
@@ -2412,10 +2485,33 @@ class Article {
                $wgUser->clearNotification( $this->mTitle );
        }
 
+       /**
+        * Prepare text which is about to be saved.
+        * Returns a stdclass with source, pst and output members
+        */
+       function prepareTextForEdit( $text, $revid=null ) {
+               if ( $this->mPreparedEdit && $this->mPreparedEdit->newText == $text && $this->mPreparedEdit->revid == $revid) {
+                       // Already prepared
+                       return $this->mPreparedEdit;
+               }
+               global $wgParser;
+               $edit = (object)array();
+               $edit->revid = $revid;
+               $edit->newText = $text;
+               $edit->pst = $this->preSaveTransform( $text );
+               $options = new ParserOptions;
+               $options->setTidy( true );
+               $options->enableLimitReport();
+               $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $options, true, true, $revid );
+               $edit->oldText = $this->getContent();
+               $this->mPreparedEdit = $edit;
+               return $edit;
+       }
+
        /**
         * Do standard deferred updates after page edit.
         * Update links tables, site stats, search index and message cache.
-        * Every 1000th edit, prune the recent changes table.
+        * Every 100th edit, prune the recent changes table.
         *
         * @private
         * @param $text New text of the article
@@ -2426,21 +2522,28 @@ class Article {
         * @param $changed Whether or not the content actually changed
         */
        function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange, $newid, $changed = true ) {
-               global $wgDeferredUpdateList, $wgMessageCache, $wgUser, $wgParser;
+               global $wgDeferredUpdateList, $wgMessageCache, $wgUser, $wgParser, $wgEnableParserCache;
 
                wfProfileIn( __METHOD__ );
 
                # Parse the text
-               $options = new ParserOptions;
-               $options->setTidy(true);
-               $poutput = $wgParser->parse( $text, $this->mTitle, $options, true, true, $newid );
+               # Be careful not to double-PST: $text is usually already PST-ed once
+               if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
+                       wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
+                       $editInfo = $this->prepareTextForEdit( $text, $newid );
+               } else {
+                       wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
+                       $editInfo = $this->mPreparedEdit;
+               }
 
                # Save it to the parser cache
-               $parserCache =& ParserCache::singleton();
-               $parserCache->save( $poutput, $this, $wgUser );
+               if ( $wgEnableParserCache ) {
+                       $parserCache =& ParserCache::singleton();
+                       $parserCache->save( $editInfo->output, $this, $wgUser );
+               }
 
                # Update the links tables
-               $u = new LinksUpdate( $this->mTitle, $poutput );
+               $u = new LinksUpdate( $this->mTitle, $editInfo->output );
                $u->doUpdate();
 
                if( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
@@ -2794,6 +2897,7 @@ class Article {
 
                $title->touchLinks();
                $title->purgeSquid();
+               $title->deleteTitleProtection();
        }
 
        static function onArticleDelete( $title ) {
@@ -2811,6 +2915,10 @@ class Article {
                if( $title->getNamespace() == NS_MEDIAWIKI) {
                        $wgMessageCache->replace( $title->getDBkey(), false );
                }
+               if( $title->getNamespace() == NS_IMAGE ) {
+                       $update = new HTMLCacheUpdate( $title, 'imagelinks' );
+                       $update->doUpdate();
+               }
        }
 
        /**
@@ -2820,9 +2928,11 @@ class Article {
                global $wgDeferredUpdateList, $wgUseFileCache;
 
                // Invalidate caches of articles which include this page
-               $update = new HTMLCacheUpdate( $title, 'templatelinks' );
-               $wgDeferredUpdateList[] = $update;
+               $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
 
+               // Invalidate the caches of all pages which redirect here
+               $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
+               
                # Purge squid for this page only
                $title->purgeSquid();
 
@@ -2961,6 +3071,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.
         *
@@ -3047,14 +3187,16 @@ class Article {
         * @param bool    $cache
         */
        public function outputWikiText( $text, $cache = true ) {
-               global $wgParser, $wgUser, $wgOut;
+               global $wgParser, $wgUser, $wgOut, $wgEnableParserCache;
 
                $popts = $wgOut->parserOptions();
                $popts->setTidy(true);
+               $popts->enableLimitReport();
                $parserOutput = $wgParser->parse( $text, $this->mTitle,
                        $popts, true, true, $this->getRevIdFetched() );
                $popts->setTidy(false);
-               if ( $cache && $this && $parserOutput->getCacheTime() != -1 ) {
+               $popts->enableLimitReport( false );
+               if ( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) {
                        $parserCache =& ParserCache::singleton();
                        $parserCache->save( $parserOutput, $this, $wgUser );
                }