Move the hook 'NewRevisionFromEditComplete'
[lhc/web/wiklou.git] / includes / Article.php
index 3637973..4d8277b 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 /**
  * File for articles
+ * @file
  */
 
 /**
@@ -34,6 +35,8 @@ class Article {
        var $mTouched;                  //!<
        var $mUser;                             //!<
        var $mUserText;                 //!<
+       var $mRedirectTarget;           //!<
+       var $mIsRedirect;
        /**@}}*/
 
        /**
@@ -57,12 +60,69 @@ class Article {
        }
 
        /**
+        * If this page is a redirect, get its target
+        *
+        * The target will be fetched from the redirect table if possible.
+        * If this page doesn't have an entry there, call insertRedirect()
+        * @return mixed Title object, or null if this page is not a redirect
+        */
+       public function getRedirectTarget() {
+               if(!$this->mTitle || !$this->mTitle->isRedirect())
+                       return null;
+               if(!is_null($this->mRedirectTarget))
+                       return $this->mRedirectTarget;
+
+               # Query the redirect table
+               $dbr = wfGetDB(DB_SLAVE);
+               $res = $dbr->select('redirect',
+                               array('rd_namespace', 'rd_title'),
+                               array('rd_from' => $this->getID()),
+                               __METHOD__
+               );
+               $row = $dbr->fetchObject($res);
+               if($row)
+                       return $this->mRedirectTarget = Title::makeTitle($row->rd_namespace, $row->rd_title);
+
+               # This page doesn't have an entry in the redirect table
+               return $this->mRedirectTarget = $this->insertRedirect();
+       }
+
+       /**
+        * Insert an entry for this page into the redirect table.
+        *
+        * Don't call this function directly unless you know what you're doing.
+        * @return Title object
+        */
+       public function insertRedirect() {
+               $retval = Title::newFromRedirect($this->getContent());
+               if(!$retval)
+                       return null;
+               $dbw = wfGetDB(DB_MASTER);
+               $dbw->replace('redirect', array('rd_from'), array(
+                               'rd_from' => $this->getID(),
+                               'rd_namespace' => $retval->getNamespace(),
+                               'rd_title' => $retval->getDBKey()
+               ), __METHOD__);
+               return $retval;
+       }
+
+       /**
+        * Get the Title object this page redirects to
+        *
         * @return mixed false, Title of in-wiki target, or string with URL
         */
