Refactored parser output handling slightly, and added a hook function, to allow exten...
[lhc/web/wiklou.git] / includes / Article.php
index 945f5c5..368210f 100644 (file)
@@ -8,10 +8,6 @@
  * Need the CacheManager to be loaded
  */
 require_once( 'CacheManager.php' );
-require_once( 'Revision.php' );
-
-$wgArticleCurContentFields = false;
-$wgArticleOldContentFields = false;
 
 /**
  * Class representing a MediaWiki article and history.
@@ -30,10 +26,8 @@ class Article {
        var $mContent;                  //!<
        var $mContentLoaded;    //!<
        var $mCounter;                  //!<
-       var $mFileCache;                //!<
        var $mForUpdate;                //!<
        var $mGoodAdjustment;   //!<
-       var $mId;                               //!<
        var $mLatest;                   //!<
        var $mMinorEdit;                //!<
        var $mOldId;                    //!<
@@ -41,7 +35,6 @@ class Article {
        var $mRedirectUrl;              //!<
        var $mRevIdFetched;             //!<
        var $mRevision;                 //!<
-       var $mTable;                    //!<
        var $mTimestamp;                //!<
        var $mTitle;                    //!<
        var $mTotalAdjustment;  //!<
@@ -129,7 +122,7 @@ class Article {
                $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded
                $this->mRedirectedFrom = null; # Title object if set
                $this->mUserText =
-               $this->mTimestamp = $this->mComment = $this->mFileCache = '';
+               $this->mTimestamp = $this->mComment = '';
                $this->mGoodAdjustment = $this->mTotalAdjustment = 0;
                $this->mTouched = '19700101000000';
                $this->mForUpdate = false;
@@ -153,27 +146,10 @@ class Article {
        function getContent() {
                global $wgRequest, $wgUser, $wgOut;
 
-               # Get variables from query string :P
-               $action = $wgRequest->getText( 'action', 'view' );
-               $section = $wgRequest->getText( 'section' );
-               $preload = $wgRequest->getText( 'preload' );
-
-               $fname =  'Article::getContent';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
 
                if ( 0 == $this->getID() ) {
-                       if ( 'edit' == $action ) {
-                               wfProfileOut( $fname );
-
-                               # If requested, preload some text.
-                               $text=$this->getPreloadedText($preload);
-
-                               # We used to put MediaWiki:Newarticletext here if
-                               # $text was empty at this point.
-                               # This is now shown above the edit box instead.
-                               return $text;
-                       }
-                       wfProfileOut( $fname );
+                       wfProfileOut( __METHOD__ );
                        $wgOut->setRobotpolicy( 'noindex,nofollow' );
 
                        if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
@@ -185,51 +161,11 @@ class Article {
                        return "<div class='noarticletext'>$ret</div>";
                } else {
                        $this->loadContent();
-                       if($action=='edit') {
-                               if($section!='') {
-                                       if($section=='new') {
-                                               wfProfileOut( $fname );
-                                               $text=$this->getPreloadedText($preload);
-                                               return $text;
-                                       }
-
-                                       # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
-                                       # comments to be stripped as well)
-                                       $rv=$this->getSection($this->mContent,$section);
-                                       wfProfileOut( $fname );
-                                       return $rv;
-                               }
-                       }
-                       wfProfileOut( $fname );
+                       wfProfileOut( __METHOD__ );
                        return $this->mContent;
                }
        }
 
-       /**
-        * Get the contents of a page from its title and remove includeonly tags
-        *
-        * @param $preload String: the title of the page.
-        * @return string The contents of the page.
-        */
-       function getPreloadedText($preload) {
-               if ( $preload === '' )
-                       return '';
-               else {
-                       $preloadTitle = Title::newFromText( $preload );
-                       if ( isset( $preloadTitle ) && $preloadTitle->userCanRead() ) {
-                               $rev=Revision::newFromTitle($preloadTitle);
-                               if ( is_object( $rev ) ) {
-                                       $text = $rev->getText();
-                                       // TODO FIXME: AAAAAAAAAAA, this shouldn't be implementing
-                                       // its own mini-parser! -ævar
-                                       $text = preg_replace( '~</?includeonly>~', '', $text );
-                                       return $text;
-                               } else
-                                       return '';
-                       }
-               }
-       }
-
        /**
         * This function returns the text of a section, specified by a number ($section).
         * A section is text under a heading like == Heading == or \<h1\>Heading\</h1\>, or
@@ -240,64 +176,11 @@ class Article {
         * @param $text String: text to look in
         * @param $section Integer: section number
         * @return string text of the requested section
+        * @deprecated
         */
        function getSection($text,$section) {
-
-               # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
-               # comments to be stripped as well)
-               $striparray=array();
-               $parser=new Parser();
-               $parser->mOutputType=OT_WIKI;
-               $parser->mOptions = new ParserOptions();
-               $striptext=$parser->strip($text, $striparray, true);
-
-               # now that we can be sure that no pseudo-sections are in the source,
-               # split it up by section
-               $secs =
-                 preg_split(
-                 '/(^=+.+?=+|^<h[1-6].*?>.*?<\/h[1-6].*?>)(?!\S)/mi',
-                 $striptext, -1,
-                 PREG_SPLIT_DELIM_CAPTURE);
-               if($section==0) {
-                       $rv=$secs[0];
-               } else {
-                       $headline=$secs[$section*2-1];
-                       preg_match( '/^(=+).+?=+|^<h([1-6]).*?>.*?<\/h[1-6].*?>(?!\S)/mi',$headline,$matches);
-                       $hlevel=$matches[1];
-
-                       # translate wiki heading into level
-                       if(strpos($hlevel,'=')!==false) {
-                               $hlevel=strlen($hlevel);
-                       }
-
-                       $rv=$headline. $secs[$section*2];
-                       $count=$section+1;
-
-                       $break=false;
-                       while(!empty($secs[$count*2-1]) && !$break) {
-
-                               $subheadline=$secs[$count*2-1];
-                               preg_match( '/^(=+).+?=+|^<h([1-6]).*?>.*?<\/h[1-6].*?>(?!\S)/mi',$subheadline,$matches);
-                               $subhlevel=$matches[1];
-                               if(strpos($subhlevel,'=')!==false) {
-                                       $subhlevel=strlen($subhlevel);
-                               }
-                               if($subhlevel > $hlevel) {
-                                       $rv.=$subheadline.$secs[$count*2];
-                               }
-                               if($subhlevel <= $hlevel) {
-                                       $break=true;
-                               }
-                               $count++;
-
-                       }
-               }
-               # reinsert stripped tags
-               $rv=$parser->unstrip($rv,$striparray);
-               $rv=$parser->unstripNoWiki($rv,$striparray);
-               $rv=trim($rv);
-               return $rv;
-
+               global $wgParser;
+               return $wgParser->getSection( $text, $section );
        }
 
        /**
@@ -340,6 +223,7 @@ class Article {
                        # unused:
                        # $lastid = $oldid;
                }
+
                if ( !$oldid ) {
                        $oldid = 0;
                }
@@ -458,7 +342,6 @@ class Article {
                }
 
                $dbr =& $this->getDB();
-               $fname = 'Article::fetchContent';
 
                # Pre-fill content with error message so that if something
                # fails we'll have something telling us what we intended.
@@ -471,12 +354,12 @@ class Article {
                if( $oldid ) {
                        $revision = Revision::newFromId( $oldid );
                        if( is_null( $revision ) ) {
-                               wfDebug( "$fname failed to retrieve specified revision, id $oldid\n" );
+                               wfDebug( __METHOD__." failed to retrieve specified revision, id $oldid\n" );
                                return false;
                        }
                        $data = $this->pageDataFromId( $dbr, $revision->getPage() );
                        if( !$data ) {
-                               wfDebug( "$fname failed to get page data linked to revision id $oldid\n" );
+                               wfDebug( __METHOD__." failed to get page data linked to revision id $oldid\n" );
                                return false;
                        }
                        $this->mTitle = Title::makeTitle( $data->page_namespace, $data->page_title );
@@ -485,21 +368,21 @@ class Article {
                        if( !$this->mDataLoaded ) {
                                $data = $this->pageDataFromTitle( $dbr, $this->mTitle );
                                if( !$data ) {
-                                       wfDebug( "$fname failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" );
+                                       wfDebug( __METHOD__." failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" );
                                        return false;
                                }
                                $this->loadPageData( $data );
                        }
                        $revision = Revision::newFromId( $this->mLatest );
                        if( is_null( $revision ) ) {
-                               wfDebug( "$fname failed to retrieve current page, rev_id {$data->page_latest}\n" );
+                               wfDebug( __METHOD__." failed to retrieve current page, rev_id {$data->page_latest}\n" );
                                return false;
                        }
                }
 
                // FIXME: Horrible, horrible! This content-loading interface just plain sucks.
                // We should instead work with the Revision object when we need it...
-               $this->mContent = $revision->userCan( MW_REV_DELETED_TEXT ) ? $revision->getRawText() : "";
+               $this->mContent = $revision->userCan( Revision::DELETED_TEXT ) ? $revision->getRawText() : "";
                //$this->mContent   = $revision->getText();
 
                $this->mUser      = $revision->getUser();
@@ -596,11 +479,11 @@ class Article {
         * @return bool
         */
        function isCountable( $text ) {
-               global $wgUseCommaCount;
+               global $wgUseCommaCount, $wgContentNamespaces;
 
                $token = $wgUseCommaCount ? ',' : '[[';
                return
-                       $this->mTitle->getNamespace() == NS_MAIN
+                       array_search( $this->mTitle->getNamespace(), $wgContentNamespaces ) !== false
                        && ! $this->isRedirect( $text )
                        && in_string( $token, $text );
        }
