catch exceptions while generating auto delete reason
[lhc/web/wiklou.git] / includes / Article.php
index 45dcadf..5470dbc 100644 (file)
@@ -23,7 +23,7 @@ class Article extends Page {
         */
 
        /**
-        * @var RequestContext
+        * @var IContextSource
         */
        protected $mContext;
 
@@ -32,7 +32,13 @@ class Article extends Page {
         */
        protected $mPage;
 
-       var $mContent;                    // !<
+       /**
+        * @var ParserOptions: ParserOptions object for $wgUser articles
+        */
+       public $mParserOptions;
+
+       var $mContent;                    // !< #BC cruft
+    var $mContentObject;              // !<
        var $mContentLoaded = false;      // !<
        var $mOldId;                      // !<
 
@@ -69,6 +75,10 @@ class Article extends Page {
                $this->mPage = $this->newPage( $title );
        }
 
+       /**
+        * @param $title Title
+        * @return WikiPage
+        */
        protected function newPage( Title $title ) {
                return new WikiPage( $title );
        }
@@ -76,6 +86,7 @@ class Article extends Page {
        /**
         * Constructor from a page id
         * @param $id Int article ID to load
+        * @return Article|null
         */
        public static function newFromID( $id ) {
                $t = Title::newFromID( $id );
@@ -88,10 +99,10 @@ class Article extends Page {
         * Create an Article object of the appropriate class for the given page.
         *
         * @param $title Title
-        * @param $context RequestContext
+        * @param $context IContextSource
         * @return Article object
         */
-       public static function newFromTitle( $title, RequestContext $context ) {
+       public static function newFromTitle( $title, IContextSource $context ) {
                if ( NS_MEDIA == $title->getNamespace() ) {
                        // FIXME: where should this go?
                        $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
@@ -102,13 +113,14 @@ class Article extends Page {
                if ( !$page ) {
                        switch( $title->getNamespace() ) {
                                case NS_FILE:
-                                       $page = new ImagePage( $title );
+                                       $page = new ImagePage( $title ); #FIXME: teach ImagePage to use ContentHandler
                                        break;
                                case NS_CATEGORY:
-                                       $page = new CategoryPage( $title );
+                                       $page = new CategoryPage( $title ); #FIXME: teach ImagePage to use ContentHandler
                                        break;
                                default:
-                                       $page = new Article( $title );
+                    $handler = ContentHandler::getForTitle( $title );
+                                       $page = $handler->createArticle( $title );
                        }
                }
                $page->setContext( $context );
@@ -116,6 +128,19 @@ class Article extends Page {
                return $page;
        }
 
+       /**
+        * Create an Article object of the appropriate class for the given page.
+        *
+        * @param $page WikiPage
+        * @param $context IContextSource
+        * @return Article object
+        */
+       public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
+               $article = self::newFromTitle( $page->getTitle(), $context );
+               $article->mPage = $page; // override to keep process cached vars
+               return $article;
+       }
+
        /**
         * Tell the page view functions that this view was redirected
         * from another page on the wiki.
@@ -126,29 +151,26 @@ class Article extends Page {
        }
 
        /**
-        * Get the Title object this text redirects to
+        * Get the title object of the article
         *
-        * @param $text string article content containing redirect info
-        * @return mixed false, Title of in-wiki target, or string with URL
-        * @deprecated since 1.17
+        * @return Title object of this page
         */
-       public function followRedirectText( $text ) {
-               // recurse through to only get the final target
-               return $this->getRedirectURL( Title::newFromRedirectRecurse( $text ) );
+       public function getTitle() {
+               return $this->mPage->getTitle();
        }
 
        /**
-        * Get the title object of the article
-        * @return Title object of this page
+        * Get the WikiPage object of this instance
+        *
+        * @since 1.19
+        * @return WikiPage
         */
-       public function getTitle() {
-               return $this->mPage->getTitle();
+       public function getPage() {
+               return $this->mPage;
        }
 
        /**
         * Clear the object
-        * @todo FIXME: Shouldn't this be public?
-        * @private
         */
        public function clear() {
                $this->mContentLoaded = false;
@@ -163,14 +185,31 @@ class Article extends Page {
        /**
         * Note that getContent/loadContent do not follow redirects anymore.
         * If you need to fetch redirectable content easily, try
-        * the shortcut in Article::followRedirect()
+        * the shortcut in WikiPage::getRedirectTarget()
         *
         * This function has side effects! Do not use this function if you
         * only want the real revision text if any.
         *
         * @return Return the text of this revision
+     * @deprecated in 1.20; use getContentObject() instead
         */
        public function getContent() {
+        wfDeprecated( __METHOD__, '1.20' );
+        $content = $this->getContentObject();
+        return ContentHandler::getContentText( $content );
+    }
+
+    /**
+     * Note that getContent/loadContent do not follow redirects anymore.
+     * If you need to fetch redirectable content easily, try
+     * the shortcut in WikiPage::getRedirectTarget()
+     *
+     * This function has side effects! Do not use this function if you
+     * only want the real revision text if any.
+     *
+     * @return Return the content of this revision
+     */
+   public function getContentObject() {
                global $wgUser;
 
                wfProfileIn( __METHOD__ );
@@ -188,12 +227,12 @@ class Article extends Page {
                        }
                        wfProfileOut( __METHOD__ );
 
-                       return $text;
+                       return ContentHandler::makeContent( $text, $this->getTitle() );
                } else {
-                       $this->loadContent();
+                       $this->fetchContentObject();
                        wfProfileOut( __METHOD__ );
 
-                       return $this->mContent;
+                       return $this->mContentObject;
                }
        }
 
@@ -219,27 +258,39 @@ class Article extends Page {
 
                $this->mRedirectUrl = false;
 
-               $oldid = $wgRequest->getVal( 'oldid' );
+               $oldid = $wgRequest->getIntOrNull( 'oldid' );
 
-               if ( isset( $oldid ) ) {
-                       $oldid = intval( $oldid );
-                       if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
-                               $nextid = $this->getTitle()->getNextRevisionID( $oldid );
-                               if ( $nextid ) {
-                                       $oldid = $nextid;
-                               } else {
-                                       $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
-                               }
-                       } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
-                               $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
-                               if ( $previd ) {
-                                       $oldid = $previd;
+               if ( $oldid === null ) {
+                       return 0;
+               }
+
+               if ( $oldid !== 0 ) {
+                       # Load the given revision and check whether the page is another one.
+                       # In that case, update this instance to reflect the change.
+                       $this->mRevision = Revision::newFromId( $oldid );
+                       if ( $this->mRevision !== null ) {
+                               // Revision title doesn't match the page title given?
+                               if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
+                                       $function = array( get_class( $this->mPage ), 'newFromID' );
+                                       $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
                                }
                        }
                }
 
-               if ( !$oldid ) {
-                       $oldid = 0;
+               if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
+                       $nextid = $this->getTitle()->getNextRevisionID( $oldid );
+                       if ( $nextid ) {
+                               $oldid = $nextid;
+                               $this->mRevision = null;
+                       } else {
+                               $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
+                       }
+               } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
+                       $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
+                       if ( $previd ) {
+                               $oldid = $previd;
+                               $this->mRevision = null;
+                       }
                }
 
                return $oldid;
@@ -247,88 +298,109 @@ class Article extends Page {
 
        /**
         * Load the revision (including text) into this object
+        *
+        * @deprecated in 1.19; use fetchContent()
         */
        function loadContent() {
-               if ( $this->mContentLoaded ) {
-                       return;
-               }
-
-               wfProfileIn( __METHOD__ );
-
-               $this->fetchContent( $this->getOldID() );
-
-               wfProfileOut( __METHOD__ );
+               wfDeprecated( __METHOD__, '1.19' );
+               $this->fetchContent();
        }
 
        /**
         * Get text of an article from database
         * Does *NOT* follow redirects.
         *
-        * @param $oldid Int: 0 for whatever the latest revision is
         * @return mixed string containing article contents, or false if null
+     * @deprecated in 1.20, use getContentObject() instead
         */
-       function fetchContent( $oldid = 0 ) {
-               if ( $this->mContentLoaded ) {
+       protected function fetchContent() { #BC cruft!
+        wfDeprecated( __METHOD__, '1.20' );
+
+               if ( $this->mContentLoaded && $this->mContent ) {
                        return $this->mContent;
                }
 
-               # Pre-fill content with error message so that if something
-               # fails we'll have something telling us what we intended.
-               $t = $this->getTitle()->getPrefixedText();
-               $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
-               $this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
+               wfProfileIn( __METHOD__ );
 
-               if ( $oldid ) {
-                       $revision = Revision::newFromId( $oldid );
-                       if ( $revision === null ) {
-                               wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
-                               return false;
-                       }
+        $content = $this->fetchContentObject();
 
-                       if ( $this->mPage->getID() != $revision->getPage() ) {
-                               $data = $this->mPage->pageDataFromId( wfGetDB( DB_SLAVE ), $revision->getPage() );
+        $this->mContent = ContentHandler::getContentText( $content ); #FIXME: get rid of mContent everywhere!
+               wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); #BC cruft!
 
-                               if ( !$data ) {
-                                       wfDebug( __METHOD__ . " failed to get page data linked to revision id $oldid\n" );
-                                       return false;
-                               }
+               wfProfileOut( __METHOD__ );
 
-                               $title = Title::makeTitle( $data->page_namespace, $data->page_title );
-                               $this->mPage = new WikiPage( $title );
-                               $this->mPage->loadPageData( $data );
-                       }
-               } else {
-                       if ( $this->mPage->getLatest() === false ) {
-                               wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
-                               return false;
-                       }
+               return $this->mContent;
+       }
 
-                       $revision = $this->mPage->getRevision();
-                       if ( $revision === null ) {
-                               wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
-                               return false;
-                       }
-               }
 
-               // @todo 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->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
+    /**
+     * Get text content object
+     * Does *NOT* follow redirects.
+     *
+     * @return Content object containing article contents, or null
+     */
+    protected function fetchContentObject() {
+        if ( $this->mContentLoaded ) {
+            return $this->mContentObject;
+        }
 
-               $this->mRevIdFetched = $revision->getId();
-               $this->mContentLoaded = true;
-               $this->mRevision =& $revision;
+        wfProfileIn( __METHOD__ );
 
-               wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+        $this->mContentLoaded = true;
+        $this->mContent = null;
 
-               return $this->mContent;
-       }
+        $oldid = $this->getOldID();
+
+        # Pre-fill content with error message so that if something
+        # fails we'll have something telling us what we intended.
+        $t = $this->getTitle()->getPrefixedText();
+        $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
+        $this->mContentObject = new MessageContent( 'missing-article', array($t, $d), array() ) ;
+
+        if ( $oldid ) {
+            # $this->mRevision might already be fetched by getOldIDFromRequest()
+            if ( !$this->mRevision ) {
+                $this->mRevision = Revision::newFromId( $oldid );
+                if ( !$this->mRevision ) {
+                    wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
+                    wfProfileOut( __METHOD__ );
+                    return false;
+                }
+            }
+        } else {
+            if ( !$this->mPage->getLatest() ) {
+                wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
+                wfProfileOut( __METHOD__ );
+                return false;
+            }
+
+            $this->mRevision = $this->mPage->getRevision();
+
+            if ( !$this->mRevision ) {
+                wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
+                wfProfileOut( __METHOD__ );
+                return false;
+            }
+        }
+
+        // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
+        // We should instead work with the Revision object when we need it...
+        $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER ); // Loads if user is allowed
+        $this->mRevIdFetched = $this->mRevision->getId();
+
+        wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); #FIXME: register new hook
+
+        wfProfileOut( __METHOD__ );
+
+        return $this->mContentObject;
+    }
 
        /**
         * No-op
         * @deprecated since 1.18
         */
        public function forUpdate() {
-               wfDeprecated( __METHOD__ );
+               wfDeprecated( __METHOD__, '1.18' );
        }
 
        /**
@@ -345,6 +417,19 @@ class Article extends Page {
                return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
        }
 
+       /**
+        * Get the fetched Revision object depending on request parameters or null
+        * on failure.
+        *
+        * @since 1.19
+        * @return Revision|null
+        */
+       public function getRevisionFetched() {
+               $this->fetchContentObject();
+
+               return $this->mRevision;
+       }
+
        /**
         * Use this to fetch the rev ID used on page views
         *
@@ -364,14 +449,25 @@ class Article extends Page {
         */
        public function view() {
                global $wgUser, $wgOut, $wgRequest, $wgParser;
-               global $wgUseFileCache, $wgUseETag;
+               global $wgUseFileCache, $wgUseETag, $wgDebugToolbar;
 
                wfProfileIn( __METHOD__ );
 
                # Get variables from query string
+               # As side effect this will load the revision and update the title
+               # in a revision ID is passed in the request, so this should remain
+               # the first call of this method even if $oldid is used way below.
                $oldid = $this->getOldID();
 
-               # getOldID may want us to redirect somewhere else
+               # Another whitelist check in case getOldID() is altering the title
+               $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $wgUser );
+               if ( count( $permErrors ) ) {
+                       wfDebug( __METHOD__ . ": denied on secondary read check\n" );
+                       wfProfileOut( __METHOD__ );
+                       throw new PermissionsError( 'read', $permErrors );
+               }
+
+               # getOldID() may as well want us to redirect somewhere else
                if ( $this->mRedirectUrl ) {
                        $wgOut->redirect( $this->mRedirectUrl );
                        wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
@@ -380,10 +476,6 @@ class Article extends Page {
                        return;
                }
 
-               $wgOut->setArticleFlag( true );
-               # Set page title (may be overridden by DISPLAYTITLE)
-               $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
-
                # If we got diff in the query, we want to see a diff page instead of the article.
                if ( $wgRequest->getCheck( 'diff' ) ) {
                        wfDebug( __METHOD__ . ": showing diff page\n" );
@@ -393,22 +485,26 @@ class Article extends Page {
                        return;
                }
 
+               # Set page title (may be overridden by DISPLAYTITLE)
+               $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
+
+               $wgOut->setArticleFlag( true );
                # Allow frames by default
                $wgOut->allowClickjacking();
 
                $parserCache = ParserCache::singleton();
 
-               $parserOptions = $this->mPage->getParserOptions();
+               $parserOptions = $this->getParserOptions();
                # Render printable version, use printable version cache
                if ( $wgOut->isPrintable() ) {
                        $parserOptions->setIsPrintable( true );
                        $parserOptions->setEditSection( false );
-               } elseif ( $wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) {
+               } elseif ( !$this->getTitle()->quickUserCan( 'edit' ) ) {
                        $parserOptions->setEditSection( false );
                }
 
                # Try client and file cache
-               if ( $oldid === 0 && $this->mPage->checkTouched() ) {
+               if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
                        if ( $wgUseETag ) {
                                $wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) );
                        }
@@ -424,25 +520,21 @@ class Article extends Page {
                                wfDebug( __METHOD__ . ": done file cache\n" );
                                # tell wgOut that output is taken care of
                                $wgOut->disable();
-                               $this->mPage->viewUpdates();
+                               $this->mPage->doViewUpdates( $wgUser );
                                wfProfileOut( __METHOD__ );
 
                                return;
                        }
                }
 
-               if ( !$wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) {
-                       $parserOptions->setEditSection( false );
-               }
-
                # Should the parser cache be used?
-               $useParserCache = $this->useParserCache( $oldid );
+               $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
                wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
                if ( $wgUser->getStubThreshold() ) {
                        wfIncrStats( 'pcache_miss_stub' );
                }
 
-               $wasRedirected = $this->showRedirectedFromHeader();
+               $this->showRedirectedFromHeader();
                $this->showNamespaceHeader();
 
                # Iterate through the possible ways of constructing the output text.
@@ -457,45 +549,45 @@ class Article extends Page {
                                        wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
                                        break;
                                case 2:
+                                       # Early abort if the page doesn't exist
+                                       if ( !$this->mPage->exists() ) {
+                                               wfDebug( __METHOD__ . ": showing missing article\n" );
+                                               $this->showMissingArticle();
+                                               wfProfileOut( __METHOD__ );
+                                               return;
+                                       }
+
                                        # Try the parser cache
                                        if ( $useParserCache ) {
                                                $this->mParserOutput = $parserCache->get( $this, $parserOptions );
 
                                                if ( $this->mParserOutput !== false ) {
-                                                       wfDebug( __METHOD__ . ": showing parser cache contents\n" );
+                                                       if ( $oldid ) {
+                                                               wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
+                                                               $this->setOldSubtitle( $oldid );
+                                                       } else {
+                                                               wfDebug( __METHOD__ . ": showing parser cache contents\n" );
+                                                       }
                                                        $wgOut->addParserOutput( $this->mParserOutput );
                                                        # Ensure that UI elements requiring revision ID have
                                                        # the correct version information.
                                                        $wgOut->setRevisionId( $this->mPage->getLatest() );
-                                                       $outputDone = true;
                                                        # Preload timestamp to avoid a DB hit
-                                                       if ( isset( $this->mParserOutput->mTimestamp ) ) {
-                                                               $this->mPage->setTimestamp( $this->mParserOutput->mTimestamp );
+                                                       $cachedTimestamp = $this->mParserOutput->getTimestamp();
+                                                       if ( $cachedTimestamp !== null ) {
+                                                               $wgOut->setRevisionTimestamp( $cachedTimestamp );
+                                                               $this->mPage->setTimestamp( $cachedTimestamp );
                                                        }
+                                                       $outputDone = true;
                                                }
                                        }
                                        break;
                                case 3:
-                                       $text = $this->getContent();
-                                       if ( $text === false || $this->mPage->getID() == 0 ) {
-                                               wfDebug( __METHOD__ . ": showing missing article\n" );
-                                               $this->showMissingArticle();
-                                               wfProfileOut( __METHOD__ );
-                                               return;
-                                       }
-
-                                       # Another whitelist check in case oldid is altering the title
-                                       if ( !$this->getTitle()->userCanRead() ) {
-                                               wfDebug( __METHOD__ . ": denied on secondary read check\n" );
-                                               $wgOut->loginToUse();
-                                               $wgOut->output();
-                                               $wgOut->disable();
-                                               wfProfileOut( __METHOD__ );
-                                               return;
-                                       }
+                                       # This will set $this->mRevision if needed
+                                       $this->fetchContentObject();
 
                                        # Are we looking at an old revision
-                                       if ( $oldid && !is_null( $this->mRevision ) ) {
+                                       if ( $oldid && $this->mRevision ) {
                                                $this->setOldSubtitle( $oldid );
 
                                                if ( !$this->showDeletedRevisionHeader() ) {
@@ -503,38 +595,35 @@ class Article extends Page {
                                                        wfProfileOut( __METHOD__ );
                                                        return;
                                                }
-
-                                               # If this "old" version is the current, then try the parser cache...
-                                               if ( $oldid === $this->mPage->getLatest() && $this->useParserCache( false ) ) {
-                                                       $this->mParserOutput = $parserCache->get( $this, $parserOptions );
-                                                       if ( $this->mParserOutput ) {
-                                                               wfDebug( __METHOD__ . ": showing parser cache for current rev permalink\n" );
-                                                               $wgOut->addParserOutput( $this->mParserOutput );
-                                                               $wgOut->setRevisionId( $this->mPage->getLatest() );
-                                                               $outputDone = true;
-                                                               break;
-                                                       }
-                                               }
                                        }
 
                                        # Ensure that UI elements requiring revision ID have
                                        # the correct version information.
                                        $wgOut->setRevisionId( $this->getRevIdFetched() );
+                                       # Preload timestamp to avoid a DB hit
+                                       $wgOut->setRevisionTimestamp( $this->getTimestamp() );
 
                                        # Pages containing custom CSS or JavaScript get special treatment
                                        if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
+                        #FIXME: use ContentHandler for specialized actions insetad.
                                                wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
                                                $this->showCssOrJsPage();
                                                $outputDone = true;
+                    } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->fetchContentObject(), $this->getTitle(), $wgOut ) ) ) { #FIXME: document new hook!
+                        # Allow extensions do their own custom view for certain pages
+                        $outputDone = true;
+                                       } elseif( Hooks::isRegistered( 'ArticleViewCustom' ) && !wfRunHooks( 'ArticleViewCustom', array( $this->fetchContent(), $this->getTitle(), $wgOut ) ) ) { #FIXME: fetchContent() is deprecated! #FIXME: deprecate hook!
+                                               # Allow extensions do their own custom view for certain pages
+                                               $outputDone = true;
                                        } else {
-                                               $rt = Title::newFromRedirectArray( $text );
+                                               $content = $this->getContentObject();
+                                               $rt = $content->getRedirectChain();
                                                if ( $rt ) {
                                                        wfDebug( __METHOD__ . ": showing redirect=no page\n" );
                                                        # Viewing a redirect page (e.g. with parameter redirect=no)
-                                                       # Don't append the subtitle if this was an old revision
-                                                       $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
+                                                       $wgOut->addHTML( $this->viewRedirect( $rt ) );
                                                        # Parse just to get categories, displaytitle, etc.
-                                                       $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
+                                                       $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions );
                                                        $wgOut->addParserOutputNoText( $this->mParserOutput );
                                                        $outputDone = true;
                                                }
@@ -544,16 +633,34 @@ class Article extends Page {
                                        # Run the parse, protected by a pool counter
                                        wfDebug( __METHOD__ . ": doing uncached parse\n" );
 
-                                       $key = $parserCache->getKey( $this, $parserOptions );
-                                       $poolArticleView = new PoolWorkArticleView( $this, $key, $useParserCache, $parserOptions );
+                                       $poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
+                                               $this->getRevIdFetched(), $useParserCache, $this->getContentObject() );
 
                                        if ( !$poolArticleView->execute() ) {
+                                               $error = $poolArticleView->getError();
+                                               if ( $error ) {
+                                                       $wgOut->clearHTML(); // for release() errors
+                                                       $wgOut->enableClientCache( false );
+                                                       $wgOut->setRobotPolicy( 'noindex,nofollow' );
+
+                                                       $errortext = $error->getWikiText( false, 'view-pool-error' );
+                                                       $wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
+                                               }
                                                # Connection or timeout error
                                                wfProfileOut( __METHOD__ );
                                                return;
-                                       } else {
-                                               $outputDone = true;
                                        }
+
+                                       $this->mParserOutput = $poolArticleView->getParserOutput();
+                                       $wgOut->addParserOutput( $this->mParserOutput );
+
+                                       # Don't cache a dirty ParserOutput object
+                                       if ( $poolArticleView->getIsDirty() ) {
+                                               $wgOut->setSquidMaxage( 0 );
+                                               $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
+                                       }
+
+                                       $outputDone = true;
                                        break;
                                # Should be unreachable, but just in case...
                                default:
@@ -561,37 +668,52 @@ class Article extends Page {
                        }
                }
 
-               # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
-               if ( $this->mParserOutput ) {
-                       $titleText = $this->mParserOutput->getTitleText();
+               # Get the ParserOutput actually *displayed* here.
+               # Note that $this->mParserOutput is the *current* version output.
+               $pOutput = ( $outputDone instanceof ParserOutput )
+                       ? $outputDone // object fetched by hook
+                       : $this->mParserOutput;
 
-                       if ( strval( $titleText ) !== '' ) {
-                               $wgOut->setPageTitle( $titleText );
-                       }
+               # Adjust title for main page & pages with displaytitle
+               if ( $pOutput ) {
+                       $this->adjustDisplayTitle( $pOutput );
                }
 
                # For the main page, overwrite the <title> element with the con-
                # tents of 'pagetitle-view-mainpage' instead of the default (if
                # that's not empty).
                # This message always exists because it is in the i18n files
-               if ( $this->getTitle()->equals( Title::newMainPage() ) ) {
+               if ( $this->getTitle()->isMainPage() ) {
                        $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
                        if ( !$msg->isDisabled() ) {
                                $wgOut->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
                        }
                }
 
-               # Now that we've filled $this->mParserOutput, we know whether
-               # there are any __NOINDEX__ tags on the page
-               $policy = $this->getRobotPolicy( 'view' );
+               # Check for any __NOINDEX__ tags on the page using $pOutput
+               $policy = $this->getRobotPolicy( 'view', $pOutput );
                $wgOut->setIndexPolicy( $policy['index'] );
                $wgOut->setFollowPolicy( $policy['follow'] );
 
                $this->showViewFooter();
-               $this->mPage->viewUpdates();
+               $this->mPage->doViewUpdates( $wgUser );
+
                wfProfileOut( __METHOD__ );
        }
 
+       /**
+        * Adjust title for pages with displaytitle, -{T|}- or language conversion
+        * @param $pOutput ParserOutput
+        */
+       public function adjustDisplayTitle( ParserOutput $pOutput ) {
+               global $wgOut;
+               # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
+               $titleText = $pOutput->getTitleText();
+               if ( strval( $titleText ) !== '' ) {
+                       $wgOut->setPageTitle( $titleText );
+               }
+       }
+
        /**
         * Show a diff page according to current request variables. For use within
         * Article::view() only, other callers should use the DifferenceEngine class.
@@ -606,14 +728,16 @@ class Article extends Page {
                $unhide = $wgRequest->getInt( 'unhide' ) == 1;
                $oldid = $this->getOldID();
 
-               $de = new DifferenceEngine( $this->getTitle(), $oldid, $diff, $rcid, $purge, $unhide );
+        $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
+               $de = $contentHandler->getDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
+
                // DifferenceEngine directly fetched the revision:
                $this->mRevIdFetched = $de->mNewid;
                $de->showDiffPage( $diffOnly );
 
                if ( $diff == 0 || $diff == $this->mPage->getLatest() ) {
                        # Run view updates for current revision only
-                       $this->mPage->viewUpdates();
+                       $this->mPage->doViewUpdates( $wgUser );
                }
        }
 
@@ -627,26 +751,27 @@ class Article extends Page {
        protected function showCssOrJsPage() {
                global $wgOut;
 
-               $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache'>\n$1\n</div>", 'clearyourcache' );
+               $dir = $this->getContext()->getLanguage()->getDir();
+               $lang = $this->getContext()->getLanguage()->getCode();
+
+               $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
+                       'clearyourcache' );
 
                // Give hooks a chance to customise the output
-               if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
-                       // Wrap the whole lot in a <pre> and don't parse
-                       $m = array();
-                       preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
-                       $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
-                       $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
-                       $wgOut->addHTML( "\n</pre>\n" );
+               if ( !Hooks::isRegistered('ShowRawCssJs') || wfRunHooks( 'ShowRawCssJs', array( $this->fetchContent(), $this->getTitle(), $wgOut ) ) ) { #FIXME: fetchContent() is deprecated #FIXME: hook is deprecated
+            $po = $this->mContentObject->getParserOutput();
+                       $wgOut->addHTML( $po->getText() );
                }
        }
 
        /**
         * Get the robot policy to be used for the current view
         * @param $action String the action= GET parameter
+        * @param $pOutput ParserOutput
         * @return Array the policy that should be set
         * TODO: actions other than 'view'
         */
-       public function getRobotPolicy( $action ) {
+       public function getRobotPolicy( $action, $pOutput ) {
                global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies;
                global $wgDefaultRobotPolicy, $wgRequest;
 
@@ -694,12 +819,12 @@ class Article extends Page {
                                self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
                        );
                }
-               if ( $this->getTitle()->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) {
+               if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
                        # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
                        # a final sanity check that we have really got the parser output.
                        $policy = array_merge(
                                $policy,
-                               array( 'index' => $this->mParserOutput->getIndexPolicy() )
+                               array( 'index' => $pOutput->getIndexPolicy() )
                        );
                }
 
@@ -759,16 +884,14 @@ class Article extends Page {
                        // This is an internally redirected page view.
                        // We'll need a backlink to the source page for navigation.
                        if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
-                               $redir = Linker::link(
+                               $redir = Linker::linkKnown(
                                        $this->mRedirectedFrom,
                                        null,
                                        array(),
-                                       array( 'redirect' => 'no' ),
-                                       array( 'known', 'noclasses' )
+                                       array( 'redirect' => 'no' )
                                );
 
-                               $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
-                               $wgOut->setSubtitle( $s );
+                               $wgOut->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
 
                                // Set the fragment if one was specified in the redirect
                                if ( strval( $this->getTitle()->getFragment() ) != '' ) {
@@ -781,6 +904,9 @@ class Article extends Page {
                                        'href' => $this->getTitle()->getLocalURL() )
                                );
 
+                               // Tell $wgOut the user arrived at this article through a redirect
+                               $wgOut->setRedirectedFrom( $this->mRedirectedFrom );
+
                                return true;
                        }
                } elseif ( $rdfrom ) {
@@ -788,8 +914,7 @@ class Article extends Page {
                        // If it was reported from a trusted site, supply a backlink.
                        if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
                                $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
-                               $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
-                               $wgOut->setSubtitle( $s );
+                               $wgOut->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
 
                                return true;
                        }
@@ -816,7 +941,7 @@ class Article extends Page {
         * Show the footer section of an ordinary page view
         */
        public function showViewFooter() {
-               global $wgOut, $wgUseTrackbacks;
+               global $wgOut;
 
                # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
                if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) {
@@ -827,11 +952,6 @@ class Article extends Page {
                # chance to mark this new article as patrolled.
                $this->showPatrolFooter();
 
-               # Trackbacks
-               if ( $wgUseTrackbacks ) {
-                       $this->addTrackbacks();
-               }
-
                wfRunHooks( 'ArticleViewFooter', array( $this ) );
 
        }
@@ -850,7 +970,7 @@ class Article extends Page {
                        return;
                }
 
-               $token = $wgUser->editToken( $rcid );
+               $token = $wgUser->getEditToken( $rcid );
                $wgOut->preventClickjacking();
 
                $wgOut->addHTML(
@@ -878,7 +998,7 @@ class Article extends Page {
         * namespace, show the default message text. To be called from Article::view().
         */
        public function showMissingArticle() {
-               global $wgOut, $wgRequest, $wgUser;
+               global $wgOut, $wgRequest, $wgUser, $wgSend404Code;
 
                # Show info in user (talk) namespace. Does the user exist? Is he blocked?
                if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) {
@@ -887,7 +1007,7 @@ class Article extends Page {
                        $user = User::newFromName( $rootPart, false /* allow IP users*/ );
                        $ip = User::isIP( $rootPart );
 
-                       if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
+                       if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
                                $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
                                        array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) );
                        } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
@@ -940,7 +1060,7 @@ class Article extends Page {
                }
                $text = "<div class='noarticletext'>\n$text\n</div>";
 
-               if ( !$this->mPage->hasViewableContent() ) {
+               if ( !$this->mPage->hasViewableContent() && $wgSend404Code ) {
                        // If there's no backing content, send a 404 Not Found
                        // for better machine handling of broken links.
                        $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
@@ -991,63 +1111,126 @@ class Article extends Page {
        }
 
        /**
-        * Execute the uncached parse for action=view
+        * Generate the navigation links when browsing through an article revisions
+        * It shows the information as:
+        *   Revision as of \<date\>; view current revision
+        *   \<- Previous version | Next Version -\>
+        *
+        * @param $oldid String: revision ID of this article revision
         */
-       public function doViewParse() {
-               global $wgOut;
+       public function setOldSubtitle( $oldid = 0 ) {
+               global $wgLang, $wgOut, $wgUser, $wgRequest;
 
-               $oldid = $this->getOldID();
-               $parserOptions = $this->mPage->getParserOptions();
+               if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
+                       return;
+               }
 
-               # Render printable version, use printable version cache
-               $parserOptions->setIsPrintable( $wgOut->isPrintable() );
+               $unhide = $wgRequest->getInt( 'unhide' ) == 1;
 
-               # Don't show section-edit links on old revisions... this way lies madness.
-               if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
-                       $parserOptions->setEditSection( false );
+               # Cascade unhide param in links for easy deletion browsing
+               $extraParams = array();
+               if ( $wgRequest->getVal( 'unhide' ) ) {
+                       $extraParams['unhide'] = 1;
                }
 
-               $useParserCache = $this->useParserCache( $oldid );
-               $this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions );
-
-               return true;
-       }
+               $revision = Revision::newFromId( $oldid );
+               $timestamp = $revision->getTimestamp();
 
-       /**
-        * Try to fetch an expired entry from the parser cache. If it is present,
-        * output it and return true. If it is not present, output nothing and
-        * return false. This is used as a callback function for
-        * PoolCounter::executeProtected().
-        *
-        * @return boolean
-        */
-       public function tryDirtyCache() {
-               global $wgOut;
-               $parserCache = ParserCache::singleton();
-               $options = $this->mPage->getParserOptions();
+               $current = ( $oldid == $this->mPage->getLatest() );
+               $td = $wgLang->timeanddate( $timestamp, true );
+               $tddate = $wgLang->date( $timestamp, true );
+               $tdtime = $wgLang->time( $timestamp, true );
 
-               if ( $wgOut->isPrintable() ) {
-                       $options->setIsPrintable( true );
-                       $options->setEditSection( false );
-               }
+               # Show user links if allowed to see them. If hidden, then show them only if requested...
+               $userlinks = Linker::revUserTools( $revision, !$unhide );
 
-               $output = $parserCache->getDirty( $this, $options );
+               $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
+                       ? 'revision-info-current'
+                       : 'revision-info';
 
-               if ( $output ) {
-                       wfDebug( __METHOD__ . ": sending dirty output\n" );
-                       wfDebugLog( 'dirty', "dirty output " . $parserCache->getKey( $this, $options ) . "\n" );
-                       $wgOut->setSquidMaxage( 0 );
-                       $this->mParserOutput = $output;
-                       $wgOut->addParserOutput( $output );
-                       $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
+               $wgOut->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
+                       $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
+                       $tdtime, $revision->getUser() )->parse() . "</div>" );
 
-                       return true;
-               } else {
-                       wfDebugLog( 'dirty', "dirty missing\n" );
-                       wfDebug( __METHOD__ . ": no dirty cache\n" );
+               $lnk = $current
+                       ? wfMsgHtml( 'currentrevisionlink' )
+                       : Linker::link(
+                               $this->getTitle(),
+                               wfMsgHtml( 'currentrevisionlink' ),
+                               array(),
+                               $extraParams,
+                               array( 'known', 'noclasses' )
+                       );
+               $curdiff = $current
+                       ? wfMsgHtml( 'diff' )
+                       : Linker::link(
+                               $this->getTitle(),
+                               wfMsgHtml( 'diff' ),
+                               array(),
+                               array(
+                                       'diff' => 'cur',
+                                       'oldid' => $oldid
+                               ) + $extraParams,
+                               array( 'known', 'noclasses' )
+                       );
+               $prev = $this->getTitle()->getPreviousRevisionID( $oldid ) ;
+               $prevlink = $prev
+                       ? Linker::link(
+                               $this->getTitle(),
+                               wfMsgHtml( 'previousrevision' ),
+                               array(),
+                               array(
+                                       'direction' => 'prev',
+                                       'oldid' => $oldid
+                               ) + $extraParams,
+                               array( 'known', 'noclasses' )
+                       )
+                       : wfMsgHtml( 'previousrevision' );
+               $prevdiff = $prev
+                       ? Linker::link(
+                               $this->getTitle(),
+                               wfMsgHtml( 'diff' ),
+                               array(),
+                               array(
+                                       'diff' => 'prev',
+                                       'oldid' => $oldid
+                               ) + $extraParams,
+                               array( 'known', 'noclasses' )
+                       )
+                       : wfMsgHtml( 'diff' );
+               $nextlink = $current
+                       ? wfMsgHtml( 'nextrevision' )
+                       : Linker::link(
+                               $this->getTitle(),
+                               wfMsgHtml( 'nextrevision' ),
+                               array(),
+                               array(
+                                       'direction' => 'next',
+                                       'oldid' => $oldid
+                               ) + $extraParams,
+                               array( 'known', 'noclasses' )
+                       );
+               $nextdiff = $current
+                       ? wfMsgHtml( 'diff' )
+                       : Linker::link(
+                               $this->getTitle(),
+                               wfMsgHtml( 'diff' ),
+                               array(),
+                               array(
+                                       'diff' => 'next',
+                                       'oldid' => $oldid
+                               ) + $extraParams,
+                               array( 'known', 'noclasses' )
+                       );
 
-                       return false;
+               $cdel = Linker::getRevDeleteLink( $wgUser, $revision, $this->getTitle() );
+               if ( $cdel !== '' ) {
+                       $cdel .= ' ';
                }
+
+               $wgOut->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
+                       wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ),
+                       $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>" );
        }
 
        /**
@@ -1065,13 +1248,18 @@ class Article extends Page {
                        $target = array( $target );
                }
 
-               $imageDir = wfUILang()->getDir();
+               $lang = $this->getTitle()->getPageLanguage();
+               $imageDir = $lang->getDir();
 
                if ( $appendSubtitle ) {
                        $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
                }
 
                // the loop prepends the arrow image before the link, so the first case needs to be outside
+
+               /**
+                * @var $title Title
+                */
                $title = array_shift( $target );
 
                if ( $forceKnown ) {
@@ -1081,7 +1269,7 @@ class Article extends Page {
                }
 
                $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
-               $alt = wfUILang()->isRTL() ? '←' : '→';
+               $alt = $lang->isRTL() ? '←' : '→';
                // Automatically append redirect=no to each link, since most of them are redirect pages themselves.
                foreach ( $target as $rt ) {
                        $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) );
@@ -1099,119 +1287,14 @@ class Article extends Page {
        }
 
        /**
-        * Builds trackback links for article display if $wgUseTrackbacks is set to true
+        * Handle action=render
         */
-       public function addTrackbacks() {
+       public function render() {
                global $wgOut;
 
-               $dbr = wfGetDB( DB_SLAVE );
-               $tbs = $dbr->select( 'trackbacks',
-                       array( 'tb_id', 'tb_title', 'tb_url', 'tb_ex', 'tb_name' ),
-                       array( 'tb_page' => $this->mPage->getID() )
-               );
-
-               if ( !$dbr->numRows( $tbs ) ) {
-                       return;
-               }
-
-               $wgOut->preventClickjacking();
-
-               $tbtext = "";
-               foreach ( $tbs as $o ) {
-                       $rmvtxt = "";
-
-                       if ( $this->getContext()->getUser()->isAllowed( 'trackback' ) ) {
-                               $delurl = $this->getTitle()->getFullURL( "action=deletetrackback&tbid=" .
-                                       $o->tb_id . "&token=" . urlencode( $this->getContext()->getUser()->editToken() ) );
-                               $rmvtxt = wfMsg( 'trackbackremove', htmlspecialchars( $delurl ) );
-                       }
-
-                       $tbtext .= "\n";
-                       $tbtext .= wfMsgNoTrans( strlen( $o->tb_ex ) ? 'trackbackexcerpt' : 'trackback',
-                                       $o->tb_title,
-                                       $o->tb_url,
-                                       $o->tb_ex,
-                                       $o->tb_name,
-                                       $rmvtxt );
-               }
-
-               $wgOut->wrapWikiMsg( "<div id='mw_trackbacks'>\n$1\n</div>\n", array( 'trackbackbox', $tbtext ) );
-       }
-
-       /**
-        * Removes trackback record for current article from trackbacks table
-        * @deprecated since 1.19
-        */
-       public function deletetrackback() {
-               return Action::factory( 'deletetrackback', $this )->show();
-       }
-
-       /**
-        * Handle action=render
-        */
-
-       public function render() {
-               global $wgOut;
-
-               $wgOut->setArticleBodyOnly( true );
-               $this->view();
-       }
-
-       /**
-        * Handle action=purge
-        */
-       public function purge() {
-               return Action::factory( 'purge', $this )->show();
-       }
-
-       /**
-        * Mark this particular edit/page as patrolled
-        * @deprecated since 1.19
-        */
-       public function markpatrolled() {
-               Action::factory( 'markpatrolled', $this )->show();
-       }
-
-       /**
-        * User-interface handler for the "watch" action.
-        * Requires Request to pass a token as of 1.19.
-        * @deprecated since 1.18
-        */
-       public function watch() {
-               Action::factory( 'watch', $this )->show();
-       }
-
-       /**
-        * Add this page to $wgUser's watchlist
-        *
-        * This is safe to be called multiple times
-        *
-        * @return bool true on successful watch operation
-        * @deprecated since 1.18
-        */
-       public function doWatch() {
-               global $wgUser;
-               return WatchAction::doWatch( $this->getTitle(), $wgUser );
-       }
-
-       /**
-        * User interface handler for the "unwatch" action.
-        * Requires Request to pass a token as of 1.19.
-        * @deprecated since 1.18
-        */
-       public function unwatch() {
-               Action::factory( 'unwatch', $this )->show();
-       }
-
-       /**
-        * Stop watching a page
-        * @return bool true on successful unwatch
-        * @deprecated since 1.18
-        */
-       public function doUnwatch() {
-               global $wgUser;
-               return WatchAction::doUnwatch( $this->getTitle(), $wgUser );
-       }
+               $wgOut->setArticleBodyOnly( true );
+               $this->view();
+       }
 
        /**
         * action=protect handler
@@ -1228,217 +1311,70 @@ class Article extends Page {
                $this->protect();
        }
 
-       /**
-        * Info about this page
-        * Called for ?action=info when $wgAllowPageInfo is on.
-        */
-       public function info() {
-               Action::factory( 'info', $this )->show();
-       }
-
-       /**
-        * Output a redirect back to the article.
-        * This is typically used after an edit.
-        *
-        * @deprecated in 1.19; call $wgOut->redirect() directly
-        * @param $noRedir Boolean: add redirect=no
-        * @param $sectionAnchor String: section to redirect to, including "#"
-        * @param $extraQuery String: extra query params
-        */
-       public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
-               wfDeprecated( __METHOD__ );
-               global $wgOut;
-
-               if ( $noRedir ) {
-                       $query = 'redirect=no';
-                       if ( $extraQuery )
-                               $query .= "&$extraQuery";
-               } else {
-                       $query = $extraQuery;
-               }
-
-               $wgOut->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor );
-       }
-
-       /**
-        * Auto-generates a deletion reason
-        *
-        * @param &$hasHistory Boolean: whether the page has a history
-        * @return mixed String containing deletion reason or empty string, or boolean false
-        *    if no revision occurred
-        */
-       public function generateReason( &$hasHistory ) {
-               global $wgContLang;
-
-               $dbw = wfGetDB( DB_MASTER );
-               // Get the last revision
-               $rev = Revision::newFromTitle( $this->getTitle() );
-
-               if ( is_null( $rev ) ) {
-                       return false;
-               }
-
-               // Get the article's contents
-               $contents = $rev->getText();
-               $blank = false;
-
-               // If the page is blank, use the text from the previous revision,
-               // which can only be blank if there's a move/import/protect dummy revision involved
-               if ( $contents == '' ) {
-                       $prev = $rev->getPrevious();
-
-                       if ( $prev )    {
-                               $contents = $prev->getText();
-                               $blank = true;
-                       }
-               }
-
-               // Find out if there was only one contributor
-               // Only scan the last 20 revisions
-               $res = $dbw->select( 'revision', 'rev_user_text',
-                       array( 'rev_page' => $this->mPage->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
-                       __METHOD__,
-                       array( 'LIMIT' => 20 )
-               );
-
-               if ( $res === false ) {
-                       // This page has no revisions, which is very weird
-                       return false;
-               }
-
-               $hasHistory = ( $res->numRows() > 1 );
-               $row = $dbw->fetchObject( $res );
-
-               if ( $row ) { // $row is false if the only contributor is hidden
-                       $onlyAuthor = $row->rev_user_text;
-                       // Try to find a second contributor
-                       foreach ( $res as $row ) {
-                               if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
-                                       $onlyAuthor = false;
-                                       break;
-                               }
-                       }
-               } else {
-                       $onlyAuthor = false;
-               }
-
-               // Generate the summary with a '$1' placeholder
-               if ( $blank ) {
-                       // The current revision is blank and the one before is also
-                       // blank. It's just not our lucky day
-                       $reason = wfMsgForContent( 'exbeforeblank', '$1' );
-               } else {
-                       if ( $onlyAuthor ) {
-                               $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
-                       } else {
-                               $reason = wfMsgForContent( 'excontent', '$1' );
-                       }
-               }
-
-               if ( $reason == '-' ) {
-                       // Allow these UI messages to be blanked out cleanly
-                       return '';
-               }
-
-               // Replace newlines with spaces to prevent uglyness
-               $contents = preg_replace( "/[\n\r]/", ' ', $contents );
-               // Calculate the maximum amount of chars to get
-               // Max content length = max comment length - length of the comment (excl. $1)
-               $maxLength = 255 - ( strlen( $reason ) - 2 );
-               $contents = $wgContLang->truncate( $contents, $maxLength );
-               // Remove possible unfinished links
-               $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
-               // Now replace the '$1' placeholder
-               $reason = str_replace( '$1', $contents, $reason );
-
-               return $reason;
-       }
-
-
        /**
         * UI entry point for page deletion
         */
        public function delete() {
-               global $wgOut, $wgRequest;
+               global $wgOut, $wgRequest, $wgLang;
 
-               $confirm = $wgRequest->wasPosted() &&
-                               $this->getContext()->getUser()->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
-
-               $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
-               $this->DeleteReason = $wgRequest->getText( 'wpReason' );
+               # This code desperately needs to be totally rewritten
 
-               $reason = $this->DeleteReasonList;
+               $title = $this->getTitle();
+               $user = $this->getContext()->getUser();
 
-               if ( $reason != 'other' && $this->DeleteReason != '' ) {
-                       // Entry from drop down menu + additional comment
-                       $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason;
-               } elseif ( $reason == 'other' ) {
-                       $reason = $this->DeleteReason;
+               # Check permissions
+               $permission_errors = $title->getUserPermissionsErrors( 'delete', $user );
+               if ( count( $permission_errors ) ) {
+                       throw new PermissionsError( 'delete', $permission_errors );
                }
 
-               # Flag to hide all contents of the archived revisions
-               $suppress = $wgRequest->getVal( 'wpSuppress' ) && $this->getContext()->getUser()->isAllowed( 'suppressrevision' );
-
-               # This code desperately needs to be totally rewritten
-
                # Read-only check...
                if ( wfReadOnly() ) {
-                       $wgOut->readOnlyPage();
-
-                       return;
-               }
-
-               # Check permissions
-               $permission_errors = $this->getTitle()->getUserPermissionsErrors( 'delete', $this->getContext()->getUser() );
-
-               if ( count( $permission_errors ) > 0 ) {
-                       $wgOut->showPermissionsErrorPage( $permission_errors );
-
-                       return;
+                       throw new ReadOnlyError;
                }
 
-               $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
-
                # Better double-check that it hasn't been deleted yet!
                $dbw = wfGetDB( DB_MASTER );
-               $conds = $this->getTitle()->pageCond();
+               $conds = $title->pageCond();
                $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
                if ( $latest === false ) {
-                       $wgOut->showFatalError(
-                               Html::rawElement(
-                                       'div',
-                                       array( 'class' => 'error mw-error-cannotdelete' ),
-                                       wfMsgExt( 'cannotdelete', array( 'parse' ),
-                                               wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
-                               )
-                       );
+                       $wgOut->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) );
+                       $wgOut->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
+                                       array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) )
+                               );
                        $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
                        LogEventsList::showLogExtract(
                                $wgOut,
                                'delete',
-                               $this->getTitle()->getPrefixedText()
+                               $title->getPrefixedText()
                        );
 
                        return;
                }
 
-               # Hack for big sites
-               $bigHistory = $this->mPage->isBigDeletion();
-               if ( $bigHistory && !$this->getTitle()->userCan( 'bigdelete' ) ) {
-                       global $wgLang, $wgDeleteRevisionsLimit;
+               $deleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
+               $deleteReason = $wgRequest->getText( 'wpReason' );
 
-                       $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
-                               array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
-
-                       return;
+               if ( $deleteReasonList == 'other' ) {
+                       $reason = $deleteReason;
+               } elseif ( $deleteReason != '' ) {
+                       // Entry from drop down menu + additional comment
+                       $reason = $deleteReasonList . wfMsgForContent( 'colon-separator' ) . $deleteReason;
+               } else {
+                       $reason = $deleteReasonList;
                }
 
-               if ( $confirm ) {
+               if ( $wgRequest->wasPosted() && $user->matchEditToken( $wgRequest->getVal( 'wpEditToken' ),
+                       array( 'delete', $this->getTitle()->getPrefixedText() ) ) )
+               {
+                       # Flag to hide all contents of the archived revisions
+                       $suppress = $wgRequest->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
+
                        $this->doDelete( $reason, $suppress );
 
-                       if ( $wgRequest->getCheck( 'wpWatch' ) && $this->getContext()->getUser()->isLoggedIn() ) {
+                       if ( $wgRequest->getCheck( 'wpWatch' ) && $user->isLoggedIn() ) {
                                $this->doWatch();
-                       } elseif ( $this->getTitle()->userIsWatching() ) {
+                       } elseif ( $title->userIsWatching() ) {
                                $this->doUnwatch();
                        }
 
@@ -1448,25 +1384,29 @@ class Article extends Page {
                // Generate deletion reason
                $hasHistory = false;
                if ( !$reason ) {
-                       $reason = $this->generateReason( $hasHistory );
+            try {
+                $reason = $this->generateReason( $hasHistory );
+            } catch (MWException $e) {
+                # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here.
+                wfDebug("Error while building auto delete summary: $e");
+                $reason = '';
+            }
                }
 
                // If the page has a history, insert a warning
-               if ( $hasHistory && !$confirm ) {
-                       global $wgLang;
-
-                       $revisions = $this->mPage->estimateRevisionCount();
+               if ( $hasHistory ) {
+                       $revisions = $this->mTitle->estimateRevisionCount();
                        // @todo FIXME: i18n issue/patchwork message
                        $wgOut->addHTML( '<strong class="mw-delete-warning-revisions">' .
                                wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) .
-                               wfMsgHtml( 'word-separator' ) . Linker::link( $this->getTitle(),
+                               wfMsgHtml( 'word-separator' ) . Linker::link( $title,
                                        wfMsgHtml( 'history' ),
                                        array( 'rel' => 'archives' ),
                                        array( 'action' => 'history' ) ) .
                                '</strong>'
                        );
 
-                       if ( $bigHistory ) {
+                       if ( $this->mTitle->isBigDeletion() ) {
                                global $wgDeleteRevisionsLimit;
                                $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
                                        array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
@@ -1486,14 +1426,16 @@ class Article extends Page {
 
                wfDebug( "Article::confirmDelete\n" );
 
-               $deleteBackLink = Linker::linkKnown( $this->getTitle() );
-               $wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) );
+               $wgOut->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
+               $wgOut->addBacklinkSubtitle( $this->getTitle() );
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
                $wgOut->addWikiMsg( 'confirmdeletetext' );
 
                wfRunHooks( 'ArticleConfirmDelete', array( $this, $wgOut, &$reason ) );
 
-               if ( $this->getContext()->getUser()->isAllowed( 'suppressrevision' ) ) {
+               $user = $this->getContext()->getUser();
+
+               if ( $user->isAllowed( 'suppressrevision' ) ) {
                        $suppress = "<tr id=\"wpDeleteSuppressRow\">
                                        <td></td>
                                        <td class='mw-input'><strong>" .
@@ -1504,7 +1446,7 @@ class Article extends Page {
                } else {
                        $suppress = '';
                }
-               $checkWatch = $this->getContext()->getUser()->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching();
+               $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching();
 
                $form = Xml::openElement( 'form', array( 'method' => 'post',
                        'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
@@ -1537,7 +1479,7 @@ class Article extends Page {
                        </tr>";
 
                # Disallow watching if user is not logged in
-               if ( $this->getContext()->getUser()->isLoggedIn() ) {
+               if ( $user->isLoggedIn() ) {
                        $form .= "
                        <tr>
                                <td></td>
@@ -1559,10 +1501,10 @@ class Article extends Page {
                        </tr>" .
                        Xml::closeElement( 'table' ) .
                        Xml::closeElement( 'fieldset' ) .
-                       Html::hidden( 'wpEditToken', $this->getContext()->getUser()->editToken() ) .
+                       Html::hidden( 'wpEditToken', $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) ) ) .
                        Xml::closeElement( 'form' );
 
-                       if ( $this->getContext()->getUser()->isAllowed( 'editinterface' ) ) {
+                       if ( $user->isAllowed( 'editinterface' ) ) {
                                $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
                                $link = Linker::link(
                                        $title,
@@ -1582,17 +1524,17 @@ class Article extends Page {
 
        /**
         * Perform a deletion and output success or failure messages
+        * @param $reason
+        * @param $suppress bool
         */
        public function doDelete( $reason, $suppress = false ) {
                global $wgOut;
 
-               $id = $this->getTitle()->getArticleID( Title::GAID_FOR_UPDATE );
-
                $error = '';
-               if ( $this->mPage->doDeleteArticle( $reason, $suppress, $id, $error ) ) {
+               if ( $this->mPage->doDeleteArticle( $reason, $suppress, 0, true, $error ) ) {
                        $deleted = $this->getTitle()->getPrefixedText();
 
-                       $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
+                       $wgOut->setPageTitle( wfMessage( 'actioncomplete' ) );
                        $wgOut->setRobotPolicy( 'noindex,nofollow' );
 
                        $loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
@@ -1600,16 +1542,11 @@ class Article extends Page {
                        $wgOut->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
                        $wgOut->returnToMain( false );
                } else {
+                       $wgOut->setPageTitle( wfMessage( 'cannotdelete-title', $this->getTitle()->getPrefixedText() ) );
                        if ( $error == '' ) {
-                               $wgOut->showFatalError(
-                                       Html::rawElement(
-                                               'div',
-                                               array( 'class' => 'error mw-error-cannotdelete' ),
-                                               wfMsgExt( 'cannotdelete', array( 'parse' ),
-                                                       wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
-                                       )
+                               $wgOut->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
+                                       array( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
                                );
-
                                $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
 
                                LogEventsList::showLogExtract(
@@ -1618,441 +1555,236 @@ class Article extends Page {
                                        $this->getTitle()->getPrefixedText()
                                );
                        } else {
-                               $wgOut->showFatalError( $error );
+                               $wgOut->addHTML( $error );
                        }
                }
        }
 
+       /* Caching functions */
+
        /**
-        * User interface for rollback operations
+        * checkLastModified returns true if it has taken care of all
+        * output to the client that is necessary for this request.
+        * (that is, it has sent a cached version of the page)
+        *
+        * @return boolean true if cached version send, false otherwise
         */
-       public function rollback() {
-               global $wgUser, $wgOut, $wgRequest;
-
-               $details = null;
-
-               $result = $this->mPage->doRollback(
-                       $wgRequest->getVal( 'from' ),
-                       $wgRequest->getText( 'summary' ),
-                       $wgRequest->getVal( 'token' ),
-                       $wgRequest->getBool( 'bot' ),
-                       $details
-               );
+       protected function tryFileCache() {
+               static $called = false;
 
-               if ( in_array( array( 'actionthrottledtext' ), $result ) ) {
-                       $wgOut->rateLimited();
-                       return;
+               if ( $called ) {
+                       wfDebug( "Article::tryFileCache(): called twice!?\n" );
+                       return false;
                }
 
-               if ( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ) {
-                       $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
-                       $errArray = $result[0];
-                       $errMsg = array_shift( $errArray );
-                       $wgOut->addWikiMsgArray( $errMsg, $errArray );
-
-                       if ( isset( $details['current'] ) ) {
-                               $current = $details['current'];
-
-                               if ( $current->getComment() != '' ) {
-                                       $wgOut->addWikiMsgArray( 'editcomment', array(
-                                               Linker::formatComment( $current->getComment() ) ), array( 'replaceafter' ) );
-                               }
+               $called = true;
+               if ( $this->isFileCacheable() ) {
+                       $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'view' );
+                       if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
+                               wfDebug( "Article::tryFileCache(): about to load file\n" );
+                               $cache->loadFromFileCache( $this->getContext() );
+                               return true;
+                       } else {
+                               wfDebug( "Article::tryFileCache(): starting buffer\n" );
+                               ob_start( array( &$cache, 'saveToFileCache' ) );
                        }
-
-                       return;
+               } else {
+                       wfDebug( "Article::tryFileCache(): not cacheable\n" );
                }
 
-               # Display permissions errors before read-only message -- there's no
-               # point in misleading the user into thinking the inability to rollback
-               # is only temporary.
-               if ( !empty( $result ) && $result !== array( array( 'readonlytext' ) ) ) {
-                       # array_diff is completely broken for arrays of arrays, sigh.
-                       # Remove any 'readonlytext' error manually.
-                       $out = array();
-                       foreach ( $result as $error ) {
-                               if ( $error != array( 'readonlytext' ) ) {
-                                       $out [] = $error;
-                               }
-                       }
-                       $wgOut->showPermissionsErrorPage( $out );
+               return false;
+       }
 
-                       return;
+       /**
+        * Check if the page can be cached
+        * @return bool
+        */
+       public function isFileCacheable() {
+               $cacheable = false;
+
+               if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
+                       $cacheable = $this->mPage->getID()
+                               && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
+                       // Extension may have reason to disable file caching on some pages.
+                       if ( $cacheable ) {
+                               $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
+                       }
                }
 
-               if ( $result == array( array( 'readonlytext' ) ) ) {
-                       $wgOut->readOnlyPage();
+               return $cacheable;
+       }
 
-                       return;
-               }
+       /**#@-*/
 
-               $current = $details['current'];
-               $target = $details['target'];
-               $newId = $details['newid'];
-               $wgOut->setPageTitle( wfMsg( 'actioncomplete' ) );
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
+       /**
+        * Lightweight method to get the parser output for a page, checking the parser cache
+        * and so on. Doesn't consider most of the stuff that WikiPage::view is forced to
+        * consider, so it's not appropriate to use there.
+        *
+        * @since 1.16 (r52326) for LiquidThreads
+        *
+        * @param $oldid mixed integer Revision ID or null
+        * @param $user User The relevant user
+        * @return ParserOutput or false if the given revsion ID is not found
+        */
+       public function getParserOutput( $oldid = null, User $user = null ) {
+               global $wgUser;
 
-               if ( $current->getUserText() === '' ) {
-                       $old = wfMsg( 'rev-deleted-user' );
-               } else {
-                       $old = Linker::userLink( $current->getUser(), $current->getUserText() )
-                               . Linker::userToolLinks( $current->getUser(), $current->getUserText() );
-               }
+               $user = is_null( $user ) ? $wgUser : $user;
+               $parserOptions = $this->mPage->makeParserOptions( $user );
 
-               $new = Linker::userLink( $target->getUser(), $target->getUserText() )
-                       . Linker::userToolLinks( $target->getUser(), $target->getUserText() );
-               $wgOut->addHTML( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
-               $wgOut->returnToMain( false, $this->getTitle() );
+               return $this->mPage->getParserOutput( $parserOptions, $oldid );
+       }
 
-               if ( !$wgRequest->getBool( 'hidediff', false ) && !$wgUser->getBoolOption( 'norollbackdiff', false ) ) {
-                       $de = new DifferenceEngine( $this->getTitle(), $current->getId(), $newId, false, true );
-                       $de->showDiff( '', '' );
+       /**
+        * Get parser options suitable for rendering the primary article wikitext
+        * @return ParserOptions|false
+        */
+       public function getParserOptions() {
+               global $wgUser;
+               if ( !$this->mParserOptions ) {
+                       $this->mParserOptions = $this->mPage->makeParserOptions( $wgUser );
                }
+               // Clone to allow modifications of the return value without affecting cache
+               return clone $this->mParserOptions;
        }
 
        /**
-        * Generate the navigation links when browsing through an article revisions
-        * It shows the information as:
-        *   Revision as of \<date\>; view current revision
-        *   \<- Previous version | Next Version -\>
+        * Sets the context this Article is executed in
         *
-        * @param $oldid String: revision ID of this article revision
+        * @param $context IContextSource
+        * @since 1.18
         */
-       public function setOldSubtitle( $oldid = 0 ) {
-               global $wgLang, $wgOut, $wgUser, $wgRequest;
-
-               if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
-                       return;
-               }
-
-               $unhide = $wgRequest->getInt( 'unhide' ) == 1;
-
-               # Cascade unhide param in links for easy deletion browsing
-               $extraParams = array();
-               if ( $wgRequest->getVal( 'unhide' ) ) {
-                       $extraParams['unhide'] = 1;
-               }
-
-               $revision = Revision::newFromId( $oldid );
-               $timestamp = $revision->getTimestamp();
-
-               $current = ( $oldid == $this->mPage->getLatest() );
-               $td = $wgLang->timeanddate( $timestamp, true );
-               $tddate = $wgLang->date( $timestamp, true );
-               $tdtime = $wgLang->time( $timestamp, true );
-
-               $lnk = $current
-                       ? wfMsgHtml( 'currentrevisionlink' )
-                       : Linker::link(
-                               $this->getTitle(),
-                               wfMsgHtml( 'currentrevisionlink' ),
-                               array(),
-                               $extraParams,
-                               array( 'known', 'noclasses' )
-                       );
-               $curdiff = $current
-                       ? wfMsgHtml( 'diff' )
-                       : Linker::link(
-                               $this->getTitle(),
-                               wfMsgHtml( 'diff' ),
-                               array(),
-                               array(
-                                       'diff' => 'cur',
-                                       'oldid' => $oldid
-                               ) + $extraParams,
-                               array( 'known', 'noclasses' )
-                       );
-               $prev = $this->getTitle()->getPreviousRevisionID( $oldid ) ;
-               $prevlink = $prev
-                       ? Linker::link(
-                               $this->getTitle(),
-                               wfMsgHtml( 'previousrevision' ),
-                               array(),
-                               array(
-                                       'direction' => 'prev',
-                                       'oldid' => $oldid
-                               ) + $extraParams,
-                               array( 'known', 'noclasses' )
-                       )
-                       : wfMsgHtml( 'previousrevision' );
-               $prevdiff = $prev
-                       ? Linker::link(
-                               $this->getTitle(),
-                               wfMsgHtml( 'diff' ),
-                               array(),
-                               array(
-                                       'diff' => 'prev',
-                                       'oldid' => $oldid
-                               ) + $extraParams,
-                               array( 'known', 'noclasses' )
-                       )
-                       : wfMsgHtml( 'diff' );
-               $nextlink = $current
-                       ? wfMsgHtml( 'nextrevision' )
-                       : Linker::link(
-                               $this->getTitle(),
-                               wfMsgHtml( 'nextrevision' ),
-                               array(),
-                               array(
-                                       'direction' => 'next',
-                                       'oldid' => $oldid
-                               ) + $extraParams,
-                               array( 'known', 'noclasses' )
-                       );
-               $nextdiff = $current
-                       ? wfMsgHtml( 'diff' )
-                       : Linker::link(
-                               $this->getTitle(),
-                               wfMsgHtml( 'diff' ),
-                               array(),
-                               array(
-                                       'diff' => 'next',
-                                       'oldid' => $oldid
-                               ) + $extraParams,
-                               array( 'known', 'noclasses' )
-                       );
-
-               $cdel = '';
-
-               // User can delete revisions or view deleted revisions...
-               $canHide = $wgUser->isAllowed( 'deleterevision' );
-               if ( $canHide || ( $revision->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) ) {
-                       if ( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) {
-                               $cdel = Linker::revDeleteLinkDisabled( $canHide ); // rev was hidden from Sysops
-                       } else {
-                               $query = array(
-                                       'type'   => 'revision',
-                                       'target' => $this->getTitle()->getPrefixedDbkey(),
-                                       'ids'    => $oldid
-                               );
-                               $cdel = Linker::revDeleteLink( $query, $revision->isDeleted( File::DELETED_RESTRICTED ), $canHide );
-                       }
-                       $cdel .= ' ';
-               }
-
-               # Show user links if allowed to see them. If hidden, then show them only if requested...
-               $userlinks = Linker::revUserTools( $revision, !$unhide );
-
-               $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
-                       ? 'revision-info-current'
-                       : 'revision-info';
-
-               $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" .
-                       wfMsgExt(
-                               $infomsg,
-                               array( 'parseinline', 'replaceafter' ),
-                               $td,
-                               $userlinks,
-                               $revision->getID(),
-                               $tddate,
-                               $tdtime,
-                               $revision->getUser()
-                       ) .
-                       "</div>\n" .
-                       "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ),
-                       $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
-
-               $wgOut->addHTML( $r );
-       }
-
-       /* Caching functions */
+       public function setContext( $context ) {
+               $this->mContext = $context;
+       }
 
        /**
-        * checkLastModified returns true if it has taken care of all
-        * output to the client that is necessary for this request.
-        * (that is, it has sent a cached version of the page)
+        * Gets the context this Article is executed in
         *
-        * @return boolean true if cached version send, false otherwise
+        * @return IContextSource
+        * @since 1.18
         */
-       protected function tryFileCache() {
-               static $called = false;
-
-               if ( $called ) {
-                       wfDebug( "Article::tryFileCache(): called twice!?\n" );
-                       return false;
-               }
-
-               $called = true;
-               if ( $this->isFileCacheable() ) {
-                       $cache = new HTMLFileCache( $this->getTitle() );
-                       if ( $cache->isFileCacheGood( $this->mPage->getTouched() ) ) {
-                               wfDebug( "Article::tryFileCache(): about to load file\n" );
-                               $cache->loadFromFileCache();
-                               return true;
-                       } else {
-                               wfDebug( "Article::tryFileCache(): starting buffer\n" );
-                               ob_start( array( &$cache, 'saveToFileCache' ) );
-                       }
+       public function getContext() {
+               if ( $this->mContext instanceof IContextSource ) {
+                       return $this->mContext;
                } else {
-                       wfDebug( "Article::tryFileCache(): not cacheable\n" );
+                       wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
+                       return RequestContext::getMain();
                }
-
-               return false;
        }
 
        /**
-        * Check if the page can be cached
-        * @return bool
+        * Info about this page
+        * @deprecated since 1.19
         */
-       public function isFileCacheable() {
-               $cacheable = false;
-
-               if ( HTMLFileCache::useFileCache() ) {
-                       $cacheable = $this->mPage->getID() && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
-                       // Extension may have reason to disable file caching on some pages.
-                       if ( $cacheable ) {
-                               $cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
-                       }
-               }
+       public function info() {
+               wfDeprecated( __METHOD__, '1.19' );
+               Action::factory( 'info', $this )->show();
+       }
 
-               return $cacheable;
+       /**
+        * Mark this particular edit/page as patrolled
+        * @deprecated since 1.18
+        */
+       public function markpatrolled() {
+               wfDeprecated( __METHOD__, '1.18' );
+               Action::factory( 'markpatrolled', $this )->show();
        }
 
-       /**#@-*/
+       /**
+        * Handle action=purge
+        * @deprecated since 1.19
+        */
+       public function purge() {
+               return Action::factory( 'purge', $this )->show();
+       }
 
        /**
-        * Overriden by ImagePage class, only present here to avoid a fatal error
-        * Called for ?action=revert
+        * Handle action=revert
+        * @deprecated since 1.19
         */
        public function revert() {
-               global $wgOut;
-               $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
+               wfDeprecated( __METHOD__, '1.19' );
+               Action::factory( 'revert', $this )->show();
        }
 
        /**
-        * Add the primary page-view wikitext to the output buffer
-        * Saves the text into the parser cache if possible.
-        * Updates templatelinks if it is out of date.
-        *
-        * @param $text String
-        * @param $cache Boolean
-        * @param $parserOptions mixed ParserOptions object, or boolean false
+        * Handle action=rollback
+        * @deprecated since 1.19
         */
-       public function outputWikiText( $text, $cache = true, $parserOptions = false ) {
-               global $wgOut;
-
-               $this->mParserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions );
-               $wgOut->addParserOutput( $this->mParserOutput );
+       public function rollback() {
+               wfDeprecated( __METHOD__, '1.19' );
+               Action::factory( 'rollback', $this )->show();
        }
 
        /**
-        * This does all the heavy lifting for outputWikitext, except it returns the parser
-        * output instead of sending it straight to $wgOut. Makes things nice and simple for,
-        * say, embedding thread pages within a discussion system (LiquidThreads)
-        *
-        * @param $text string
-        * @param $cache boolean
-        * @param $parserOptions parsing options, defaults to false
-        * @return string containing parsed output
+        * User-interface handler for the "watch" action.
+        * Requires Request to pass a token as of 1.18.
+        * @deprecated since 1.18
         */
-       public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) {
-               global $wgParser, $wgEnableParserCache, $wgUseFileCache;
-
-               if ( !$parserOptions ) {
-                       $parserOptions = $this->mPage->getParserOptions();
-               }
-
-               $time = - wfTime();
-               $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(),
-                       $parserOptions, true, true, $this->getRevIdFetched() );
-               $time += wfTime();
-
-               # Timing hack
-               if ( $time > 3 ) {
-                       wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
-                               $this->getTitle()->getPrefixedDBkey() ) );
-               }
-
-               if ( $wgEnableParserCache && $cache && $this->mParserOutput->isCacheable() ) {
-                       $parserCache = ParserCache::singleton();
-                       $parserCache->save( $this->mParserOutput, $this, $parserOptions );
-               }
-
-               // Make sure file cache is not used on uncacheable content.
-               // Output that has magic words in it can still use the parser cache
-               // (if enabled), though it will generally expire sooner.
-               if ( !$this->mParserOutput->isCacheable() || $this->mParserOutput->containsOldMagic() ) {
-                       $wgUseFileCache = false;
-               }
-
-               $this->doCascadeProtectionUpdates( $this->mParserOutput );
-
-               return $this->mParserOutput;
+       public function watch() {
+               wfDeprecated( __METHOD__, '1.18' );
+               Action::factory( 'watch', $this )->show();
        }
 
        /**
-        * Updates cascading protections
+        * Add this page to $wgUser's watchlist
         *
-        * @param $parserOutput ParserOutput object, or boolean false
-        **/
-       protected function doCascadeProtectionUpdates( $parserOutput ) {
-               if ( !$this->isCurrent() || wfReadOnly() || !$this->getTitle()->areRestrictionsCascading() ) {
-                       return;
-               }
-
-               // templatelinks table may have become out of sync,
-               // especially if using variable-based transclusions.
-               // For paranoia, check if things have changed and if
-               // so apply updates to the database. This will ensure
-               // that cascaded protections apply as soon as the changes
-               // are visible.
-
-               # Get templates from templatelinks
-               $id = $this->getTitle()->getArticleID();
-
-               $tlTemplates = array();
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( array( 'templatelinks' ),
-                       array( 'tl_namespace', 'tl_title' ),
-                       array( 'tl_from' => $id ),
-                       __METHOD__
-               );
-
-               foreach ( $res as $row ) {
-                       $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
-               }
-
-               # Get templates from parser output.
-               $poTemplates = array();
-               foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
-                       foreach ( $templates as $dbk => $id ) {
-                               $poTemplates["$ns:$dbk"] = true;
-                       }
-               }
-
-               # Get the diff
-               $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
+        * This is safe to be called multiple times
+        *
+        * @return bool true on successful watch operation
+        * @deprecated since 1.18
+        */
+       public function doWatch() {
+               global $wgUser;
+               wfDeprecated( __METHOD__, '1.18' );
+               return WatchAction::doWatch( $this->getTitle(), $wgUser );
+       }
 
-               if ( count( $templates_diff ) > 0 ) {
-                       # Whee, link updates time.
-                       $u = new LinksUpdate( $this->getTitle(), $parserOutput, false );
-                       $u->doUpdate();
-               }
+       /**
+        * User interface handler for the "unwatch" action.
+        * Requires Request to pass a token as of 1.18.
+        * @deprecated since 1.18
+        */
+       public function unwatch() {
+               wfDeprecated( __METHOD__, '1.18' );
+               Action::factory( 'unwatch', $this )->show();
        }
 
        /**
-        * Sets the context this Article is executed in
-        *
-        * @param $context RequestContext
-        * @since 1.18
+        * Stop watching a page
+        * @return bool true on successful unwatch
+        * @deprecated since 1.18
         */
-       public function setContext( $context ) {
-               $this->mContext = $context;
+       public function doUnwatch() {
+               global $wgUser;
+               wfDeprecated( __METHOD__, '1.18' );
+               return WatchAction::doUnwatch( $this->getTitle(), $wgUser );
        }
 
        /**
-        * Gets the context this Article is executed in
+        * Output a redirect back to the article.
+        * This is typically used after an edit.
         *
-        * @return RequestContext
-        * @since 1.18
+        * @deprecated in 1.18; call $wgOut->redirect() directly
+        * @param $noRedir Boolean: add redirect=no
+        * @param $sectionAnchor String: section to redirect to, including "#"
+        * @param $extraQuery String: extra query params
         */
-       public function getContext() {
-               if ( $this->mContext instanceof RequestContext ) {
-                       return $this->mContext;
+       public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
+               wfDeprecated( __METHOD__, '1.18' );
+               global $wgOut;
+
+               if ( $noRedir ) {
+                       $query = 'redirect=no';
+                       if ( $extraQuery )
+                               $query .= "&$extraQuery";
                } else {
-                       wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
-                       return RequestContext::getMain();
+                       $query = $extraQuery;
                }
+
+               $wgOut->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor );
        }
 
        /**
@@ -2066,7 +1798,7 @@ class Article extends Page {
                        #wfWarn( "Access to raw $fname field " . __CLASS__ );
                        return $this->mPage->$fname;
                }
-        trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
+               trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
        }
 
        /**
@@ -2075,7 +1807,6 @@ class Article extends Page {
         *
         * @param $fname String Field name
         * @param $fvalue mixed New value
-        * @param $args Array Arguments to the method
         */
        public function __set( $fname, $fvalue ) {
                if ( property_exists( $this->mPage, $fname ) ) {
@@ -2085,7 +1816,7 @@ class Article extends Page {
                } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
                        $this->mPage->$fname = $fvalue;
                } else {
-                       trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
+                       trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
                }
        }
 
@@ -2101,101 +1832,124 @@ class Article extends Page {
                        #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" );
                        return call_user_func_array( array( $this->mPage, $fname ), $args );
                }
-        trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
+               trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
        }
 
        // ****** B/C functions to work-around PHP silliness with __call and references ****** //
+
+       /**
+        * @param $limit array
+        * @param $expiry array
+        * @param $cascade bool
+        * @param $reason string
+        * @param $user User
+        * @return Status
+        */
+       public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
+               return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
+       }
+
+       /**
+        * @param $limit array
+        * @param $reason string
+        * @param $cascade int
+        * @param $expiry array
+        * @return bool
+        */
        public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
                return $this->mPage->updateRestrictions( $limit, $reason, $cascade, $expiry );
        }
 
+       /**
+        * @param $reason string
+        * @param $suppress bool
+        * @param $id int
+        * @param $commit bool
+        * @param $error string
+        * @return bool
+        */
        public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
                return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
        }
 
-       public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
-               return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails );
+       /**
+        * @param $fromP
+        * @param $summary
+        * @param $token
+        * @param $bot
+        * @param $resultDetails
+        * @param $user User
+        * @return array
+        */
+       public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
+               global $wgUser;
+               $user = is_null( $user ) ? $wgUser : $user;
+               return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
        }
 
-       public function commitRollback( $fromP, $summary, $bot, &$resultDetails ) {
-               return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails );
+       /**
+        * @param $fromP
+        * @param $summary
+        * @param $bot
+        * @param $resultDetails
+        * @param $guser User
+        * @return array
+        */
+       public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
+               global $wgUser;
+               $guser = is_null( $guser ) ? $wgUser : $guser;
+               return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
+       }
+
+       /**
+        * @param $hasHistory bool
+        * @return mixed
+        */
+       public function generateReason( &$hasHistory ) {
+        $title = $this->mPage->getTitle();
+        $handler = ContentHandler::getForTitle( $title );
+               return $handler->getAutoDeleteReason( $title, $hasHistory );
        }
 
        // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
+
+       /**
+        * @return array
+        */
        public static function selectFields() {
                return WikiPage::selectFields();
        }
 
+       /**
+        * @param $title Title
+        */
        public static function onArticleCreate( $title ) {
-               return WikiPage::onArticleCreate( $title );
+               WikiPage::onArticleCreate( $title );
        }
 
+       /**
+        * @param $title Title
+        */
        public static function onArticleDelete( $title ) {
-               return WikiPage::onArticleDelete( $title );
-       }
-
-       public static function onArticleEdit( $title ) {
-               return WikiPage::onArticleEdit( $title );
-       }
-
-       public static function getAutosummary( $oldtext, $newtext, $flags ) {
-               return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
+               WikiPage::onArticleDelete( $title );
        }
-       // ******
-}
-
-class PoolWorkArticleView extends PoolCounterWork {
 
        /**
-        * @var Article
+        * @param $title Title
         */
-       private $mArticle;
-
-       function __construct( $article, $key, $useParserCache, $parserOptions ) {
-               parent::__construct( 'ArticleView', $key );
-               $this->mArticle = $article;
-               $this->cacheable = $useParserCache;
-               $this->parserOptions = $parserOptions;
-       }
-
-       function doWork() {
-               return $this->mArticle->doViewParse();
-       }
-
-       function getCachedWork() {
-               global $wgOut;
-
-               $parserCache = ParserCache::singleton();
-               $this->mArticle->mParserOutput = $parserCache->get( $this->mArticle, $this->parserOptions );
-
-               if ( $this->mArticle->mParserOutput !== false ) {
-                       wfDebug( __METHOD__ . ": showing contents parsed by someone else\n" );
-                       $wgOut->addParserOutput( $this->mArticle->mParserOutput );
-                       # Ensure that UI elements requiring revision ID have
-                       # the correct version information.
-                       $wgOut->setRevisionId( $this->mArticle->getLatest() );
-                       return true;
-               }
-               return false;
-       }
-
-       function fallback() {
-               return $this->mArticle->tryDirtyCache();
+       public static function onArticleEdit( $title ) {
+               WikiPage::onArticleEdit( $title );
        }
 
        /**
-        * @param  $status Status
+        * @param $oldtext
+        * @param $newtext
+        * @param $flags
+        * @return string
+     * @deprecated since 1.20, use ContentHandler::getAutosummary() instead
         */
-       function error( $status ) {
-               global $wgOut;
-
-               $wgOut->clearHTML(); // for release() errors
-               $wgOut->enableClientCache( false );
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-
-               $errortext = $status->getWikiText( false, 'view-pool-error' );
-               $wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
-
-               return false;
+       public static function getAutosummary( $oldtext, $newtext, $flags ) {
+               return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
        }
+       // ******
 }