-       function followRedirect() {
+       public function followRedirect() {
                $text = $this->getContent();
+               return self::followRedirectText( $text );
+       }
+       
+       /**
+        * Get the Title object this text redirects to
+        *
+        * @return mixed false, Title of in-wiki target, or string with URL
+        */
+       public function followRedirectText( $text ) {
                $rt = Title::newFromRedirect( $text );
-
                # process if title object is valid and not special:userlogout
                if( $rt ) {
                        if( $rt->getInterwiki() != '' ) {
@@ -92,7 +152,6 @@ class Article {
                                return $rt;
                        }
                }
-
                // No or invalid redirect
                return false;
        }
@@ -114,6 +173,7 @@ class Article {
 
                $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded
                $this->mRedirectedFrom = null; # Title object if set
+               $this->mRedirectTarget = null; # Title object if set
                $this->mUserText =
                $this->mTimestamp = $this->mComment = '';
                $this->mGoodAdjustment = $this->mTotalAdjustment = 0;
@@ -299,13 +359,13 @@ class Article {
         */
        function loadPageData( $data = 'fromdb' ) {
                if ( $data === 'fromdb' ) {
-                       $dbr = $this->getDB();
+                       $dbr = wfGetDB( DB_MASTER );
                        $data = $this->pageDataFromId( $dbr, $this->getId() );
                }
 
-               $lc =& LinkCache::singleton();
+               $lc = LinkCache::singleton();
                if ( $data ) {
-                       $lc->addGoodLinkObj( $data->page_id, $this->mTitle );
+                       $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect );
 
                        $this->mTitle->mArticleID = $data->page_id;
 
@@ -337,15 +397,13 @@ class Article {
                        return $this->mContent;
                }
 
-               $dbr = $this->getDB();
+               $dbr = wfGetDB( DB_MASTER );
 
                # Pre-fill content with error message so that if something
                # fails we'll have something telling us what we intended.
                $t = $this->mTitle->getPrefixedText();
-               if( $oldid ) {
-                       $t .= ',oldid='.$oldid;
-               }
-               $this->mContent = wfMsg( 'missingarticle', $t ) ;
+               $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
+               $this->mContent = wfMsg( 'missing-article', $t, $d ) ;
 
                if( $oldid ) {
                        $revision = Revision::newFromId( $oldid );
@@ -385,7 +443,7 @@ class Article {
                $this->mComment   = $revision->getComment();
                $this->mTimestamp = wfTimestamp( TS_MW, $revision->getTimestamp() );
 
-               $this->mRevIdFetched = $revision->getID();
+               $this->mRevIdFetched = $revision->getId();
                $this->mContentLoaded = true;
                $this->mRevision =& $revision;
 
@@ -407,8 +465,10 @@ class Article {
         * Get the database which should be used for reads
         *
         * @return Database
+        * @deprecated - just call wfGetDB( DB_MASTER ) instead
         */
        function getDB() {
+               wfDeprecated( __METHOD__ );
                return wfGetDB( DB_MASTER );
        }
 
@@ -490,6 +550,10 @@ class Article {
         */
        function isRedirect( $text = false ) {
                if ( $text === false ) {
+                       if ( $this->mDataLoaded ) 
+                               return $this->mIsRedirect;
+                       
+                       // Apparently loadPageData was never called
                        $this->loadContent();
                        $titleObj = Title::newFromRedirect( $this->fetchContent() );
                } else {
@@ -526,14 +590,14 @@ class Article {
                $id = $this->getID();
                if ( 0 == $id ) return;
 
-               $this->mLastRevision = Revision::loadFromPageId( $this->getDB(), $id );
+               $this->mLastRevision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id );
                if( !is_null( $this->mLastRevision ) ) {
                        $this->mUser      = $this->mLastRevision->getUser();
                        $this->mUserText  = $this->mLastRevision->getUserText();
                        $this->mTimestamp = $this->mLastRevision->getTimestamp();
                        $this->mComment   = $this->mLastRevision->getComment();
                        $this->mMinorEdit = $this->mLastRevision->isMinor();
-                       $this->mRevIdFetched = $this->mLastRevision->getID();
+                       $this->mRevIdFetched = $this->mLastRevision->getId();
                }
        }
 
@@ -571,7 +635,6 @@ class Article {
        }
 
        /**
-        * @todo Document, fixme $offset never used.
         * @param $limit Integer: default 0.
         * @param $offset Integer: default 0.
         */
@@ -593,6 +656,8 @@ class Article {
                        ORDER BY timestamp DESC";
 
                if ($limit > 0) { $sql .= ' LIMIT '.$limit; }
+               if ($offset > 0) { $sql .= ' OFFSET '.$offset; }
+               
                $sql .= ' '. $this->getSelectOptions();
 
                $res = $dbr->query($sql, __METHOD__);
@@ -609,7 +674,7 @@ class Article {
         * This is the default action of the script: just view the page of
         * the given title.
        */
-       function view() {
+       function view() {
                global $wgUser, $wgOut, $wgRequest, $wgContLang;
                global $wgEnableParserCache, $wgStylePath, $wgParser;
                global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies;
@@ -618,7 +683,7 @@ class Article {
 
                wfProfileIn( __METHOD__ );
 
-               $parserCache =& ParserCache::singleton();
+               $parserCache = ParserCache::singleton();
                $ns = $this->mTitle->getNamespace(); # shortcut
 
                # Get variables from query string
@@ -689,12 +754,7 @@ class Article {
                }
 
                # Should the parser cache be used?
-               $pcache = $wgEnableParserCache
-                       && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0
-                       && $this->exists()
-                       && empty( $oldid )
-                       && !$this->mTitle->isCssOrJsPage()
-                       && !$this->mTitle->isCssJsSubpage();
+               $pcache = $this->useParserCache( $oldid );
                wfDebug( 'Article::view using parser cache: ' . ($pcache ? 'yes' : 'no' ) . "\n" );
                if ( $wgUser->getOption( 'stubthreshold' ) ) {
                        wfIncrStats( 'pcache_miss_stub' );
@@ -705,7 +765,6 @@ class Article {
                        // This is an internally redirected page view.
                        // We'll need a backlink to the source page for navigation.
                        if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
-                               $sk = $wgUser->getSkin();
                                $redir = $sk->makeKnownLinkObj( $this->mRedirectedFrom, '', 'redirect=no' );
                                $s = wfMsg( 'redirectedfrom', $redir );
                                $wgOut->setSubtitle( $s );
@@ -722,7 +781,6 @@ class Article {
                        // If it was reported from a trusted site, supply a backlink.
                        global $wgRedirectSources;
                        if( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
-                               $sk = $wgUser->getSkin();
                                $redir = $sk->makeExternalLink( $rdfrom, $rdfrom );
                                $s = wfMsg( 'redirectedfrom', $redir );
                                $wgOut->setSubtitle( $s );
@@ -740,16 +798,17 @@ class Article {
                                $outputDone = true;
                        }
                }
+               # Fetch content and check for errors
                if ( !$outputDone ) {
                        $text = $this->getContent();
                        if ( $text === false ) {
                                # Failed to load, replace text with error message
                                $t = $this->mTitle->getPrefixedText();
                                if( $oldid ) {
-                                       $t .= ',oldid='.$oldid;
-                                       $text = wfMsg( 'missingarticle', $t );
+                                       $d = wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid );
+                                       $text = wfMsg( 'missing-article', $t, $d );
                                } else {
-                                       $text = wfMsg( 'noarticletext', $t );
+                                       $text = wfMsg( 'noarticletext' );
                                }
                        }
 
@@ -757,11 +816,11 @@ class Article {
                        if ( !$this->mTitle->userCanRead() ) {
                                $wgOut->loginToUse();
                                $wgOut->output();
+                               wfProfileOut( __METHOD__ );
                                exit;
                        }
 
                        # We're looking at an old revision
-
                        if ( !empty( $oldid ) ) {
                                $wgOut->setRobotpolicy( 'noindex,nofollow' );
                                if( is_null( $this->mRevision ) ) {
@@ -772,6 +831,7 @@ class Article {
                                                if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) {
                                                        $wgOut->addWikiMsg( 'rev-deleted-text-permission' );
                                                        $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
+                                                       wfProfileOut( __METHOD__ );
                                                        return;
                                                } else {
                                                        $wgOut->addWikiMsg( 'rev-deleted-text-view' );
@@ -779,16 +839,13 @@ class Article {
                                                }
                                        }
                                }
-
                        }
-               }
-               if( !$outputDone ) {
-                       $wgOut->setRevisionId( $this->getRevIdFetched() );
                        
+                       $wgOut->setRevisionId( $this->getRevIdFetched() );
+
                         // Pages containing custom CSS or JavaScript get special treatment
                        if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
                                $wgOut->addHtml( wfMsgExt( 'clearyourcache', 'parse' ) );
-
                                // Give hooks a chance to customise the output
                                if( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) {
                                        // Wrap the whole lot in a <pre> and don't parse
@@ -798,22 +855,9 @@ class Article {
                                        $wgOut->addHtml( htmlspecialchars( $this->mContent ) );
                                        $wgOut->addHtml( "\n</pre>\n" );
                                }
-                       
-                       }
-                       
-                       elseif ( $rt = Title::newFromRedirect( $text ) ) {
-                               # Display redirect
-                               $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
-                               $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png';
-                               # Don't overwrite the subtitle if this was an old revision
-                               if( !$wasRedirected && $this->isCurrent() ) {
-                                       $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) );
-                               }
-                               $link = $sk->makeLinkObj( $rt, $rt->getFullText() );
-
-                               $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT " />' .
-                                 '<span class="redirectText">'.$link.'</span>' );
-
+                       } else if ( $rt = Title::newFromRedirect( $text ) ) {
+                               # Don't append the subtitle if this was an old revision
+                               $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() );
                                $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser));
                                $wgOut->addParserOutputNoText( $parseout );
                        } else if ( $pcache ) {
@@ -840,7 +884,6 @@ class Article {
                                if( !$this->isCurrent() ) {
                                        $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
                                }
-
                        }
                }
                /* title may have been set from the cache */
@@ -850,8 +893,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() ) ) {
+               if( $ns == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) {
                        $wgOut->addWikiMsg('anontalkpagetext');
                }
 
@@ -875,6 +917,41 @@ class Article {
                $this->viewUpdates();
                wfProfileOut( __METHOD__ );
        }
+       
+       /* 
+       * Should the parser cache be used?
+       */
+       protected function useParserCache( $oldid ) {
+               global $wgUser, $wgEnableParserCache;
+               
+               return $wgEnableParserCache
+                       && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0
+                       && $this->exists()
+                       && empty( $oldid )
+                       && !$this->mTitle->isCssOrJsPage()
+                       && !$this->mTitle->isCssJsSubpage();
+       }
+       
+       protected function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
+               global $wgParser, $wgOut, $wgContLang, $wgStylePath, $wgUser;
+               
+               # Display redirect
+               $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
+               $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png';
+               
+               if( $appendSubtitle ) {
+                       $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
+               }
+               $sk = $wgUser->getSkin();
+               if ( $forceKnown )
+                       $link = $sk->makeKnownLinkObj( $target, htmlspecialchars( $target->getFullText() ) );
+               else
+                       $link = $sk->makeLinkObj( $target, htmlspecialchars( $target->getFullText() ) );
+
+               $wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT " />' .
+                       '<span class="redirectText">'.$link.'</span>' );
+               
+       }
 
        function addTrackbacks() {
                global $wgOut, $wgUser;
@@ -1112,6 +1189,8 @@ class Article {
                                $dbw->delete( 'redirect', $where, __METHOD__);
                        }
 
+                       if( $this->getTitle()->getNamespace() == NS_IMAGE )
+                               RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
                        wfProfileOut( __METHOD__ );
                        return ( $dbw->affectedRows() != 0 );
                }
@@ -1252,10 +1331,10 @@ class Article {
                                }
                        }
 
-                       $extraq = ''; // Give extensions a chance to modify URL query on update
-                       wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraq ) );
+                       $extraQuery = ''; // Give extensions a chance to modify URL query on update
+                       wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraQuery ) );
 
-                       $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraq );
+                       $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraQuery );
                }
                return $good;
        }
@@ -1291,11 +1370,12 @@ class Article {
         * EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception
         * to be thrown from the Database. These two conditions are also possible with auto-detection due
         * to MediaWiki's performance-optimised locking strategy.
+        * @param $baseRevId, the revision ID this edit was based off, if any
         *
         * @return bool success
         */
-       function doEdit( $text, $summary, $flags = 0 ) {
-               global $wgUser, $wgDBtransactions;
+       function doEdit( $text, $summary, $flags = 0, $baseRevId = false ) {
+               global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
 
                wfProfileIn( __METHOD__ );
                $good = true;
@@ -1325,9 +1405,10 @@ class Article {
                $oldtext = $this->getContent();
                $oldsize = strlen( $oldtext );
 
-               # Provide autosummaries if one is not provided.
-               if ($flags & EDIT_AUTOSUMMARY && $summary == '')
+               # Provide autosummaries if one is not provided and autosummaries are enabled.
+               if( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
                        $summary = $this->getAutosummary( $oldtext, $text, $flags );
+               }
 
                $editInfo = $this->prepareTextForEdit( $text );
                $text = $editInfo->pst;
@@ -1346,7 +1427,7 @@ class Article {
 
                        $lastRevision = 0;
                        $revisionId = 0;
-                       
+
                        $changed = ( strcmp( $text, $oldtext ) != 0 );
 
                        if ( $changed ) {
@@ -1383,6 +1464,8 @@ class Article {
                                        $good = false;
                                        $dbw->rollback();
                                } else {
+                                       wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId ) );
+
                                        # Update recentchanges
                                        if( !( $flags & EDIT_SUPPRESS_RC ) ) {
                                                $rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary,
@@ -1415,7 +1498,7 @@ 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.
                                $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
                        }
@@ -1447,6 +1530,8 @@ class Article {
 
                        # Update the page record with revision data
                        $this->updateRevisionOn( $dbw, $revision, 0 );
+                       
+                       wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, false) );
 
                        if( !( $flags & EDIT_SUPPRESS_RC ) ) {
                                $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot,
@@ -1496,16 +1581,16 @@ class Article {
         *
         * @param boolean $noRedir Add redirect=no
         * @param string $sectionAnchor section to redirect to, including "#"
-        * @param string $extraq, extra query params
+        * @param string $extraQuery, extra query params
         */
-       function doRedirect( $noRedir = false, $sectionAnchor = '', $extraq = '' ) {
+       function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
                global $wgOut;
                if ( $noRedir ) {
                        $query = 'redirect=no';
-                       if( $extraq )
+                       if( $extraQuery )
                                $query .= "&$query";
                } else {
-                       $query = $extraq;
+                       $query = $extraQuery;
                }
                $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor );
        }
@@ -1521,7 +1606,7 @@ class Article {
 
                if ( !($wgUseNPPatrol || $wgUseRCPatrol)) {
                        $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
-                       return;         
+                       return;
                }
 
                # If we haven't been given an rc_id value, we can't do anything
@@ -1540,7 +1625,7 @@ class Article {
                        $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
                        return;
                }
-               
+
                # Check permissions
                $permission_errors = $this->mTitle->getUserPermissionsErrors( 'patrol', $wgUser );
 
@@ -1730,8 +1815,8 @@ class Article {
                $updated = Article::flattenRestrictions( $limit );
 
                $changed = ( $current != $updated );
-               $changed = $changed || ($this->mTitle->areRestrictionsCascading() != $cascade);
-               $changed = $changed || ($this->mTitle->mRestrictionsExpiry != $expiry);
+               $changed = $changed || ($updated && $this->mTitle->areRestrictionsCascading() != $cascade);
+               $changed = $changed || ($updated && $this->mTitle->mRestrictionsExpiry != $expiry);
                $protect = ( $updated != '' );
 
                # If nothing's changed, do nothing
@@ -1757,13 +1842,16 @@ class Article {
                                }
                                $comment = $wgContLang->ucfirst( wfMsgForContent( $comment_type, $this->mTitle->getPrefixedText() ) );
 
-                               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'] );        
+                               # Only restrictions with the 'protect' right can cascade...
+                               # Otherwise, people who cannot normally protect can "protect" pages via transclusion
+                               foreach( $limit as $action => $restriction ) {
+                                       # FIXME: can $restriction be an array or what? (same as fixme above)
+                                       if( $restriction != 'protect' && $restriction != 'sysop' ) {
+                                               $cascade = false;
+                                               break;
+                                       }
                                }
-                               
+
                                $cascade_description = '';
                                if ($cascade) {
                                        $cascade_description = ' ['.wfMsg('protect-summary-cascade').']';
@@ -1777,7 +1865,7 @@ class Article {
                                        $comment .= "$expiry_description";
                                if ( $cascade )
                                        $comment .= "$cascade_description";
-                               
+
                                # Update restrictions table
                                foreach( $limit as $action => $restrictions ) {
                                        if ($restrictions != '' ) {
@@ -1805,15 +1893,15 @@ class Article {
                                                'page_id' => $id
                                        ), 'Article::protect'
                                );
+                               
+                               wfRunHooks( 'NewRevisionFromEditComplete', array($this, $nullRevision, false) );
                                wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) );
 
                                # Update the protection log
                                $log = new LogPage( 'protect' );
-                               
-                               
-
                                if( $protect ) {
-                                       $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason . " [$updated]$cascade_description$expiry_description" ) );
+                                       $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, 
+                                               trim( $reason . " [$updated]$cascade_description$expiry_description" ) );
                                } else {
                                        $log->addEntry( 'unprotect', $this->mTitle, $reason );
                                }
@@ -1844,7 +1932,7 @@ class Article {
                }
                return implode( ':', $bits );
        }
-       
+
        /**
         * Auto-generates a deletion reason
         * @param bool &$hasHistory Whether the page has a history
@@ -1906,7 +1994,7 @@ class Article {
                        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
@@ -1929,12 +2017,12 @@ class Article {
 
                $confirm = $wgRequest->wasPosted() &&
                                $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;
@@ -1942,7 +2030,7 @@ class Article {
                        $reason = $this->DeleteReason;
                }
                # Flag to hide all contents of the archived revisions
-               $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('deleterevision');
+               $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('suppressrevision');
 
                # This code desperately needs to be totally rewritten
 
@@ -1951,7 +2039,7 @@ class Article {
                        $wgOut->readOnlyPage();
                        return;
                }
-               
+
                # Check permissions
                $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
 
@@ -2004,10 +2092,10 @@ class Article {
                                        array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
                        }
                }
-               
-               return $this->confirmDelete( '', $reason );
+
+               return $this->confirmDelete( $reason );
        }
-       
+
        /**
         * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
         */
@@ -2019,7 +2107,7 @@ class Article {
                }
                return false;
        }
-       
+
        /**
         * @return int approximate revision count
         */
@@ -2080,10 +2168,9 @@ 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 ) {
+       function confirmDelete( $reason ) {
                global $wgOut, $wgUser, $wgContLang;
                $align = $wgContLang->isRtl() ? 'left' : 'right';
 
@@ -2093,7 +2180,7 @@ class Article {
                $wgOut->setRobotpolicy( 'noindex,nofollow' );
                $wgOut->addWikiMsg( 'confirmdeletetext' );
 
-               if( $wgUser->isAllowed( 'deleterevision' ) ) {
+               if( $wgUser->isAllowed( 'suppressrevision' ) ) {
                        $suppress = "<tr id=\"wpDeleteSuppressRow\" name=\"wpDeleteSuppressRow\"><td></td><td>";
                        $suppress .= Xml::checkLabel( wfMsg( 'revdelete-suppress' ), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '2' ) );
                        $suppress .= "</td></tr>";
@@ -2101,9 +2188,9 @@ class Article {
                        $suppress = '';
                }
 
-               $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=delete' . $par ), 'id' => 'deleteconfirm' ) ) .
+               $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
                        Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
-                       Xml::element( 'legend', null, wfMsg( 'delete-legend' ) ) .
+                       Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) .
                        Xml::openElement( 'table' ) .
                        "<tr id=\"wpDeleteReasonListRow\">
                                <td align='$align'>" .
@@ -2111,7 +2198,7 @@ class Article {
                                "</td>
                                <td>" .
                                        Xml::listDropDown( 'wpDeleteReasonList',
-                                               wfMsgForContent( 'deletereason-dropdown' ), 
+                                               wfMsgForContent( 'deletereason-dropdown' ),
                                                wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) .
                                "</td>
                        </tr>
@@ -2167,8 +2254,12 @@ class Article {
        function doDelete( $reason, $suppress = false ) {
                global $wgOut, $wgUser;
                wfDebug( __METHOD__."\n" );
+               
+               $id = $this->getId();
+               
+               $error = '';
 
-               if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
+               if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason, &$error))) {
                        if ( $this->doDeleteArticle( $reason, $suppress ) ) {
                                $deleted = $this->mTitle->getPrefixedText();
 
@@ -2179,9 +2270,12 @@ class Article {
 
                                $wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink );
                                $wgOut->returnToMain( false );
-                               wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason));
+                               wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason, $id));
                        } else {
-                               $wgOut->showFatalError( wfMsg( 'cannotdelete' ).'<br/>'.wfMsg('cannotdelete-merge') );
+                               if ($error = '')
+                                       $wgOut->showFatalError( wfMsg( 'cannotdelete' ) );
+                               else
+                                       $wgOut->showFatalError( $error );
                        }
                }
        }
@@ -2220,7 +2314,8 @@ class Article {
                } else {
                        $bitfield = 'rev_deleted';
                }
-               
+
+               $dbw->begin();
                // 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
@@ -2267,6 +2362,11 @@ class Article {
 
                # Now that it's safely backed up, delete it
                $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__);
+               $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
+               if( !$ok ) {
+                       $dbw->rollback();
+                       return false;
+               }
 
                # If using cascading deletes, we can skip some explicit deletes
                if ( !$dbw->cascadingDeletes() ) {
@@ -2289,20 +2389,26 @@ class Article {
                if ( !$dbw->cleanupTriggers() ) {
 
                        # Clean up recentchanges entries...
-                       $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), __METHOD__ );
+                       $dbw->delete( 'recentchanges',
+                               array( 'rc_namespace' => $ns, 'rc_title' => $t, 'rc_type != '.RC_LOG ),
+                               __METHOD__ );
                }
+               $dbw->commit();
 
                # Clear caches
                Article::onArticleDelete( $this->mTitle );
 
+               # Clear the cached article id so the interface doesn't act like we exist
+               $this->mTitle->resetArticleID( 0 );
+               $this->mTitle->mArticleID = 0;
+
                # Log the deletion, if the page was suppressed, log it at Oversight instead
                $logtype = $suppress ? 'suppress' : '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
-               $this->mTitle->resetArticleID( 0 );
-               $this->mTitle->mArticleID = 0;
+               # Make sure logging got through
+               $log->addEntry( 'delete', $this->mTitle, $reason, array() );
+
                return true;
        }
 