@@ -695,8 +578,6 @@ class Article {
         * @param $offset Integer: default 0.
         */
        function getContributors($limit = 0, $offset = 0) {
-               $fname = 'Article::getContributors';
-
                # XXX: this is expensive; cache this info somewhere.
 
                $title = $this->mTitle;
@@ -719,7 +600,7 @@ class Article {
                if ($limit > 0) { $sql .= ' LIMIT '.$limit; }
                $sql .= ' '. $this->getSelectOptions();
 
-               $res = $dbr->query($sql, $fname);
+               $res = $dbr->query($sql, __METHOD__);
 
                while ( $line = $dbr->fetchObject( $res ) ) {
                        $contribs[] = array($line->rev_user, $line->rev_user_text, $line->user_real_name);
@@ -736,19 +617,21 @@ class Article {
        function view() {
                global $wgUser, $wgOut, $wgRequest, $wgContLang;
                global $wgEnableParserCache, $wgStylePath, $wgUseRCPatrol, $wgParser;
-               global $wgUseTrackbacks;
+               global $wgUseTrackbacks, $wgNamespaceRobotPolicies;
                $sk = $wgUser->getSkin();
 
-               $fname = 'Article::view';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
+
                $parserCache =& ParserCache::singleton();
+               $ns = $this->mTitle->getNamespace(); # shortcut
+               
                # Get variables from query string
                $oldid = $this->getOldID();
 
                # getOldID may want us to redirect somewhere else
                if ( $this->mRedirectUrl ) {
                        $wgOut->redirect( $this->mRedirectUrl );
-                       wfProfileOut( $fname );
+                       wfProfileOut( __METHOD__ );
                        return;
                }
 
@@ -757,7 +640,13 @@ class Article {
                $rdfrom = $wgRequest->getVal( 'rdfrom' );
 
                $wgOut->setArticleFlag( true );
-               $wgOut->setRobotpolicy( 'index,follow' );
+               if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
+                       $policy = $wgNamespaceRobotPolicies[$ns];
+               } else {
+                       $policy = 'index,follow';
+               }
+               $wgOut->setRobotpolicy( $policy );
+
                # If we got diff and oldid in the query, we want to see a
                # diff page instead of the article.
 
@@ -774,7 +663,7 @@ class Article {
                                # Run view updates for current revision only
                                $this->viewUpdates();
                        }
-                       wfProfileOut( $fname );
+                       wfProfileOut( __METHOD__ );
                        return;
                }
                
@@ -782,16 +671,17 @@ class Article {
                        $wgOut->setETag($parserCache->getETag($this, $wgUser));
 
                        if( $wgOut->checkLastModified( $this->mTouched ) ){
-                               wfProfileOut( $fname );
+                               wfProfileOut( __METHOD__ );
                                return;
                        } else if ( $this->tryFileCache() ) {
                                # tell wgOut that output is taken care of
                                $wgOut->disable();
                                $this->viewUpdates();
-                               wfProfileOut( $fname );
+                               wfProfileOut( __METHOD__ );
                                return;
                        }
                }
+
                # Should the parser cache be used?
                $pcache = $wgEnableParserCache &&
                        intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 &&
@@ -829,6 +719,7 @@ class Article {
                $outputDone = false;
                if ( $pcache ) {
                        if ( $wgOut->tryParserCache( $this, $wgUser ) ) {
+                               wfRunHooks( 'ArticleViewHeader', array( &$this ) );
                                $outputDone = true;
                        }
                }
@@ -860,8 +751,8 @@ class Article {
                                        // FIXME: This would be a nice place to load the 'no such page' text.
                                } else {
                                        $this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid );
-                                       if( $this->mRevision->isDeleted( MW_REV_DELETED_TEXT ) ) {
-                                               if( !$this->mRevision->userCan( MW_REV_DELETED_TEXT ) ) {
+                                       if( $this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
+                                               if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) {
                                                        $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
                                                        $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
                                                        return;
@@ -884,7 +775,7 @@ class Article {
                        # wrap user css and user js in pre and don't parse
                        # XXX: use $this->mTitle->usCssJsSubpage() when php is fixed/ a workaround is found
                        if (
-                               $this->mTitle->getNamespace() == NS_USER &&
+                               $ns == NS_USER &&
                                preg_match('/\\/[\\w]+\\.(css|js)$/', $this->mTitle->getDBkey())
                        ) {
                                $wgOut->addWikiText( wfMsg('clearyourcache'));
@@ -893,7 +784,8 @@ class Article {
                                # Display redirect
                                $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
                                $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png';
-                               if( !$wasRedirected ) {
+                               # Don't overwrite the subtitle if this was an old revision
+                               if( !$wasRedirected && $this->isCurrent() ) {
                                        $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) );
                                }
                                $targetUrl = $rt->escapeLocalURL();
@@ -911,16 +803,15 @@ class Article {
                                $wgOut->addPrimaryWikiText( $text, $this );
                        } else {
                                # Display content, don't attempt to save to parser cache
-
                                # Don't show section-edit links on old revisions... this way lies madness.
                                if( !$this->isCurrent() ) {
-                                       $oldEditSectionSetting = $wgOut->mParserOptions->setEditSection( false );
+                                       $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
                                }
                                # Display content and don't save to parser cache
                                $wgOut->addPrimaryWikiText( $text, $this, false );
 
                                if( !$this->isCurrent() ) {
-                                       $wgOut->mParserOptions->setEditSection( $oldEditSectionSetting );
+                                       $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
                                }
                        }
                }
@@ -929,9 +820,9 @@ class Article {
                if( empty( $t ) ) {
                        $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
                }
-               
+
                # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
-               if( $this->mTitle->getNamespace() == NS_USER_TALK &&
+               if( $ns == NS_USER_TALK &&
                        User::isIP( $this->mTitle->getText() ) ) {
                        $wgOut->addWikiText( wfMsg('anontalkpagetext') );
                }
@@ -953,7 +844,7 @@ class Article {
                        $this->addTrackbacks();
 
                $this->viewUpdates();
-               wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
        }
 
        function addTrackbacks() {
@@ -996,7 +887,7 @@ class Article {
                }
 
                if ((!$wgUser->isAllowed('delete'))) {
-                       $wgOut->sysopRequired();
+                       $wgOut->permissionRequired( 'delete' );
                        return;
                }
 
@@ -1076,8 +967,7 @@ class Article {
         * @private
         */
        function insertOn( &$dbw, $restrictions = '' ) {
-               $fname = 'Article::insertOn';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
 
                $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
                $dbw->insert( 'page', array(
@@ -1092,12 +982,12 @@ class Article {
                        'page_touched'      => $dbw->timestamp(),
                        'page_latest'       => 0, # Fill this in shortly...
                        'page_len'          => 0, # Fill this in shortly...
-               ), $fname );
+               ), __METHOD__ );
                $newid = $dbw->insertId();
 
                $this->mTitle->resetArticleId( $newid );
 
-               wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
                return $newid;
        }
 
@@ -1115,8 +1005,7 @@ class Article {
         * @private
         */
        function updateRevisionOn( &$dbw, $revision, $lastRevision = null ) {
-               $fname = 'Article::updateToRevision';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
 
                $conditions = array( 'page_id' => $this->getId() );
                if( !is_null( $lastRevision ) ) {
@@ -1134,9 +1023,9 @@ class Article {
                                'page_len'         => strlen( $text ),
                        ),
                        $conditions,
-                       $fname );
+                       __METHOD__ );
 
-               wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
                return ( $dbw->affectedRows() != 0 );
        }
 
@@ -1148,8 +1037,7 @@ class Article {
         * @param Revision $revision
         */
        function updateIfNewerOn( &$dbw, $revision ) {
-               $fname = 'Article::updateIfNewerOn';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
 
                $row = $dbw->selectRow(
                        array( 'revision', 'page' ),
@@ -1157,10 +1045,10 @@ class Article {
                        array(
                                'page_id' => $this->getId(),
                                'page_latest=rev_id' ),
-                       $fname );
+                       __METHOD__ );
                if( $row ) {
                        if( wfTimestamp(TS_MW, $row->rev_timestamp) >= $revision->getTimestamp() ) {
-                               wfProfileOut( $fname );
+                               wfProfileOut( __METHOD__ );
                                return false;
                        }
                        $prev = $row->rev_id;
@@ -1170,116 +1058,19 @@ class Article {
                }
 
                $ret = $this->updateRevisionOn( $dbw, $revision, $prev );
-               wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
                return $ret;
        }
 
-       /**
-        * Insert a new article into the database
-        * @private
-        */
-       function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) {
-               global $wgUser;
-
-               $fname = 'Article::insertNewArticle';
-               wfProfileIn( $fname );
-
-               if( !wfRunHooks( 'ArticleSave', array( &$this, &$wgUser, &$text,
-                       &$summary, &$isminor, &$watchthis, NULL ) ) ) {
-                       wfDebug( "$fname: ArticleSave hook aborted save!\n" );
-                       wfProfileOut( $fname );
-                       return false;
-               }
-
-               $ns = $this->mTitle->getNamespace();
-               $ttl = $this->mTitle->getDBkey();
-
-               # If this is a comment, add the summary as headline
-               if($comment && $summary!="") {
-                       $text="== {$summary} ==\n\n".$text;
-               }
-               $text = $this->preSaveTransform( $text );
-
-
-               # Set statistics members
-               # We work out if it's countable after PST to avoid counter drift 
-               # when articles are created with {{subst:}}
-               $this->mGoodAdjustment = (int)$this->isCountable( $text );
-               $this->mTotalAdjustment = 1;
-
-               /* Silently ignore minoredit if not allowed */
-               $isminor = $isminor && $wgUser->isAllowed('minoredit');
-               $now = wfTimestampNow();
-
-               $dbw =& wfGetDB( DB_MASTER );
-
-               # Add the page record; stake our claim on this title!
-               $newid = $this->insertOn( $dbw );
-
-               # Save the revision text...
-               $revision = new Revision( array(
-                       'page'       => $newid,
-                       'comment'    => $summary,
-                       'minor_edit' => $isminor,
-                       'text'       => $text
-                       ) );
-               $revisionId = $revision->insertOn( $dbw );
-
-               $this->mTitle->resetArticleID( $newid );
-
-               # Update the page record with revision data
-               $this->updateRevisionOn( $dbw, $revision, 0 );
-
-               Article::onArticleCreate( $this->mTitle );
-               if(!$suppressRC) {
-                       require_once( 'RecentChange.php' );
-                       $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, 'default',
-                         '', strlen( $text ), $revisionId );
-                       # Mark as patrolled if the user can and has the option set
-                       if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) {
-                               RecentChange::markPatrolled( $rcid );
-                       }
-               }
-
-               if ($watchthis) {
-                       if(!$this->mTitle->userIsWatching()) $this->doWatch();
-               } else {
-                       if ( $this->mTitle->userIsWatching() ) {
-                               $this->doUnwatch();
-                       }
-               }
-
-               # The talk page isn't in the regular link tables, so we need to update manually:
-               $talkns = $ns ^ 1; # talk -> normal; normal -> talk
-               $dbw->update( 'page',
-                       array( 'page_touched' => $dbw->timestamp($now) ),
-                       array( 'page_namespace' => $talkns,
-                              'page_title' => $ttl ),
-                       $fname );
-
-               # standard deferred updates
-               $this->editUpdates( $text, $summary, $isminor, $now, $revisionId );
-
-               $oldid = 0; # new article
-               $this->showArticle( $text, wfMsg( 'newarticle' ), false, $isminor, $now, $summary, $oldid );
-
-               wfRunHooks( 'ArticleInsertComplete', array( &$this, &$wgUser, $text,
-                       $summary, $isminor,
-                       $watchthis, NULL ) );
-               wfRunHooks( 'ArticleSaveComplete', array( &$this, &$wgUser, $text,
-                       $summary, $isminor,
-                       $watchthis, NULL ) );
-               wfProfileOut( $fname );
-       }
-
        /**
         * @return string Complete article text, or null if error
         */
        function replaceSection($section, $text, $summary = '', $edittime = NULL) {
-               $fname = 'Article::replaceSection';
-               wfProfileIn( $fname );
-
-               if ($section != '') {
+               wfProfileIn( __METHOD__ );
+               
+               if( $section == '' ) {
+                       // Whole-page edit; let the text through unmolested.
+               } else {
                        if( is_null( $edittime ) ) {
                                $rev = Revision::newFromTitle( $this->mTitle );
                        } else {
@@ -1297,256 +1088,316 @@ class Article {
                                if($summary) $subject="== {$summary} ==\n\n";
                                $text=$oldtext."\n\n".$subject.$text;
                        } else {
+                               global $wgParser;
+                               $text = $wgParser->replaceSection( $oldtext, $section, $text );
+                       }
+               }
 
-                               # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML
-                               # comments to be stripped as well)
-                               $striparray=array();
-                               $parser=new Parser();
-                               $parser->mOutputType=OT_WIKI;
-                               $parser->mOptions = new ParserOptions();
-                               $oldtext=$parser->strip($oldtext, $striparray, true);
-
-                               # now that we can be sure that no pseudo-sections are in the source,
-                               # split it up
-                               # Unfortunately we can't simply do a preg_replace because that might
-                               # replace the wrong section, so we have to use the section counter instead
-                               $secs=preg_split('/(^=+.+?=+|^<h[1-6].*?>.*?<\/h[1-6].*?>)(?!\S)/mi',
-                                 $oldtext,-1,PREG_SPLIT_DELIM_CAPTURE);
-                               $secs[$section*2]=$text."\n\n"; // replace with edited
-
-                               # section 0 is top (intro) section
-                               if($section!=0) {
-
-                                       # headline of old section - we need to go through this section
-                                       # to determine if there are any subsections that now need to
-                                       # be erased, as the mother section has been replaced with
-                                       # the text of all subsections.
-                                       $headline=$secs[$section*2-1];
-                                       preg_match( '/^(=+).+?=+|^<h([1-6]).*?>.*?<\/h[1-6].*?>(?!\S)/mi',$headline,$matches);
-                                       $hlevel=$matches[1];
-
-                                       # determine headline level for wikimarkup headings
-                                       if(strpos($hlevel,'=')!==false) {
-                                               $hlevel=strlen($hlevel);
-                                       }
+               wfProfileOut( __METHOD__ );
+               return $text;
+       }
 
-                                       $secs[$section*2-1]=''; // erase old headline
-                                       $count=$section+1;
-                                       $break=false;
-                                       while(!empty($secs[$count*2-1]) && !$break) {
-
-                                               $subheadline=$secs[$count*2-1];
-                                               preg_match(
-                                                '/^(=+).+?=+|^<h([1-6]).*?>.*?<\/h[1-6].*?>(?!\S)/mi',$subheadline,$matches);
-                                               $subhlevel=$matches[1];
-                                               if(strpos($subhlevel,'=')!==false) {
-                                                       $subhlevel=strlen($subhlevel);
-                                               }
-                                               if($subhlevel > $hlevel) {
-                                                       // erase old subsections
-                                                       $secs[$count*2-1]='';
-                                                       $secs[$count*2]='';
-                                               }
-                                               if($subhlevel <= $hlevel) {
-                                                       $break=true;
-                                               }
-                                               $count++;
+       /**
+        * @deprecated use Article::doEdit()
+        */
+       function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) {
+               $flags = EDIT_NEW | EDIT_DEFER_UPDATES |
+                       ( $isminor ? EDIT_MINOR : 0 ) |
+                       ( $suppressRC ? EDIT_SUPPRESS_RC : 0 );
 
-                                       }
+               # If this is a comment, add the summary as headline
+               if ( $comment && $summary != "" ) {
+                       $text = "== {$summary} ==\n\n".$text;
+               }
+               
+               $this->doEdit( $text, $summary, $flags );
+
+               $dbw =& wfGetDB( DB_MASTER );
+               if ($watchthis) {
+                       if (!$this->mTitle->userIsWatching()) {
+                               $dbw->begin();
+                               $this->doWatch();
+                               $dbw->commit();
+                       }
+               } else {
+                       if ( $this->mTitle->userIsWatching() ) {
+                               $dbw->begin();
+                               $this->doUnwatch();
+                               $dbw->commit();
+                       }
+               }
+               $this->doRedirect( $this->isRedirect( $text ) );
+       }
 
+       /**
+        * @deprecated use Article::doEdit()
+        */
+       function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) {
+               $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES |
+                       ( $minor ? EDIT_MINOR : 0 ) |
+                       ( $forceBot ? EDIT_FORCE_BOT : 0 );
+
+               $good = $this->doEdit( $text, $summary, $flags );
+               if ( $good ) {
+                       $dbw =& wfGetDB( DB_MASTER );
+                       if ($watchthis) {
+                               if (!$this->mTitle->userIsWatching()) {
+                                       $dbw->begin();
+                                       $this->doWatch();
+                                       $dbw->commit();
+                               }
+                       } else {
+                               if ( $this->mTitle->userIsWatching() ) {
+                                       $dbw->begin();
+                                       $this->doUnwatch();
+                                       $dbw->commit();
                                }
-                               $text=join('',$secs);
-                               # reinsert the stuff that we stripped out earlier
-                               $text=$parser->unstrip($text,$striparray);
-                               $text=$parser->unstripNoWiki($text,$striparray);
                        }
 
+                       $this->doRedirect( $this->isRedirect( $text ), $sectionanchor );
                }
-               wfProfileOut( $fname );
-               return $text;
+               return $good;
        }
 
        /**
-        * Change an existing article. Puts the previous version back into the old table, updates RC
-        * and all necessary caches, mostly via the deferred update array.
+        * Article::doEdit()
+        *
+        * Change an existing article or create a new article. Updates RC and all necessary caches, 
+        * optionally via the deferred update array.
+        *
+        * $wgUser must be set before calling this function.
         *
-        * It is possible to call this function from a command-line script, but note that you should
-        * first set $wgUser, and clean up $wgDeferredUpdates after each edit.
+        * @param string $text New text
+        * @param string $summary Edit summary
+        * @param integer $flags bitfield:
+        *      EDIT_NEW
+        *          Article is known or assumed to be non-existent, create a new one
+        *      EDIT_UPDATE
+        *          Article is known or assumed to be pre-existing, update it
+        *      EDIT_MINOR
+        *          Mark this edit minor, if the user is allowed to do so
+        *      EDIT_SUPPRESS_RC
+        *          Do not log the change in recentchanges
+        *      EDIT_FORCE_BOT
+        *          Mark the edit a "bot" edit regardless of user rights
+        *      EDIT_DEFER_UPDATES
+        *          Defer some of the updates until the end of index.php
+        * 
+        * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. 
+        * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If 
+        * 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.
+        *
+        * @return bool success
         */
-       function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) {
-               global $wgUser, $wgDBtransactions, $wgUseSquid;
-               global $wgPostCommitUpdateList, $wgUseFileCache;
+       function doEdit( $text, $summary, $flags = 0 ) {
+               global $wgUser, $wgDBtransactions;
 
-               $fname = 'Article::updateArticle';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
                $good = true;
 
+               if ( !($flags & EDIT_NEW) && !($flags & EDIT_UPDATE) ) {
+                       $aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE );
+                       if ( $aid ) {
+                               $flags |= EDIT_UPDATE;
+                       } else {
+                               $flags |= EDIT_NEW;
+                       }
+               }
+
                if( !wfRunHooks( 'ArticleSave', array( &$this, &$wgUser, &$text,
-                       &$summary, &$minor,
-                       &$watchthis, &$sectionanchor ) ) ) {
-                       wfDebug( "$fname: ArticleSave hook aborted save!\n" );
-                       wfProfileOut( $fname );
+                       &$summary, $flags & EDIT_MINOR,
+                       null, null, &$flags ) ) ) 
+               {
+                       wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
+                       wfProfileOut( __METHOD__ );
                        return false;
                }
 
-               $isminor = $minor && $wgUser->isAllowed('minoredit');
+               # Silently ignore EDIT_MINOR if not allowed
+               $isminor = ( $flags & EDIT_MINOR ) && $wgUser->isAllowed('minoredit');
+               $bot = $wgUser->isAllowed( 'bot' ) || ( $flags & EDIT_FORCE_BOT );
 
                $text = $this->preSaveTransform( $text );
+
                $dbw =& wfGetDB( DB_MASTER );
                $now = wfTimestampNow();
+               
+               if ( $flags & EDIT_UPDATE ) {
+                       # Update article, but only if changed.
 
-               # Update article, but only if changed.
+                       # Make sure the revision is either completely inserted or not inserted at all
+                       if( !$wgDBtransactions ) {
+                               $userAbort = ignore_user_abort( true );
+                       }
 
-               # It's important that we either rollback or complete, otherwise an attacker could
-               # overwrite cur entries by sending precisely timed user aborts. Random bored users
-               # could conceivably have the same effect, especially if cur is locked for long periods.
-               if( !$wgDBtransactions ) {
-                       $userAbort = ignore_user_abort( true );
-               }
+                       $oldtext = $this->getContent();
+                       $oldsize = strlen( $oldtext );
+                       $newsize = strlen( $text );
+                       $lastRevision = 0;
+                       $revisionId = 0;
+
+                       if ( 0 != strcmp( $text, $oldtext ) ) {
+                               $this->mGoodAdjustment = (int)$this->isCountable( $text )
+                                 - (int)$this->isCountable( $oldtext );
+                               $this->mTotalAdjustment = 0;
+
+                               $lastRevision = $dbw->selectField(
+                                       'page', 'page_latest', array( 'page_id' => $this->getId() ) );
 
-               $oldtext = $this->getContent();
-               $oldsize = strlen( $oldtext );
-               $newsize = strlen( $text );
-               $lastRevision = 0;
-               $revisionId = 0;
+                               if ( !$lastRevision ) {
+                                       # Article gone missing
+                                       wfDebug( __METHOD__.": EDIT_UPDATE specified but article doesn't exist\n" );
+                                       wfProfileOut( __METHOD__ );
+                                       return false;
+                               }
+                               
+                               $revision = new Revision( array(
+                                       'page'       => $this->getId(),
+                                       'comment'    => $summary,
+                                       'minor_edit' => $isminor,
+                                       'text'       => $text
+                                       ) );
+
+                               $dbw->begin();
+                               $revisionId = $revision->insertOn( $dbw );
+
+                               # Update page
+                               $ok = $this->updateRevisionOn( $dbw, $revision, $lastRevision );
+
+                               if( !$ok ) {
+                                       /* Belated edit conflict! Run away!! */
+                                       $good = false;
+                                       $dbw->rollback();
+                               } else {
+                                       # Update recentchanges
+                                       if( !( $flags & EDIT_SUPPRESS_RC ) ) {
+                                               $rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary,
+                                                       $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
+                                                       $revisionId );
+                                                       
+                                               # Mark as patrolled if the user can do so and has it set in their options
+                                               if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) {
+                                                       RecentChange::markPatrolled( $rcid );
+                                               }
+                                       }
+                                       $dbw->commit();
+                               }
+                       } else {
+                               // Keep the same revision ID, but do some updates on it
+                               $revisionId = $this->getRevIdFetched();
+                               // Update page_touched, this is usually implicit in the page update
+                               // Other cache updates are done in onArticleEdit()
+                               $this->mTitle->invalidateCache();
+                       }
+
+                       if( !$wgDBtransactions ) {
+                               ignore_user_abort( $userAbort );
+                       }
+
+                       if ( $good ) {
+                               # 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 {
+                       # Create new article
+                       
+                       # Set statistics members
+                       # We work out if it's countable after PST to avoid counter drift 
+                       # when articles are created with {{subst:}}
+                       $this->mGoodAdjustment = (int)$this->isCountable( $text );
+                       $this->mTotalAdjustment = 1;
 
-               if ( 0 != strcmp( $text, $oldtext ) ) {
-                       $this->mGoodAdjustment = (int)$this->isCountable( $text )
-                         - (int)$this->isCountable( $oldtext );
-                       $this->mTotalAdjustment = 0;
-                       $now = wfTimestampNow();
+                       $dbw->begin();
 
-                       $lastRevision = $dbw->selectField(
-                               'page', 'page_latest', array( 'page_id' => $this->getId() ) );
+                       # Add the page record; stake our claim on this title!
+                       # This will fail with a database query exception if the article already exists
+                       $newid = $this->insertOn( $dbw );
 
+                       # Save the revision text...
                        $revision = new Revision( array(
-                               'page'       => $this->getId(),
+                               'page'       => $newid,
                                'comment'    => $summary,
                                'minor_edit' => $isminor,
                                'text'       => $text
                                ) );
-
-                       $dbw->immediateCommit();
-                       $dbw->begin();
                        $revisionId = $revision->insertOn( $dbw );
 
-                       # Update page
-                       $ok = $this->updateRevisionOn( $dbw, $revision, $lastRevision );
+                       $this->mTitle->resetArticleID( $newid );
 
-                       if( !$ok ) {
-                               /* Belated edit conflict! Run away!! */
-                               $good = false;
-                               $dbw->rollback();
-                       } else {
-                               # Update recentchanges and purge cache and whatnot
-                               require_once( 'RecentChange.php' );
-                               $bot = (int)($wgUser->isBot() || $forceBot);
-                               $rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary,
-                                       $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
-                                       $revisionId );
-                                       
-                               # Mark as patrolled if the user can do so and has it set in their options
+                       # Update the page record with revision data
+                       $this->updateRevisionOn( $dbw, $revision, 0 );
+
+                       if( !( $flags & EDIT_SUPPRESS_RC ) ) {
+                               $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot,
+                                 '', strlen( $text ), $revisionId );
+                               # Mark as patrolled if the user can and has the option set
                                if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) {
                                        RecentChange::markPatrolled( $rcid );
                                }
-                                       
-                               $dbw->commit();
-
-                               // Update caches outside the main transaction
-                               Article::onArticleEdit( $this->mTitle );
-                       }
-               } else {
-                       // Keep the same revision ID, but do some updates on it
-                       $revisionId = $this->getRevIdFetched();
-               }
-
-               if( !$wgDBtransactions ) {
-                       ignore_user_abort( $userAbort );
-               }
-
-               if ( $good ) {
-                       if ($watchthis) {
-                               if (!$this->mTitle->userIsWatching()) {
-                                       $dbw->immediateCommit();
-                                       $dbw->begin();
-                                       $this->doWatch();
-                                       $dbw->commit();
-                               }
-                       } else {
-                               if ( $this->mTitle->userIsWatching() ) {
-                                       $dbw->immediateCommit();
-                                       $dbw->begin();
-                                       $this->doUnwatch();
-                                       $dbw->commit();
-                               }
                        }
-                       # standard deferred updates
-                       $this->editUpdates( $text, $summary, $minor, $now, $revisionId );
-
+                       $dbw->commit();
 
-                       $urls = array();
-                       # Invalidate caches of all articles using this article as a template
+                       # Update links, etc.
+                       $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, true );
 
-                       # Template namespace
-                       # Purge all articles linking here
-                       $titles = $this->mTitle->getTemplateLinksTo();
-                       Title::touchArray( $titles );
-                       if ( $wgUseSquid ) {
-                                       foreach ( $titles as $title ) {
-                                               $urls[] = $title->getInternalURL();
-                                       }
-                       }
+                       # Clear caches
+                       Article::onArticleCreate( $this->mTitle );
 
-                       # Squid updates
-                       if ( $wgUseSquid ) {
-                               $urls = array_merge( $urls, $this->mTitle->getSquidURLs() );
-                               $u = new SquidUpdate( $urls );
-                               array_push( $wgPostCommitUpdateList, $u );
-                       }
-
-                       # File cache
-                       if ( $wgUseFileCache ) {
-                               $cm = new CacheManager($this->mTitle);
-                               @unlink($cm->fileCacheName());
-                       }
+                       wfRunHooks( 'ArticleInsertComplete', array( &$this, &$wgUser, $text,
+                               $summary, $flags & EDIT_MINOR,
+                               null, null, &$flags ) );
+               }
 
-                       $this->showArticle( $text, wfMsg( 'updated' ), $sectionanchor, $isminor, $now, $summary, $lastRevision );
+               if ( $good && !( $flags & EDIT_DEFER_UPDATES ) ) {
+                       wfDoUpdates();
                }
+
                wfRunHooks( 'ArticleSaveComplete',
                        array( &$this, &$wgUser, $text,
-                       $summary, $minor,
-                       $watchthis, $sectionanchor ) );
-               wfProfileOut( $fname );
+                       $summary, $flags & EDIT_MINOR,
+                       null, null, &$flags ) );
+               
+               wfProfileOut( __METHOD__ );
                return $good;
        }
 
        /**
-        * After we've either updated or inserted the article, update
-        * the link tables and redirect to the new page.
-        * @todo FIXME some function arguments never used
+        * @deprecated wrapper for doRedirect
         */
        function showArticle( $text, $subtitle , $sectionanchor = '', $me2, $now, $summary, $oldid ) {
-               global $wgOut;
-
-               $fname = 'Article::showArticle';
-               wfProfileIn( $fname );
-
-               # Output the redirect
-               if( $this->isRedirect( $text ) )
-                       $r = 'redirect=no';
-               else
-                       $r = '';
-               $wgOut->redirect( $this->mTitle->getFullURL( $r ).$sectionanchor );
-
-               wfProfileOut( $fname );
+               $this->doRedirect( $this->isRedirect( $text ), $sectionanchor );
        }
 
+       /**
+        * Output a redirect back to the article.
+        * This is typically used after an edit.
+        *
+        * @param boolean $noRedir Add redirect=no
+        * @param string $sectionAnchor section to redirect to, including "#"
+        */
+       function doRedirect( $noRedir = false, $sectionAnchor = '' ) {
+               global $wgOut;
+               if ( $noRedir ) {
+                       $query = 'redirect=no';
+               } else {
+                       $query = '';
+               }
+               $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor );
+       }
+               
        /**
         * Mark this particular edit as patrolled
         */
        function markpatrolled() {
                global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUser;
-               $wgOut->setRobotpolicy( 'noindex,follow' );
+               $wgOut->setRobotpolicy( 'noindex,nofollow' );
 
                # Check RC patrol config. option
                if( !$wgUseRCPatrol ) {
@@ -1563,7 +1414,6 @@ class Article {
                $rcid = $wgRequest->getVal( 'rcid' );
                if ( !is_null ( $rcid ) ) {
                        if( wfRunHooks( 'MarkPatrolled', array( &$rcid, &$wgUser, false ) ) ) {
-                               require_once( 'RecentChange.php' );
                                RecentChange::markPatrolled( $rcid );
                                wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) );
                                $wgOut->setPagetitle( wfMsg( 'markedaspatrolled' ) );
@@ -1573,7 +1423,7 @@ class Article {
                        $wgOut->returnToMain( false, $rcTitle->getPrefixedText() );
                }
                else {
-                       $wgOut->errorpage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
+                       $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
                }
        }
 
@@ -1586,7 +1436,7 @@ class Article {
                global $wgUser, $wgOut;
 
                if ( $wgUser->isAnon() ) {
-                       $wgOut->errorpage( 'watchnologin', 'watchnologintext' );
+                       $wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
                        return;
                }
                if ( wfReadOnly() ) {
@@ -1596,9 +1446,9 @@ class Article {
                
                if( $this->doWatch() ) {
                        $wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
-                       $wgOut->setRobotpolicy( 'noindex,follow' );
+                       $wgOut->setRobotpolicy( 'noindex,nofollow' );
 
-                       $link = $this->mTitle->getPrefixedText();
+                       $link = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
                        $text = wfMsg( 'addedwatchtext', $link );
                        $wgOut->addWikiText( $text );
                }
@@ -1634,7 +1484,7 @@ class Article {
                global $wgUser, $wgOut;
 
                if ( $wgUser->isAnon() ) {
-                       $wgOut->errorpage( 'watchnologin', 'watchnologintext' );
+                       $wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
                        return;
                }
                if ( wfReadOnly() ) {
@@ -1644,9 +1494,9 @@ class Article {
                
                if( $this->doUnwatch() ) {
                        $wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
-                       $wgOut->setRobotpolicy( 'noindex,follow' );
+                       $wgOut->setRobotpolicy( 'noindex,nofollow' );
 
-                       $link = $this->mTitle->getPrefixedText();
+                       $link = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
                        $text = wfMsg( 'removedwatchtext', $link );
                        $wgOut->addWikiText( $text );
                }
@@ -1767,7 +1617,7 @@ class Article {
         */
        function flattenRestrictions( $limit ) {
                if( !is_array( $limit ) ) {
-                       wfDebugDieBacktrace( 'Article::flattenRestrictions given non-array restriction set' );
+                       throw new MWException( 'Article::flattenRestrictions given non-array restriction set' );
                }
                $bits = array();
                ksort( $limit );
@@ -1784,7 +1634,6 @@ class Article {
         */
        function delete() {
                global $wgUser, $wgOut, $wgRequest;
-               $fname = 'Article::delete';
                $confirm = $wgRequest->wasPosted() &&
                        $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
                $reason = $wgRequest->getText( 'wpReason' );
@@ -1793,7 +1642,7 @@ class Article {
 
                # Check permissions
                if( $wgUser->isAllowed( 'delete' ) ) {
-                       if( $wgUser->isBlocked() ) {
+                       if( $wgUser->isBlocked( !$confirm ) ) {
                                $wgOut->blockedPage();
                                return;
                        }
@@ -1812,9 +1661,9 @@ class Article {
                # Better double-check that it hasn't been deleted yet!
                $dbw =& wfGetDB( DB_MASTER );
                $conds = $this->mTitle->pageCond();
-               $latest = $dbw->selectField( 'page', 'page_latest', $conds, $fname );
+               $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
                if ( $latest === false ) {
-                       $wgOut->fatalError( wfMsg( 'cannotdelete' ) );
+                       $wgOut->showFatalError( wfMsg( 'cannotdelete' ) );
                        return;
                }
 
@@ -1902,8 +1751,7 @@ class Article {
         * @return array Array of authors, duplicates not removed
         */
        function getLastNAuthors( $num, $revLatest = 0 ) {
-               $fname = 'Article::getLastNAuthors';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
 
                // First try the slave
                // If that doesn't have the latest revision, try the master
@@ -1916,13 +1764,13 @@ class Article {
                                        'page_namespace' => $this->mTitle->getNamespace(),
                                        'page_title' => $this->mTitle->getDBkey(),
                                        'rev_page = page_id'
-                               ), $fname, $this->getSelectOptions( array(
+                               ), __METHOD__, $this->getSelectOptions( array(
                                        'ORDER BY' => 'rev_timestamp DESC',
                                        'LIMIT' => $num
                                ) )
                        );
                        if ( !$res ) {
-                               wfProfileOut( $fname );
+                               wfProfileOut( __METHOD__ );
                                return array();
                        }
                        $row = $db->fetchObject( $res );
@@ -1938,7 +1786,7 @@ class Article {
                while ( $row = $db->fetchObject( $res ) ) {
                        $authors[] = $row->rev_user_text;
                }
-               wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
                return $authors;
        }
        
@@ -1991,12 +1839,11 @@ class Article {
         */
        function doDelete( $reason ) {
                global $wgOut, $wgUser;
-               $fname = 'Article::doDelete';
-               wfDebug( $fname."\n" );
+               wfDebug( __METHOD__."\n" );
 
                if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
                        if ( $this->doDeleteArticle( $reason ) ) {
-                               $deleted = $this->mTitle->getPrefixedText();
+                               $deleted = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
 
                                $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
                                $wgOut->setRobotpolicy( 'noindex,nofollow' );
@@ -2008,7 +1855,7 @@ class Article {
                                $wgOut->returnToMain( false );
                                wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason));
                        } else {
-                               $wgOut->fatalError( wfMsg( 'cannotdelete' ) );
+                               $wgOut->showFatalError( wfMsg( 'cannotdelete' ) );
                        }
                }
        }
@@ -2022,8 +1869,7 @@ class Article {
                global $wgUseSquid, $wgDeferredUpdateList;
                global $wgPostCommitUpdateList, $wgUseTrackbacks;
 
-               $fname = 'Article::doDeleteArticle';
-               wfDebug( $fname."\n" );
+               wfDebug( __METHOD__."\n" );
 
                $dbw =& wfGetDB( DB_MASTER );
                $ns = $this->mTitle->getNamespace();
@@ -2037,24 +1883,6 @@ class Article {
                $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 );
                array_push( $wgDeferredUpdateList, $u );
 
-               $linksTo = $this->mTitle->getLinksTo();
-
-               # Squid purging
-               if ( $wgUseSquid ) {
-                       $urls = array(
-                               $this->mTitle->getInternalURL(),
-                               $this->mTitle->getInternalURL( 'history' )
-                       );
-
-                       $u = SquidUpdate::newFromTitles( $linksTo, $urls );
-                       array_push( $wgPostCommitUpdateList, $u );
-
-               }
-
-               # Client and file cache invalidation
-               Title::touchArray( $linksTo );
-
-
                // 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
@@ -2079,31 +1907,38 @@ class Article {
                        ), array(
                                'page_id' => $id,
                                'page_id = rev_page'
-                       ), $fname
+                       ), __METHOD__
                );
 
                # Now that it's safely backed up, delete it
-               $dbw->delete( 'revision', array( 'rev_page' => $id ), $fname );
-               $dbw->delete( 'page', array( 'page_id' => $id ), $fname);
+               $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__);
 
-               if ($wgUseTrackbacks)
-                       $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), $fname );
+               # If using cascading deletes, we can skip some explicit deletes
+               if ( !$dbw->cascadingDeletes() ) {
 
-               # Clean up recentchanges entries...
-               $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), $fname );
+                       $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
 
-               # Finally, clean up the link tables
-               $t = $this->mTitle->getPrefixedDBkey();
+                       if ($wgUseTrackbacks)
+                               $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
 
-               Article::onArticleDelete( $this->mTitle );
+                       # Delete outgoing links
+                       $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
+                       $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
+                       $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
+                       $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
+                       $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
+                       $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
+               }
 
-               # Delete outgoing links
-               $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
-               $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
-               $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
-               $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
-               $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
-               $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
+               # If using cleanup triggers, we can skip some manual deletes
+               if ( !$dbw->cleanupTriggers() ) {
+
+                       # Clean up recentchanges entries...
+                       $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), __METHOD__ );
+               }
+
+               # Clear caches
+               Article::onArticleDelete( $this->mTitle );
 
                # Log the deletion
                $log = new LogPage( 'delete' );
@@ -2120,7 +1955,6 @@ class Article {
         */
        function rollback() {
                global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
-               $fname = 'Article::rollback';
 
                if( $wgUser->isAllowed( 'rollback' ) ) {
                        if( $wgUser->isBlocked() ) {
@@ -2152,12 +1986,10 @@ class Article {
                $tt = $this->mTitle->getDBKey();
                $n = $this->mTitle->getNamespace();
 
-               # Get the last editor, lock table exclusively
-               $dbw->begin();
+               # Get the last editor
                $current = Revision::newFromTitle( $this->mTitle );
                if( is_null( $current ) ) {
                        # Something wrong... no page?
-                       $dbw->rollback();
                        $wgOut->addHTML( wfMsg( 'notanarticle' ) );
                        return;
                }
@@ -2185,14 +2017,13 @@ class Article {
                        array(
                                'rev_page' => $current->getPage(),
                                "rev_user <> {$user} OR rev_user_text <> {$user_text}"
-                       ), $fname,
+                       ), __METHOD__,
                        array(
                                'USE INDEX' => 'page_timestamp',
                                'ORDER BY'  => 'rev_timestamp DESC' )
                        );
                if( $s === false ) {
                        # Something wrong
-                       $dbw->rollback();
                        $wgOut->setPageTitle(wfMsg('rollbackfailed'));
                        $wgOut->addHTML( wfMsg( 'cantrollback' ) );
                        return;
@@ -2214,7 +2045,7 @@ class Article {
                                        'rc_cur_id'    => $current->getPage(),
                                        'rc_user_text' => $current->getUserText(),
                                        "rc_timestamp > '{$s->rev_timestamp}'",
-                               ), $fname
+                               ), __METHOD__
                        );
                }
 
@@ -2229,9 +2060,7 @@ class Article {
                $wgOut->addHTML( '<h2>' . htmlspecialchars( $newComment ) . "</h2>\n<hr />\n" );
 
                $this->updateArticle( $target->getText(), $newComment, 1, $this->mTitle->userIsWatching(), $bot );
-               Article::onArticleEdit( $this->mTitle );
 
-               $dbw->commit();
                $wgOut->returnToMain( false );
        }
 
@@ -2259,15 +2088,21 @@ class Article {
 
        /**
         * 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.
+        * 
         * @private
-        * @param string $text
-        */
-       function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange, $newid) {
+        * @param $text New text of the article
+        * @param $summary Edit summary
+        * @param $minoredit Minor edit
+        * @param $timestamp_of_pagechange Timestamp associated with the page change
+        * @param $newid rev_id value of the new revision
+        * @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;
 
-               $fname = 'Article::editUpdates';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
 
                # Parse the text
                $options = new ParserOptions;
@@ -2301,7 +2136,7 @@ class Article {
                $shortTitle = $this->mTitle->getDBkey();
 
                if ( 0 == $id ) {
-                       wfProfileOut( $fname );
+                       wfProfileOut( __METHOD__ );
                        return;
                }
 
@@ -2311,8 +2146,9 @@ class Article {
                array_push( $wgDeferredUpdateList, $u );
 
                # If this is another user's talk page, update newtalk
-
-               if ($this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getName()) {
+               # Don't do this if $changed = false otherwise some idiot can null-edit a
+               # load of user talk pages and piss people off
+               if( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed ) {
                        if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this)) ) {
                                $other = User::newFromName( $shortTitle );
                                if( is_null( $other ) && User::isIP( $shortTitle ) ) {
@@ -2330,7 +2166,7 @@ class Article {
                        $wgMessageCache->replace( $shortTitle, $text );
                }
 
-               wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
        }
 
        /**
@@ -2345,6 +2181,12 @@ class Article {
        function setOldSubtitle( $oldid=0 ) {
                global $wgLang, $wgOut, $wgUser;
 
+               if ( !wfRunHooks( 'DisplayOldSubtitle', array(&$this, &$oldid) ) ) {
+                               return; 
+               }       
+
+               $revision = Revision::newFromId( $oldid );
+
                $current = ( $oldid == $this->mLatest );
                $td = $wgLang->timeanddate( $this->mTimestamp, true );
                $sk = $wgUser->getSkin();
@@ -2355,10 +2197,20 @@ class Article {
                $prevlink = $prev
                        ? $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'previousrevision' ), 'direction=prev&oldid='.$oldid )
                        : wfMsg( 'previousrevision' );
+               $prevdiff = $prev
+                       ? $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=prev&oldid='.$oldid )
+                       : wfMsg( 'diff' );
                $nextlink = $current
                        ? wfMsg( 'nextrevision' )
                        : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'nextrevision' ), 'direction=next&oldid='.$oldid );
-               $r = wfMsg( 'revisionasofwithlink', $td, $lnk, $prevlink, $nextlink );
+               $nextdiff = $current
+                       ? wfMsg( 'diff' )
+                       : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid );
+               
+               $userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() )
+                                               . $sk->userToolLinks( $revision->getUser(), $revision->getUserText() );
+               
+               $r = wfMsg( 'old-revision-navigation', $td, $lnk, $prevlink, $nextlink, $userlinks, $prevdiff, $nextdiff );
                $wgOut->setSubtitle( $r );
        }
 
@@ -2383,7 +2235,7 @@ class Article {
        function tryFileCache() {
                static $called = false;
                if( $called ) {
-                       wfDebug( " tryFileCache() -- called twice!?\n" );
+                       wfDebug( "Article::tryFileCache(): called twice!?\n" );
                        return;
                }
                $called = true;
@@ -2391,15 +2243,15 @@ class Article {
                        $touched = $this->mTouched;
                        $cache = new CacheManager( $this->mTitle );
                        if($cache->isFileCacheGood( $touched )) {
-                               wfDebug( " tryFileCache() - about to load\n" );
+                               wfDebug( "Article::tryFileCache(): about to load file\n" );
                                $cache->loadFromFileCache();
                                return true;
                        } else {
-                               wfDebug( " tryFileCache() - starting buffer\n" );
+                               wfDebug( "Article::tryFileCache(): starting buffer\n" );
                                ob_start( array(&$cache, 'saveToFileCache' ) );
                        }
                } else {
-                       wfDebug( " tryFileCache() - not cacheable\n" );
+                       wfDebug( "Article::tryFileCache(): not cacheable\n" );
                }
        }
 
@@ -2430,7 +2282,6 @@ class Article {
         *
         */
        function checkTouched() {
-               $fname = 'Article::checkTouched';
                if( !$this->mDataLoaded ) {
                        $this->loadPageData();
                }
@@ -2468,8 +2319,7 @@ class Article {
         * @param bool $minor whereas it's a minor modification
         */
        function quickEdit( $text, $comment = '', $minor = 0 ) {
-               $fname = 'Article::quickEdit';
-               wfProfileIn( $fname );
+               wfProfileIn( __METHOD__ );
 
                $dbw =& wfGetDB( DB_MASTER );
                $dbw->begin();
@@ -2484,7 +2334,7 @@ class Article {
                $this->updateRevisionOn( $dbw, $revision );
                $dbw->commit();
 
-               wfProfileOut( $fname );
+               wfProfileOut( __METHOD__ );
        }
 
        /**
@@ -2557,27 +2407,31 @@ class Article {
         * @param $title_obj a title object
         */
 
-       function onArticleCreate($title_obj) {
-               global $wgUseSquid, $wgPostCommitUpdateList;
-
-               $title_obj->touchLinks();
-               $titles = $title_obj->getLinksTo();
-
-               # Purge squid
-               if ( $wgUseSquid ) {
-                       $urls = $title_obj->getSquidURLs();
-                       foreach ( $titles as $linkTitle ) {
-                               $urls[] = $linkTitle->getInternalURL();
-                       }
-                       $u = new SquidUpdate( $urls );
-                       array_push( $wgPostCommitUpdateList, $u );
+       static function onArticleCreate($title) {
+               # The talk page isn't in the regular link tables, so we need to update manually:
+               if ( $title->isTalkPage() ) {
+                       $other = $title->getSubjectPage();
+               } else {
+                       $other = $title->getTalkPage();
                }
+               $other->invalidateCache();
+               $other->purgeSquid();
+
+               $title->touchLinks();
+               $title->purgeSquid();
        }
 
-       function onArticleDelete( $title ) {
-               global $wgMessageCache;
+       static function onArticleDelete( $title ) {
+               global $wgUseFileCache, $wgMessageCache;
 
                $title->touchLinks();
+               $title->purgeSquid();
+               
+               # File cache
+               if ( $wgUseFileCache ) {
+                       $cm = new CacheManager( $title );
+                       @unlink( $cm->fileCacheName() );
+               }
 
                if( $title->getNamespace() == NS_MEDIAWIKI) {
                        $wgMessageCache->replace( $title->getDBkey(), false );
@@ -2587,31 +2441,19 @@ class Article {
        /**
         * Purge caches on page update etc
         */
-       function onArticleEdit( $title ) {
-               global $wgUseSquid, $wgPostCommitUpdateList, $wgUseFileCache;
+       static function onArticleEdit( $title ) {
+               global $wgDeferredUpdateList, $wgUseFileCache;
 
                $urls = array();
 
-               // Template namespace? Purge all articles linking here.
-               // FIXME: When a templatelinks table arrives, use it for all includes.
-               if ( $title->getNamespace() == NS_TEMPLATE) {
-                       $titles = $title->getLinksTo();
-                       Title::touchArray( $titles );
-                       if ( $wgUseSquid ) {
-                               foreach ( $titles as $link ) {
-                                       $urls[] = $link->getInternalURL();
-                               }
-                       }
-               }
+               // Invalidate caches of articles which include this page
+               $update = new HTMLCacheUpdate( $title, 'templatelinks' );
+               $wgDeferredUpdateList[] = $update;
 
-               # Squid updates
-               if ( $wgUseSquid ) {
-                       $urls = array_merge( $urls, $title->getSquidURLs() );
-                       $u = new SquidUpdate( $urls );
-                       array_push( $wgPostCommitUpdateList, $u );
-               }
+               # Purge squid for this page only
+               $title->purgeSquid();
 
-               # File cache
+               # Clear file cache
                if ( $wgUseFileCache ) {
                        $cm = new CacheManager( $title );
                        @unlink( $cm->fileCacheName() );
@@ -2628,10 +2470,9 @@ class Article {
         */
        function info() {
                global $wgLang, $wgOut, $wgAllowPageInfo, $wgUser;
-               $fname = 'Article::info';
 
                if ( !$wgAllowPageInfo ) {
-                       $wgOut->errorpage( 'nosuchaction', 'nosuchactiontext' );
+                       $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
                        return;
                }
 
@@ -2657,7 +2498,7 @@ class Article {
                                'watchlist',
                                'COUNT(*)',
                                $wl_clause,
-                               $fname,
+                               __METHOD__,
                                $this->getSelectOptions() );
 
                        $pageInfo = $this->pageCountInfo( $page );
@@ -2694,20 +2535,19 @@ class Article {
                $dbr =& wfGetDB( DB_SLAVE );
 
                $rev_clause = array( 'rev_page' => $id );
-               $fname = 'Article::pageCountInfo';
 
                $edits = $dbr->selectField(
                        'revision',
                        'COUNT(rev_page)',
                        $rev_clause,
-                       $fname,
+                       __METHOD__,
                        $this->getSelectOptions() );
 
                $authors = $dbr->selectField(
                        'revision',
                        'COUNT(DISTINCT rev_user_text)',
                        $rev_clause,
-                       $fname,
+                       __METHOD__,
                        $this->getSelectOptions() );
 
                return array( 'edits' => $edits, 'authors' => $authors );