@@ -2313,15 +2419,15 @@ class Article {
         * 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 $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 array $resultDetails contains result-specific array of additional values
         *    'alreadyrolled' : 'current' (rev)
         *    success        : 'summary' (str), 'current' (rev), 'target' (rev)
-        * 
+        *
         * @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
@@ -2343,10 +2449,10 @@ class Article {
                # 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
@@ -2355,9 +2461,9 @@ class Article {
         * 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;
+               global $wgUseRCPatrol, $wgUser, $wgLang;
                $dbw = wfGetDB( DB_MASTER );
 
                if( wfReadOnly() ) {
@@ -2399,7 +2505,7 @@ class Article {
                        # Only admins can see this text
                        return array(array('notvisiblerev'));
                }
-       
+
                $set = array();
                if ( $bot && $wgUser->isAllowed('markbotedits') ) {
                        # Mark all reverted edits as bot
@@ -2422,15 +2528,17 @@ class Article {
 
                # Generate the edit summary if necessary
                $target = Revision::newFromId( $s->rev_id );
-               if( empty( $summary ) )
-               {
-                       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())
-                       );
+               if( empty( $summary ) ){
+                       $summary = wfMsgForContent( 'revertpage' );
                }
+               
+               # Allow the custom summary to use the same args as the default message
+               $args = array(
+                       $target->getUserText(), $from, $s->rev_id,
+                       $wgLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp), true),
+                       $current->getId(), $wgLang->timeanddate($current->getTimestamp())
+               );
+               $summary = wfMsgReplaceArgs( $summary, $args ); 
 
                # Save
                $flags = EDIT_UPDATE;
@@ -2440,7 +2548,7 @@ class Article {
 
                if( $bot && ($wgUser->isAllowed('markbotedits') || $wgUser->isAllowed('bot')) )
                        $flags |= EDIT_FORCE_BOT;
-               $this->doEdit( $target->getText(), $summary, $flags );
+               $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
 
                wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target ) );
 
@@ -2518,6 +2626,11 @@ class Article {
                        . $wgUser->getSkin()->userToolLinks( $target->getUser(), $target->getUserText() );
                $wgOut->addHtml( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
                $wgOut->returnToMain( false, $this->mTitle );
+               
+               if( !$wgRequest->getBool( 'hidediff', false ) ) {
+                       $de = new DifferenceEngine( $this->mTitle, $current->getId(), 'next', false, true );
+                       $de->showDiff( '', '' );
+               }
        }
 
 
@@ -2526,11 +2639,12 @@ class Article {
         * @private
         */
        function viewUpdates() {
-               global $wgDeferredUpdateList;
+               global $wgDeferredUpdateList, $wgUser;
 
                if ( 0 != $this->getID() ) {
+                       # Don't update page view counters on views from bot users (bug 14044)
                        global $wgDisableCounters;
-                       if( !$wgDisableCounters ) {
+                       if( !$wgDisableCounters && !$wgUser->isAllowed( 'bot' ) ) {
                                Article::incViewCount( $this->getID() );
                                $u = new SiteStatsUpdate( 1, 0, 0 );
                                array_push( $wgDeferredUpdateList, $u );
@@ -2538,7 +2652,6 @@ class Article {
                }
 
                # Update newtalk / watchlist notification status
-               global $wgUser;
                $wgUser->clearNotification( $this->mTitle );
        }
 
@@ -2595,7 +2708,7 @@ class Article {
 
                # Save it to the parser cache
                if ( $wgEnableParserCache ) {
-                       $parserCache =& ParserCache::singleton();
+                       $parserCache = ParserCache::singleton();
                        $parserCache->save( $editInfo->output, $this, $wgUser );
                }
 
@@ -2714,14 +2827,14 @@ class Article {
                        : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid );
 
                $cdel='';
-               if( $wgUser->isAllowed( 'deleterevision' ) ) {          
+               if( $wgUser->isAllowed( 'deleterevision' ) ) {
                        $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
                        if( $revision->isCurrent() ) {
                        // We don't handle top deleted edits too well
-                               $cdel = wfMsgHtml('rev-delundel');      
+                               $cdel = wfMsgHtml('rev-delundel');
                        } else if( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) {
                        // If revision was hidden from sysops
-                               $cdel = wfMsgHtml('rev-delundel');      
+                               $cdel = wfMsgHtml('rev-delundel');
                        } else {
                                $cdel = $sk->makeKnownLinkObj( $revdel,
                                        wfMsgHtml('rev-delundel'),
@@ -2741,7 +2854,7 @@ class Article {
                $infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-'
                        ? 'revision-info-current'
                        : '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\">" . $cdel . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
@@ -2802,9 +2915,9 @@ class Article {
                $printable = $wgRequest->getVal( 'printable' );
                $page      = $wgRequest->getVal( 'page' );
 
-               //check for non-standard user language; this covers uselang, 
+               //check for non-standard user language; this covers uselang,
                //and extensions for auto-detecting user language.
-               $ulang     = $wgLang->getCode(); 
+               $ulang     = $wgLang->getCode();
                $clang     = $wgContLang->getCode();
 
                $cacheable = $wgUseFileCache
@@ -2885,6 +2998,8 @@ class Article {
                $revision->insertOn( $dbw );
                $this->updateRevisionOn( $dbw, $revision );
                $dbw->commit();
+               
+               wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, false) );
 
                wfProfileOut( __METHOD__ );
        }
@@ -2990,7 +3105,7 @@ class Article {
                }
                $other->invalidateCache();
                $other->purgeSquid();
-               
+
                $title->touchLinks();
                $title->purgeSquid();
 
@@ -3000,13 +3115,20 @@ class Article {
                        @unlink( $cm->fileCacheName() );
                }
 
-               if( $title->getNamespace() == NS_MEDIAWIKI) {
+               # Messages
+               if( $title->getNamespace() == NS_MEDIAWIKI ) {
                        $wgMessageCache->replace( $title->getDBkey(), false );
                }
+               # Images
                if( $title->getNamespace() == NS_IMAGE ) {
                        $update = new HTMLCacheUpdate( $title, 'imagelinks' );
                        $update->doUpdate();
                }
+               # User talk pages
+               if( $title->getNamespace() == NS_USER_TALK ) {
+                       $user = User::newFromName( $title->getText(), false );
+                       $user->setNewtalk( false );
+               }
        }
 
        /**
@@ -3020,7 +3142,7 @@ class Article {
 
                // Invalidate the caches of all pages which redirect here
                $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
-               
+
                # Purge squid for this page only
                $title->purgeSquid();
 
@@ -3184,7 +3306,7 @@ class Article {
                $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', 
+                       array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
                                'page_namespace' => NS_CATEGORY, 'page_title=cl_to'),
                        'Article:getHiddenCategories' );
                if ( false !== $res ) {
@@ -3198,41 +3320,6 @@ class Article {
                return $result;
        }
 
-       /**
-        * Return an auto-generated summary if the text provided is a redirect.
-        *
-        * @param  string $text The wikitext to check
-        * @return string '' or an appropriate summary
-        */
-       public static function getRedirectAutosummary( $text ) {
-               $rt = Title::newFromRedirect( $text );
-               if( is_object( $rt ) )
-                       return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
-               else
-                       return '';
-       }
-
-       /**
-        * Return an auto-generated summary if the new text is much shorter than
-        * the old text.
-        *
-        * @param  string $oldtext The previous text of the page
-        * @param  string $text    The submitted text of the page
-        * @return string An appropriate autosummary, or an empty string.
-        */
-       public static function getBlankingAutosummary( $oldtext, $text ) {
-               if ($oldtext!='' && $text=='') {
-                       return wfMsgForContent('autosumm-blank');
-               } elseif (strlen($oldtext) > 10 * strlen($text) && strlen($text) < 500) {
-                       #Removing more than 90% of the article
-                       global $wgContLang;
-                       $truncatedtext = $wgContLang->truncate($text, max(0, 200 - strlen(wfMsgForContent('autosumm-replace'))), '...');
-                       return wfMsgForContent('autosumm-replace', $truncatedtext);
-               } else {
-                       return '';
-               }
-       }
-
        /**
        * Return an applicable autosummary if one exists for the given edit.
        * @param string $oldtext The previous text of the page.
@@ -3241,38 +3328,42 @@ class Article {
        * @return string An appropriate autosummary, or an empty string.
        */
        public static function getAutosummary( $oldtext, $newtext, $flags ) {
+               # Decide what kind of autosummary is needed.
 
-               # This code is UGLY UGLY UGLY.
-               # Somebody PLEASE come up with a more elegant way to do it.
-
-               #Redirect autosummaries
-               $summary = self::getRedirectAutosummary( $newtext );
-
-               if ($summary)
-                       return $summary;
-
-               #Blanking autosummaries
-               if (!($flags & EDIT_NEW))
-                       $summary = self::getBlankingAutosummary( $oldtext, $newtext );
-
-               if ($summary)
-                       return $summary;
+               # Redirect autosummaries
+               $rt = Title::newFromRedirect( $newtext );
+               if( is_object( $rt ) ) {
+                       return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
+               }
 
-               #New page autosummaries
-               if ($flags & EDIT_NEW && strlen($newtext)) {
-                       #If they're making a new article, give its text, truncated, in the summary.
+               # New page autosummaries
+               if( $flags & EDIT_NEW && strlen( $newtext ) ) {
+                       # If they're making a new article, give its text, truncated, in the summary.
                        global $wgContLang;
                        $truncatedtext = $wgContLang->truncate(
                                str_replace("\n", ' ', $newtext),
                                max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new') ) ),
                                '...' );
-                       $summary = wfMsgForContent( 'autosumm-new', $truncatedtext );
+                       return wfMsgForContent( 'autosumm-new', $truncatedtext );
                }
 
-               if ($summary)
-                       return $summary;
+               # Blanking autosummaries
+               if( $oldtext != '' && $newtext == '' ) {
+                       return wfMsgForContent('autosumm-blank');
+               } elseif( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500) {
+                       # Removing more than 90% of the article
+                       global $wgContLang;
+                       $truncatedtext = $wgContLang->truncate(
+                               $newtext,
+                               max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ),
+                               '...'
+                       );
+                       return wfMsgForContent( 'autosumm-replace', $truncatedtext );
+               }
 
-               return $summary;
+               # If we reach this point, there's no applicable autosummary for our case, so our
+               # autosummary is empty.
+               return '';
        }
 
        /**
@@ -3294,7 +3385,7 @@ class Article {
                $popts->setTidy(false);
                $popts->enableLimitReport( false );
                if ( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) {
-                       $parserCache =& ParserCache::singleton();
+                       $parserCache = ParserCache::singleton();
                        $parserCache->save( $parserOutput, $this, $wgUser );
                }