Move implementations of Page to separate file
authorwithoutaname <drevitchi@gmail.com>
Sun, 22 Jun 2014 01:29:03 +0000 (18:29 -0700)
committerLegoktm <legoktm.wikipedia@gmail.com>
Sun, 22 Jun 2014 07:56:16 +0000 (07:56 +0000)
Moved implementations of the Page interface, including subclasses
of WikiPage and Article, to a separate /includes/page file.
Separated PoolWorkArticleView to the includes/poolcounter file.

Change-Id: I4557eab76e0cb12d9d7f93644c5831bdd5b472b0

14 files changed:
includes/Article.php [deleted file]
includes/AutoLoader.php
includes/CategoryPage.php [deleted file]
includes/ImagePage.php [deleted file]
includes/WikiCategoryPage.php [deleted file]
includes/WikiFilePage.php [deleted file]
includes/WikiPage.php [deleted file]
includes/page/Article.php [new file with mode: 0644]
includes/page/CategoryPage.php [new file with mode: 0644]
includes/page/ImagePage.php [new file with mode: 0644]
includes/page/WikiCategoryPage.php [new file with mode: 0644]
includes/page/WikiFilePage.php [new file with mode: 0644]
includes/page/WikiPage.php [new file with mode: 0644]
includes/poolcounter/PoolWorkArticleView.php [new file with mode: 0644]

diff --git a/includes/Article.php b/includes/Article.php
deleted file mode 100644 (file)
index c68c675..0000000
+++ /dev/null
@@ -1,2100 +0,0 @@
-<?php
-/**
- * User interface for page actions.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Class for viewing MediaWiki article and history.
- *
- * This maintains WikiPage functions for backwards compatibility.
- *
- * @todo Move and rewrite code to an Action class
- *
- * See design.txt for an overview.
- * Note: edit user interface and cache support functions have been
- * moved to separate EditPage and HTMLFileCache classes.
- *
- * @internal documentation reviewed 15 Mar 2010
- */
-class Article implements Page {
-       /** @var IContextSource The context this Article is executed in */
-       protected $mContext;
-
-       /** @var WikiPage The WikiPage object of this instance */
-       protected $mPage;
-
-       /** @var ParserOptions ParserOptions object for $wgUser articles */
-       public $mParserOptions;
-
-       /**
-        * @var string Text of the revision we are working on
-        * @todo BC cruft
-        */
-       public $mContent;
-
-       /**
-        * @var Content Content of the revision we are working on
-        * @since 1.21
-        */
-       protected $mContentObject;
-
-       /** @var bool Is the content ($mContent) already loaded? */
-       protected $mContentLoaded = false;
-
-       /** @var int|null The oldid of the article that is to be shown, 0 for the current revision */
-       protected $mOldId;
-
-       /** @var Title Title from which we were redirected here */
-       protected $mRedirectedFrom = null;
-
-       /** @var string|bool URL to redirect to or false if none */
-       protected $mRedirectUrl = false;
-
-       /** @var int Revision ID of revision we are working on */
-       protected $mRevIdFetched = 0;
-
-       /** @var Revision Revision we are working on */
-       protected $mRevision = null;
-
-       /** @var ParserOutput */
-       public $mParserOutput;
-
-       /**
-        * Constructor and clear the article
-        * @param Title $title Reference to a Title object.
-        * @param int $oldId Revision ID, null to fetch from request, zero for current
-        */
-       public function __construct( Title $title, $oldId = null ) {
-               $this->mOldId = $oldId;
-               $this->mPage = $this->newPage( $title );
-       }
-
-       /**
-        * @param Title $title
-        * @return WikiPage
-        */
-       protected function newPage( Title $title ) {
-               return new WikiPage( $title );
-       }
-
-       /**
-        * Constructor from a page id
-        * @param int $id Article ID to load
-        * @return Article|null
-        */
-       public static function newFromID( $id ) {
-               $t = Title::newFromID( $id );
-               # @todo FIXME: Doesn't inherit right
-               return $t == null ? null : new self( $t );
-               # return $t == null ? null : new static( $t ); // PHP 5.3
-       }
-
-       /**
-        * Create an Article object of the appropriate class for the given page.
-        *
-        * @param Title $title
-        * @param IContextSource $context
-        * @return Article
-        */
-       public static function newFromTitle( $title, IContextSource $context ) {
-               if ( NS_MEDIA == $title->getNamespace() ) {
-                       // FIXME: where should this go?
-                       $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
-               }
-
-               $page = null;
-               wfRunHooks( 'ArticleFromTitle', array( &$title, &$page, $context ) );
-               if ( !$page ) {
-                       switch ( $title->getNamespace() ) {
-                               case NS_FILE:
-                                       $page = new ImagePage( $title );
-                                       break;
-                               case NS_CATEGORY:
-                                       $page = new CategoryPage( $title );
-                                       break;
-                               default:
-                                       $page = new Article( $title );
-                       }
-               }
-               $page->setContext( $context );
-
-               return $page;
-       }
-
-       /**
-        * Create an Article object of the appropriate class for the given page.
-        *
-        * @param WikiPage $page
-        * @param IContextSource $context
-        * @return Article
-        */
-       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.
-        * @param Title $from
-        */
-       public function setRedirectedFrom( Title $from ) {
-               $this->mRedirectedFrom = $from;
-       }
-
-       /**
-        * Get the title object of the article
-        *
-        * @return Title Title object of this page
-        */
-       public function getTitle() {
-               return $this->mPage->getTitle();
-       }
-
-       /**
-        * Get the WikiPage object of this instance
-        *
-        * @since 1.19
-        * @return WikiPage
-        */
-       public function getPage() {
-               return $this->mPage;
-       }
-
-       /**
-        * Clear the object
-        */
-       public function clear() {
-               $this->mContentLoaded = false;
-
-               $this->mRedirectedFrom = null; # Title object if set
-               $this->mRevIdFetched = 0;
-               $this->mRedirectUrl = false;
-
-               $this->mPage->clear();
-       }
-
-       /**
-        * 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.
-        *
-        * @deprecated since 1.21; use WikiPage::getContent() instead
-        *
-        * @return string Return the text of this revision
-        */
-       public function getContent() {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-               $content = $this->getContentObject();
-               return ContentHandler::getContentText( $content );
-       }
-
-       /**
-        * Returns a Content object representing the pages effective display content,
-        * not necessarily the revision's 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 Content Return the content of this revision
-        *
-        * @since 1.21
-        */
-       protected function getContentObject() {
-               wfProfileIn( __METHOD__ );
-
-               if ( $this->mPage->getID() === 0 ) {
-                       # If this is a MediaWiki:x message, then load the messages
-                       # and return the message value for x.
-                       if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
-                               $text = $this->getTitle()->getDefaultMessageText();
-                               if ( $text === false ) {
-                                       $text = '';
-                               }
-
-                               $content = ContentHandler::makeContent( $text, $this->getTitle() );
-                       } else {
-                               $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
-                               $content = new MessageContent( $message, null, 'parsemag' );
-                       }
-               } else {
-                       $this->fetchContentObject();
-                       $content = $this->mContentObject;
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $content;
-       }
-
-       /**
-        * @return int The oldid of the article that is to be shown, 0 for the current revision
-        */
-       public function getOldID() {
-               if ( is_null( $this->mOldId ) ) {
-                       $this->mOldId = $this->getOldIDFromRequest();
-               }
-
-               return $this->mOldId;
-       }
-
-       /**
-        * Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect
-        *
-        * @return int The old id for the request
-        */
-       public function getOldIDFromRequest() {
-               $this->mRedirectUrl = false;
-
-               $request = $this->getContext()->getRequest();
-               $oldid = $request->getIntOrNull( 'oldid' );
-
-               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.
-                       if ( $oldid === $this->mPage->getLatest() ) {
-                               $this->mRevision = $this->mPage->getRevision();
-                       } else {
-                               $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 ( $request->getVal( 'direction' ) == 'next' ) {
-                       $nextid = $this->getTitle()->getNextRevisionID( $oldid );
-                       if ( $nextid ) {
-                               $oldid = $nextid;
-                               $this->mRevision = null;
-                       } else {
-                               $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
-                       }
-               } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
-                       $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
-                       if ( $previd ) {
-                               $oldid = $previd;
-                               $this->mRevision = null;
-                       }
-               }
-
-               return $oldid;
-       }
-
-       /**
-        * Load the revision (including text) into this object
-        *
-        * @deprecated since 1.19; use fetchContent()
-        */
-       function loadContent() {
-               wfDeprecated( __METHOD__, '1.19' );
-               $this->fetchContent();
-       }
-
-       /**
-        * Get text of an article from database
-        * Does *NOT* follow redirects.
-        *
-        * @protected
-        * @note This is really internal functionality that should really NOT be
-        * used by other functions. For accessing article content, use the WikiPage
-        * class, especially WikiBase::getContent(). However, a lot of legacy code
-        * uses this method to retrieve page text from the database, so the function
-        * has to remain public for now.
-        *
-        * @return string|bool String containing article contents, or false if null
-        * @deprecated since 1.21, use WikiPage::getContent() instead
-        */
-       function fetchContent() { #BC cruft!
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-
-               if ( $this->mContentLoaded && $this->mContent ) {
-                       return $this->mContent;
-               }
-
-               wfProfileIn( __METHOD__ );
-
-               $content = $this->fetchContentObject();
-
-               if ( !$content ) {
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               // @todo Get rid of mContent everywhere!
-               $this->mContent = ContentHandler::getContentText( $content );
-               ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
-
-               wfProfileOut( __METHOD__ );
-
-               return $this->mContent;
-       }
-
-       /**
-        * Get text content object
-        * Does *NOT* follow redirects.
-        * @todo When is this null?
-        *
-        * @note Code that wants to retrieve page content from the database should
-        * use WikiPage::getContent().
-        *
-        * @return Content|null|bool
-        *
-        * @since 1.21
-        */
-       protected function fetchContentObject() {
-               if ( $this->mContentLoaded ) {
-                       return $this->mContentObject;
-               }
-
-               wfProfileIn( __METHOD__ );
-
-               $this->mContentLoaded = true;
-               $this->mContent = null;
-
-               $oldid = $this->getOldID();
-
-               # Pre-fill content with error message so that if something
-               # fails we'll have something telling us what we intended.
-               //XXX: this isn't page content but a UI message. horrible.
-               $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), 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...
-               // Loads if user is allowed
-               $this->mContentObject = $this->mRevision->getContent(
-                       Revision::FOR_THIS_USER,
-                       $this->getContext()->getUser()
-               );
-               $this->mRevIdFetched = $this->mRevision->getId();
-
-               wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
-
-               wfProfileOut( __METHOD__ );
-
-               return $this->mContentObject;
-       }
-
-       /**
-        * Returns true if the currently-referenced revision is the current edit
-        * to this page (and it exists).
-        * @return bool
-        */
-       public function isCurrent() {
-               # If no oldid, this is the current version.
-               if ( $this->getOldID() == 0 ) {
-                       return true;
-               }
-
-               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
-        *
-        * @return int Revision ID of last article revision
-        */
-       public function getRevIdFetched() {
-               if ( $this->mRevIdFetched ) {
-                       return $this->mRevIdFetched;
-               } else {
-                       return $this->mPage->getLatest();
-               }
-       }
-
-       /**
-        * This is the default action of the index.php entry point: just view the
-        * page of the given title.
-        */
-       public function view() {
-               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();
-
-               $user = $this->getContext()->getUser();
-               # Another whitelist check in case getOldID() is altering the title
-               $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
-               if ( count( $permErrors ) ) {
-                       wfDebug( __METHOD__ . ": denied on secondary read check\n" );
-                       wfProfileOut( __METHOD__ );
-                       throw new PermissionsError( 'read', $permErrors );
-               }
-
-               $outputPage = $this->getContext()->getOutput();
-               # getOldID() may as well want us to redirect somewhere else
-               if ( $this->mRedirectUrl ) {
-                       $outputPage->redirect( $this->mRedirectUrl );
-                       wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
-                       wfProfileOut( __METHOD__ );
-
-                       return;
-               }
-
-               # If we got diff in the query, we want to see a diff page instead of the article.
-               if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
-                       wfDebug( __METHOD__ . ": showing diff page\n" );
-                       $this->showDiffPage();
-                       wfProfileOut( __METHOD__ );
-
-                       return;
-               }
-
-               # Set page title (may be overridden by DISPLAYTITLE)
-               $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
-
-               $outputPage->setArticleFlag( true );
-               # Allow frames by default
-               $outputPage->allowClickjacking();
-
-               $parserCache = ParserCache::singleton();
-
-               $parserOptions = $this->getParserOptions();
-               # Render printable version, use printable version cache
-               if ( $outputPage->isPrintable() ) {
-                       $parserOptions->setIsPrintable( true );
-                       $parserOptions->setEditSection( false );
-               } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
-                       $parserOptions->setEditSection( false );
-               }
-
-               # Try client and file cache
-               if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
-                       if ( $wgUseETag ) {
-                               $outputPage->setETag( $parserCache->getETag( $this, $parserOptions ) );
-                       }
-
-                       # Is it client cached?
-                       if ( $outputPage->checkLastModified( $this->mPage->getTouched() ) ) {
-                               wfDebug( __METHOD__ . ": done 304\n" );
-                               wfProfileOut( __METHOD__ );
-
-                               return;
-                       # Try file cache
-                       } elseif ( $wgUseFileCache && $this->tryFileCache() ) {
-                               wfDebug( __METHOD__ . ": done file cache\n" );
-                               # tell wgOut that output is taken care of
-                               $outputPage->disable();
-                               $this->mPage->doViewUpdates( $user, $oldid );
-                               wfProfileOut( __METHOD__ );
-
-                               return;
-                       }
-               }
-
-               # Should the parser cache be used?
-               $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
-               wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
-               if ( $user->getStubThreshold() ) {
-                       wfIncrStats( 'pcache_miss_stub' );
-               }
-
-               $this->showRedirectedFromHeader();
-               $this->showNamespaceHeader();
-
-               # Iterate through the possible ways of constructing the output text.
-               # Keep going until $outputDone is set, or we run out of things to do.
-               $pass = 0;
-               $outputDone = false;
-               $this->mParserOutput = false;
-
-               while ( !$outputDone && ++$pass ) {
-                       switch ( $pass ) {
-                               case 1:
-                                       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();
-                                               $this->mPage->doViewUpdates( $user );
-                                               wfProfileOut( __METHOD__ );
-                                               return;
-                                       }
-
-                                       # Try the parser cache
-                                       if ( $useParserCache ) {
-                                               $this->mParserOutput = $parserCache->get( $this, $parserOptions );
-
-                                               if ( $this->mParserOutput !== false ) {
-                                                       if ( $oldid ) {
-                                                               wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
-                                                               $this->setOldSubtitle( $oldid );
-                                                       } else {
-                                                               wfDebug( __METHOD__ . ": showing parser cache contents\n" );
-                                                       }
-                                                       $outputPage->addParserOutput( $this->mParserOutput );
-                                                       # Ensure that UI elements requiring revision ID have
-                                                       # the correct version information.
-                                                       $outputPage->setRevisionId( $this->mPage->getLatest() );
-                                                       # Preload timestamp to avoid a DB hit
-                                                       $cachedTimestamp = $this->mParserOutput->getTimestamp();
-                                                       if ( $cachedTimestamp !== null ) {
-                                                               $outputPage->setRevisionTimestamp( $cachedTimestamp );
-                                                               $this->mPage->setTimestamp( $cachedTimestamp );
-                                                       }
-                                                       $outputDone = true;
-                                               }
-                                       }
-                                       break;
-                               case 3:
-                                       # This will set $this->mRevision if needed
-                                       $this->fetchContentObject();
-
-                                       # Are we looking at an old revision
-                                       if ( $oldid && $this->mRevision ) {
-                                               $this->setOldSubtitle( $oldid );
-
-                                               if ( !$this->showDeletedRevisionHeader() ) {
-                                                       wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
-                                                       wfProfileOut( __METHOD__ );
-                                                       return;
-                                               }
-                                       }
-
-                                       # Ensure that UI elements requiring revision ID have
-                                       # the correct version information.
-                                       $outputPage->setRevisionId( $this->getRevIdFetched() );
-                                       # Preload timestamp to avoid a DB hit
-                                       $outputPage->setRevisionTimestamp( $this->getTimestamp() );
-
-                                       # Pages containing custom CSS or JavaScript get special treatment
-                                       if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
-                                               wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
-                                               $this->showCssOrJsPage();
-                                               $outputDone = true;
-                                       } elseif ( !wfRunHooks( 'ArticleContentViewCustom',
-                                                       array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
-
-                                               # Allow extensions do their own custom view for certain pages
-                                               $outputDone = true;
-                                       } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
-                                                       array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
-
-                                               # Allow extensions do their own custom view for certain pages
-                                               $outputDone = true;
-                                       }
-                                       break;
-                               case 4:
-                                       # Run the parse, protected by a pool counter
-                                       wfDebug( __METHOD__ . ": doing uncached parse\n" );
-
-                                       $content = $this->getContentObject();
-                                       $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
-                                               $this->getRevIdFetched(), $useParserCache, $content );
-
-                                       if ( !$poolArticleView->execute() ) {
-                                               $error = $poolArticleView->getError();
-                                               if ( $error ) {
-                                                       $outputPage->clearHTML(); // for release() errors
-                                                       $outputPage->enableClientCache( false );
-                                                       $outputPage->setRobotPolicy( 'noindex,nofollow' );
-
-                                                       $errortext = $error->getWikiText( false, 'view-pool-error' );
-                                                       $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
-                                               }
-                                               # Connection or timeout error
-                                               wfProfileOut( __METHOD__ );
-                                               return;
-                                       }
-
-                                       $this->mParserOutput = $poolArticleView->getParserOutput();
-                                       $outputPage->addParserOutput( $this->mParserOutput );
-                                       if ( $content->getRedirectTarget() ) {
-                                               $outputPage->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
-                                       }
-
-                                       # Don't cache a dirty ParserOutput object
-                                       if ( $poolArticleView->getIsDirty() ) {
-                                               $outputPage->setSquidMaxage( 0 );
-                                               $outputPage->addHTML( "<!-- parser cache is expired, " .
-                                                       "sending anyway due to pool overload-->\n" );
-                                       }
-
-                                       $outputDone = true;
-                                       break;
-                               # Should be unreachable, but just in case...
-                               default:
-                                       break 2;
-                       }
-               }
-
-               # 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;
-
-               # 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()->isMainPage() ) {
-                       $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
-                       if ( !$msg->isDisabled() ) {
-                               $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
-                       }
-               }
-
-               # Check for any __NOINDEX__ tags on the page using $pOutput
-               $policy = $this->getRobotPolicy( 'view', $pOutput );
-               $outputPage->setIndexPolicy( $policy['index'] );
-               $outputPage->setFollowPolicy( $policy['follow'] );
-
-               $this->showViewFooter();
-               $this->mPage->doViewUpdates( $user, $oldid );
-
-               $outputPage->addModules( 'mediawiki.action.view.postEdit' );
-
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Adjust title for pages with displaytitle, -{T|}- or language conversion
-        * @param ParserOutput $pOutput
-        */
-       public function adjustDisplayTitle( ParserOutput $pOutput ) {
-               # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
-               $titleText = $pOutput->getTitleText();
-               if ( strval( $titleText ) !== '' ) {
-                       $this->getContext()->getOutput()->setPageTitle( $titleText );
-               }
-       }
-
-       /**
-        * Show a diff page according to current request variables. For use within
-        * Article::view() only, other callers should use the DifferenceEngine class.
-        *
-        * @todo Make protected
-        */
-       public function showDiffPage() {
-               $request = $this->getContext()->getRequest();
-               $user = $this->getContext()->getUser();
-               $diff = $request->getVal( 'diff' );
-               $rcid = $request->getVal( 'rcid' );
-               $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
-               $purge = $request->getVal( 'action' ) == 'purge';
-               $unhide = $request->getInt( 'unhide' ) == 1;
-               $oldid = $this->getOldID();
-
-               $rev = $this->getRevisionFetched();
-
-               if ( !$rev ) {
-                       $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
-                       $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 );
-                       return;
-               }
-
-               $contentHandler = $rev->getContentHandler();
-               $de = $contentHandler->createDifferenceEngine(
-                       $this->getContext(),
-                       $oldid,
-                       $diff,
-                       $rcid,
-                       $purge,
-                       $unhide
-               );
-
-               // DifferenceEngine directly fetched the revision:
-               $this->mRevIdFetched = $de->mNewid;
-               $de->showDiffPage( $diffOnly );
-
-               // Run view updates for the newer revision being diffed (and shown
-               // below the diff if not $diffOnly).
-               list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
-               // New can be false, convert it to 0 - this conveniently means the latest revision
-               $this->mPage->doViewUpdates( $user, (int)$new );
-       }
-
-       /**
-        * Show a page view for a page formatted as CSS or JavaScript. To be called by
-        * Article::view() only.
-        *
-        * This exists mostly to serve the deprecated ShowRawCssJs hook (used to customize these views).
-        * It has been replaced by the ContentGetParserOutput hook, which lets you do the same but with
-        * more flexibility.
-        *
-        * @param bool $showCacheHint Whether to show a message telling the user
-        *   to clear the browser cache (default: true).
-        */
-       protected function showCssOrJsPage( $showCacheHint = true ) {
-               $outputPage = $this->getContext()->getOutput();
-
-               if ( $showCacheHint ) {
-                       $dir = $this->getContext()->getLanguage()->getDir();
-                       $lang = $this->getContext()->getLanguage()->getCode();
-
-                       $outputPage->wrapWikiMsg(
-                               "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
-                               'clearyourcache'
-                       );
-               }
-
-               $this->fetchContentObject();
-
-               if ( $this->mContentObject ) {
-                       // Give hooks a chance to customise the output
-                       if ( ContentHandler::runLegacyHooks(
-                               'ShowRawCssJs',
-                               array( $this->mContentObject, $this->getTitle(), $outputPage ) )
-                       ) {
-                               // If no legacy hooks ran, display the content of the parser output, including RL modules,
-                               // but excluding metadata like categories and language links
-                               $po = $this->mContentObject->getParserOutput( $this->getTitle() );
-                               $outputPage->addParserOutputContent( $po );
-                       }
-               }
-       }
-
-       /**
-        * Get the robot policy to be used for the current view
-        * @param string $action The action= GET parameter
-        * @param ParserOutput|null $pOutput
-        * @return array The policy that should be set
-        * @todo: actions other than 'view'
-        */
-       public function getRobotPolicy( $action, $pOutput = null ) {
-               global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
-
-               $ns = $this->getTitle()->getNamespace();
-
-               # Don't index user and user talk pages for blocked users (bug 11443)
-               if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
-                       $specificTarget = null;
-                       $vagueTarget = null;
-                       $titleText = $this->getTitle()->getText();
-                       if ( IP::isValid( $titleText ) ) {
-                               $vagueTarget = $titleText;
-                       } else {
-                               $specificTarget = $titleText;
-                       }
-                       if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
-                               return array(
-                                       'index' => 'noindex',
-                                       'follow' => 'nofollow'
-                               );
-                       }
-               }
-
-               if ( $this->mPage->getID() === 0 || $this->getOldID() ) {
-                       # Non-articles (special pages etc), and old revisions
-                       return array(
-                               'index' => 'noindex',
-                               'follow' => 'nofollow'
-                       );
-               } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
-                       # Discourage indexing of printable versions, but encourage following
-                       return array(
-                               'index' => 'noindex',
-                               'follow' => 'follow'
-                       );
-               } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
-                       # For ?curid=x urls, disallow indexing
-                       return array(
-                               'index' => 'noindex',
-                               'follow' => 'follow'
-                       );
-               }
-
-               # Otherwise, construct the policy based on the various config variables.
-               $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
-
-               if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
-                       # Honour customised robot policies for this namespace
-                       $policy = array_merge(
-                               $policy,
-                               self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
-                       );
-               }
-               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' => $pOutput->getIndexPolicy() )
-                       );
-               }
-
-               if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
-                       # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
-                       $policy = array_merge(
-                               $policy,
-                               self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
-                       );
-               }
-
-               return $policy;
-       }
-
-       /**
-        * Converts a String robot policy into an associative array, to allow
-        * merging of several policies using array_merge().
-        * @param array|string $policy Returns empty array on null/false/'', transparent
-        *   to already-converted arrays, converts string.
-        * @return array 'index' => \<indexpolicy\>, 'follow' => \<followpolicy\>
-        */
-       public static function formatRobotPolicy( $policy ) {
-               if ( is_array( $policy ) ) {
-                       return $policy;
-               } elseif ( !$policy ) {
-                       return array();
-               }
-
-               $policy = explode( ',', $policy );
-               $policy = array_map( 'trim', $policy );
-
-               $arr = array();
-               foreach ( $policy as $var ) {
-                       if ( in_array( $var, array( 'index', 'noindex' ) ) ) {
-                               $arr['index'] = $var;
-                       } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) {
-                               $arr['follow'] = $var;
-                       }
-               }
-
-               return $arr;
-       }
-
-       /**
-        * If this request is a redirect view, send "redirected from" subtitle to
-        * the output. Returns true if the header was needed, false if this is not
-        * a redirect view. Handles both local and remote redirects.
-        *
-        * @return bool
-        */
-       public function showRedirectedFromHeader() {
-               global $wgRedirectSources;
-               $outputPage = $this->getContext()->getOutput();
-
-               $rdfrom = $this->getContext()->getRequest()->getVal( 'rdfrom' );
-
-               if ( isset( $this->mRedirectedFrom ) ) {
-                       // 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::linkKnown(
-                                       $this->mRedirectedFrom,
-                                       null,
-                                       array(),
-                                       array( 'redirect' => 'no' )
-                               );
-
-                               $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
-
-                               // Set the fragment if one was specified in the redirect
-                               if ( $this->getTitle()->hasFragment() ) {
-                                       $outputPage->addJsConfigVars( 'wgRedirectToFragment', $this->getTitle()->getFragmentForURL() );
-                                       $outputPage->addModules( 'mediawiki.action.view.redirectToFragment' );
-                               }
-
-                               // Add a <link rel="canonical"> tag
-                               $outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() );
-
-                               // Tell the output object that the user arrived at this article through a redirect
-                               $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
-
-                               return true;
-                       }
-               } elseif ( $rdfrom ) {
-                       // This is an externally redirected view, from some other wiki.
-                       // If it was reported from a trusted site, supply a backlink.
-                       if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
-                               $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
-                               $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
-
-                               return true;
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Show a header specific to the namespace currently being viewed, like
-        * [[MediaWiki:Talkpagetext]]. For Article::view().
-        */
-       public function showNamespaceHeader() {
-               if ( $this->getTitle()->isTalkPage() ) {
-                       if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
-                               $this->getContext()->getOutput()->wrapWikiMsg(
-                                       "<div class=\"mw-talkpageheader\">\n$1\n</div>",
-                                       array( 'talkpageheader' )
-                               );
-                       }
-               }
-       }
-
-       /**
-        * Show the footer section of an ordinary page view
-        */
-       public function showViewFooter() {
-               # 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() )
-               ) {
-                       $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
-               }
-
-               // Show a footer allowing the user to patrol the shown revision or page if possible
-               $patrolFooterShown = $this->showPatrolFooter();
-
-               wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) );
-       }
-
-       /**
-        * If patrol is possible, output a patrol UI box. This is called from the
-        * footer section of ordinary page views. If patrol is not possible or not
-        * desired, does nothing.
-        * Side effect: When the patrol link is build, this method will call
-        * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
-        *
-        * @return bool
-        */
-       public function showPatrolFooter() {
-               global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
-
-               $outputPage = $this->getContext()->getOutput();
-               $user = $this->getContext()->getUser();
-               $cache = wfGetMainCache();
-               $rc = false;
-
-               if ( !$this->getTitle()->quickUserCan( 'patrol', $user )
-                       || !( $wgUseRCPatrol || $wgUseNPPatrol )
-               ) {
-                       // Patrolling is disabled or the user isn't allowed to
-                       return false;
-               }
-
-               wfProfileIn( __METHOD__ );
-
-               // New page patrol: Get the timestamp of the oldest revison which
-               // the revision table holds for the given page. Then we look
-               // whether it's within the RC lifespan and if it is, we try
-               // to get the recentchanges row belonging to that entry
-               // (with rc_new = 1).
-
-               // Check for cached results
-               if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) {
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               if ( $this->mRevision
-                       && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
-               ) {
-                       // The current revision is already older than what could be in the RC table
-                       // 6h tolerance because the RC might not be cleaned out regularly
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $oldestRevisionTimestamp = $dbr->selectField(
-                       'revision',
-                       'MIN( rev_timestamp )',
-                       array( 'rev_page' => $this->getTitle()->getArticleID() ),
-                       __METHOD__
-               );
-
-               if ( $oldestRevisionTimestamp
-                       && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
-               ) {
-                       // 6h tolerance because the RC might not be cleaned out regularly
-                       $rc = RecentChange::newFromConds(
-                               array(
-                                       'rc_new' => 1,
-                                       'rc_timestamp' => $oldestRevisionTimestamp,
-                                       'rc_namespace' => $this->getTitle()->getNamespace(),
-                                       'rc_cur_id' => $this->getTitle()->getArticleID(),
-                                       'rc_patrolled' => 0
-                               ),
-                               __METHOD__,
-                               array( 'USE INDEX' => 'new_name_timestamp' )
-                       );
-               }
-
-               if ( !$rc ) {
-                       // No RC entry around
-
-                       // Cache the information we gathered above in case we can't patrol
-                       // Don't cache in case we can patrol as this could change
-                       $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' );
-
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               if ( $rc->getPerformer()->getName() == $user->getName() ) {
-                       // Don't show a patrol link for own creations. If the user could
-                       // patrol them, they already would be patrolled
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-
-               $rcid = $rc->getAttribute( 'rc_id' );
-
-               $token = $user->getEditToken( $rcid );
-
-               $outputPage->preventClickjacking();
-               if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
-                       $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
-               }
-
-               $link = Linker::linkKnown(
-                       $this->getTitle(),
-                       wfMessage( 'markaspatrolledtext' )->escaped(),
-                       array(),
-                       array(
-                               'action' => 'markpatrolled',
-                               'rcid' => $rcid,
-                               'token' => $token,
-                       )
-               );
-
-               $outputPage->addHTML(
-                       "<div class='patrollink'>" .
-                               wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
-                       '</div>'
-               );
-
-               wfProfileOut( __METHOD__ );
-               return true;
-       }
-
-       /**
-        * Show the error text for a missing article. For articles in the MediaWiki
-        * namespace, show the default message text. To be called from Article::view().
-        */
-       public function showMissingArticle() {
-               global $wgSend404Code;
-               $outputPage = $this->getContext()->getOutput();
-               // Whether the page is a root user page of an existing user (but not a subpage)
-               $validUserPage = false;
-
-               # 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
-               ) {
-                       $parts = explode( '/', $this->getTitle()->getText() );
-                       $rootPart = $parts[0];
-                       $user = User::newFromName( $rootPart, false /* allow IP users*/ );
-                       $ip = User::isIP( $rootPart );
-
-                       if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
-                               $outputPage->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
-                               LogEventsList::showLogExtract(
-                                       $outputPage,
-                                       'block',
-                                       $user->getUserPage(),
-                                       '',
-                                       array(
-                                               'lim' => 1,
-                                               'showIfEmpty' => false,
-                                               'msgKey' => array(
-                                                       'blocked-notice-logextract',
-                                                       $user->getName() # Support GENDER in notice
-                                               )
-                                       )
-                               );
-                               $validUserPage = !$this->getTitle()->isSubpage();
-                       } else {
-                               $validUserPage = !$this->getTitle()->isSubpage();
-                       }
-               }
-
-               wfRunHooks( 'ShowMissingArticle', array( $this ) );
-
-               // Give extensions a chance to hide their (unrelated) log entries
-               $logTypes = array( 'delete', 'move' );
-               $conds = array( "log_action != 'revision'" );
-               wfRunHooks( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) );
-
-               # Show delete and move logs
-               LogEventsList::showLogExtract( $outputPage, $logTypes, $this->getTitle(), '',
-                       array( 'lim' => 10,
-                               'conds' => $conds,
-                               'showIfEmpty' => false,
-                               'msgKey' => array( 'moveddeleted-notice' ) )
-               );
-
-               if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
-                       // If there's no backing content, send a 404 Not Found
-                       // for better machine handling of broken links.
-                       $this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" );
-               }
-
-               if ( $validUserPage ) {
-                       // Also apply the robot policy for nonexisting user pages (as those aren't served as 404)
-                       $policy = $this->getRobotPolicy( 'view' );
-                       $outputPage->setIndexPolicy( $policy['index'] );
-                       $outputPage->setFollowPolicy( $policy['follow'] );
-               }
-
-               $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
-
-               if ( ! $hookResult ) {
-                       return;
-               }
-
-               # Show error message
-               $oldid = $this->getOldID();
-               if ( $oldid ) {
-                       $text = wfMessage( 'missing-revision', $oldid )->plain();
-               } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) {
-                       // Use the default message text
-                       $text = $this->getTitle()->getDefaultMessageText();
-               } elseif ( $this->getTitle()->quickUserCan( 'create', $this->getContext()->getUser() )
-                       && $this->getTitle()->quickUserCan( 'edit', $this->getContext()->getUser() )
-               ) {
-                       $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
-                       $text = wfMessage( $message )->plain();
-               } else {
-                       $text = wfMessage( 'noarticletext-nopermission' )->plain();
-               }
-               $text = "<div class='noarticletext'>\n$text\n</div>";
-
-               $outputPage->addWikiText( $text );
-       }
-
-       /**
-        * If the revision requested for view is deleted, check permissions.
-        * Send either an error message or a warning header to the output.
-        *
-        * @return bool true if the view is allowed, false if not.
-        */
-       public function showDeletedRevisionHeader() {
-               if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
-                       // Not deleted
-                       return true;
-               }
-
-               $outputPage = $this->getContext()->getOutput();
-               $user = $this->getContext()->getUser();
-               // If the user is not allowed to see it...
-               if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
-                       $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
-                               'rev-deleted-text-permission' );
-
-                       return false;
-               // If the user needs to confirm that they want to see it...
-               } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
-                       # Give explanation and add a link to view the revision...
-                       $oldid = intval( $this->getOldID() );
-                       $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
-                       $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
-                               'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
-                       $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
-                               array( $msg, $link ) );
-
-                       return false;
-               // We are allowed to see...
-               } else {
-                       $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
-                               'rev-suppressed-text-view' : 'rev-deleted-text-view';
-                       $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
-
-                       return true;
-               }
-       }
-
-       /**
-        * 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 int $oldid Revision ID of this article revision
-        */
-       public function setOldSubtitle( $oldid = 0 ) {
-               if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
-                       return;
-               }
-
-               $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1;
-
-               # Cascade unhide param in links for easy deletion browsing
-               $extraParams = array();
-               if ( $unhide ) {
-                       $extraParams['unhide'] = 1;
-               }
-
-               if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
-                       $revision = $this->mRevision;
-               } else {
-                       $revision = Revision::newFromId( $oldid );
-               }
-
-               $timestamp = $revision->getTimestamp();
-
-               $current = ( $oldid == $this->mPage->getLatest() );
-               $language = $this->getContext()->getLanguage();
-               $user = $this->getContext()->getUser();
-
-               $td = $language->userTimeAndDate( $timestamp, $user );
-               $tddate = $language->userDate( $timestamp, $user );
-               $tdtime = $language->userTime( $timestamp, $user );
-
-               # 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';
-
-               $outputPage = $this->getContext()->getOutput();
-               $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
-                       $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
-                       $tdtime, $revision->getUser() )->rawParams( Linker::revComment( $revision, true, true ) )->parse() . "</div>" );
-
-               $lnk = $current
-                       ? wfMessage( 'currentrevisionlink' )->escaped()
-                       : Linker::linkKnown(
-                               $this->getTitle(),
-                               wfMessage( 'currentrevisionlink' )->escaped(),
-                               array(),
-                               $extraParams
-                       );
-               $curdiff = $current
-                       ? wfMessage( 'diff' )->escaped()
-                       : Linker::linkKnown(
-                               $this->getTitle(),
-                               wfMessage( 'diff' )->escaped(),
-                               array(),
-                               array(
-                                       'diff' => 'cur',
-                                       'oldid' => $oldid
-                               ) + $extraParams
-                       );
-               $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
-               $prevlink = $prev
-                       ? Linker::linkKnown(
-                               $this->getTitle(),
-                               wfMessage( 'previousrevision' )->escaped(),
-                               array(),
-                               array(
-                                       'direction' => 'prev',
-                                       'oldid' => $oldid
-                               ) + $extraParams
-                       )
-                       : wfMessage( 'previousrevision' )->escaped();
-               $prevdiff = $prev
-                       ? Linker::linkKnown(
-                               $this->getTitle(),
-                               wfMessage( 'diff' )->escaped(),
-                               array(),
-                               array(
-                                       'diff' => 'prev',
-                                       'oldid' => $oldid
-                               ) + $extraParams
-                       )
-                       : wfMessage( 'diff' )->escaped();
-               $nextlink = $current
-                       ? wfMessage( 'nextrevision' )->escaped()
-                       : Linker::linkKnown(
-                               $this->getTitle(),
-                               wfMessage( 'nextrevision' )->escaped(),
-                               array(),
-                               array(
-                                       'direction' => 'next',
-                                       'oldid' => $oldid
-                               ) + $extraParams
-                       );
-               $nextdiff = $current
-                       ? wfMessage( 'diff' )->escaped()
-                       : Linker::linkKnown(
-                               $this->getTitle(),
-                               wfMessage( 'diff' )->escaped(),
-                               array(),
-                               array(
-                                       'diff' => 'next',
-                                       'oldid' => $oldid
-                               ) + $extraParams
-                       );
-
-               $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
-               if ( $cdel !== '' ) {
-                       $cdel .= ' ';
-               }
-
-               $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
-                       wfMessage( 'revision-nav' )->rawParams(
-                               $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
-                       )->escaped() . "</div>" );
-       }
-
-       /**
-        * Return the HTML for the top of a redirect page
-        *
-        * Chances are you should just be using the ParserOutput from
-        * WikitextContent::getParserOutput instead of calling this for redirects.
-        *
-        * @param Title|array $target Destination(s) to redirect
-        * @param bool $appendSubtitle [optional]
-        * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
-        * @return string Containing HMTL with redirect link
-        */
-       public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
-               $lang = $this->getTitle()->getPageLanguage();
-               if ( $appendSubtitle ) {
-                       $out = $this->getContext()->getOutput();
-                       $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
-               }
-               return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
-       }
-
-       /**
-        * Return the HTML for the top of a redirect page
-        *
-        * Chances are you should just be using the ParserOutput from
-        * WikitextContent::getParserOutput instead of calling this for redirects.
-        *
-        * @since 1.23
-        * @param Language $lang
-        * @param Title|array $target Destination(s) to redirect
-        * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
-        * @return string Containing HMTL with redirect link
-        */
-       public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
-               global $wgStylePath;
-
-               if ( !is_array( $target ) ) {
-                       $target = array( $target );
-               }
-
-               $imageDir = $lang->getDir();
-
-               // 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 ) {
-                       $link = Linker::linkKnown( $title, htmlspecialchars( $title->getFullText() ) );
-               } else {
-                       $link = Linker::link( $title, htmlspecialchars( $title->getFullText() ) );
-               }
-
-               $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
-               $alt = $lang->isRTL() ? '←' : '→';
-
-               // Automatically append redirect=no to each link, since most of them are
-               // redirect pages themselves.
-               /** @var Title $rt */
-               foreach ( $target as $rt ) {
-                       $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) );
-                       if ( $forceKnown ) {
-                               $link .= Linker::linkKnown(
-                                       $rt,
-                                       htmlspecialchars( $rt->getFullText(),
-                                       array(),
-                                       array( 'redirect' => 'no' )
-                               )
-                               );
-                       } else {
-                               $link .= Linker::link(
-                                       $rt,
-                                       htmlspecialchars( $rt->getFullText() ),
-                                       array(),
-                                       array( 'redirect' => 'no' )
-                               );
-                       }
-               }
-
-               $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
-               return '<div class="redirectMsg">' .
-                       Html::element( 'img', array( 'src' => $imageUrl, 'alt' => '#REDIRECT' ) ) .
-                       '<span class="redirectText">' . $link . '</span></div>';
-       }
-
-       /**
-        * Handle action=render
-        */
-       public function render() {
-               $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
-               $this->getContext()->getOutput()->setArticleBodyOnly( true );
-               $this->getContext()->getOutput()->enableSectionEditLinks( false );
-               $this->view();
-       }
-
-       /**
-        * action=protect handler
-        */
-       public function protect() {
-               $form = new ProtectionForm( $this );
-               $form->execute();
-       }
-
-       /**
-        * action=unprotect handler (alias)
-        */
-       public function unprotect() {
-               $this->protect();
-       }
-
-       /**
-        * UI entry point for page deletion
-        */
-       public function delete() {
-               # This code desperately needs to be totally rewritten
-
-               $title = $this->getTitle();
-               $user = $this->getContext()->getUser();
-
-               # Check permissions
-               $permission_errors = $title->getUserPermissionsErrors( 'delete', $user );
-               if ( count( $permission_errors ) ) {
-                       throw new PermissionsError( 'delete', $permission_errors );
-               }
-
-               # Read-only check...
-               if ( wfReadOnly() ) {
-                       throw new ReadOnlyError;
-               }
-
-               # Better double-check that it hasn't been deleted yet!
-               $this->mPage->loadPageData( 'fromdbmaster' );
-               if ( !$this->mPage->exists() ) {
-                       $deleteLogPage = new LogPage( 'delete' );
-                       $outputPage = $this->getContext()->getOutput();
-                       $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) );
-                       $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
-                                       array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) )
-                               );
-                       $outputPage->addHTML(
-                               Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
-                       );
-                       LogEventsList::showLogExtract(
-                               $outputPage,
-                               'delete',
-                               $title
-                       );
-
-                       return;
-               }
-
-               $request = $this->getContext()->getRequest();
-               $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
-               $deleteReason = $request->getText( 'wpReason' );
-
-               if ( $deleteReasonList == 'other' ) {
-                       $reason = $deleteReason;
-               } elseif ( $deleteReason != '' ) {
-                       // Entry from drop down menu + additional comment
-                       $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
-                       $reason = $deleteReasonList . $colonseparator . $deleteReason;
-               } else {
-                       $reason = $deleteReasonList;
-               }
-
-               if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
-                       array( 'delete', $this->getTitle()->getPrefixedText() ) )
-               ) {
-                       # Flag to hide all contents of the archived revisions
-                       $suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
-
-                       $this->doDelete( $reason, $suppress );
-
-                       WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
-
-                       return;
-               }
-
-               // Generate deletion reason
-               $hasHistory = false;
-               if ( !$reason ) {
-                       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 ) {
-                       $revisions = $this->mTitle->estimateRevisionCount();
-                       // @todo FIXME: i18n issue/patchwork message
-                       $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' .
-                               wfMessage( 'historywarning' )->numParams( $revisions )->parse() .
-                               wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title,
-                                       wfMessage( 'history' )->escaped(),
-                                       array( 'rel' => 'archives' ),
-                                       array( 'action' => 'history' ) ) .
-                               '</strong>'
-                       );
-
-                       if ( $this->mTitle->isBigDeletion() ) {
-                               global $wgDeleteRevisionsLimit;
-                               $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
-                                       array(
-                                               'delete-warning-toobig',
-                                               $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
-                                       )
-                               );
-                       }
-               }
-
-               $this->confirmDelete( $reason );
-       }
-
-       /**
-        * Output deletion confirmation dialog
-        * @todo FIXME: Move to another file?
-        * @param string $reason Prefilled reason
-        */
-       public function confirmDelete( $reason ) {
-               wfDebug( "Article::confirmDelete\n" );
-
-               $outputPage = $this->getContext()->getOutput();
-               $outputPage->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
-               $outputPage->addBacklinkSubtitle( $this->getTitle() );
-               $outputPage->setRobotPolicy( 'noindex,nofollow' );
-               $backlinkCache = $this->getTitle()->getBacklinkCache();
-               if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
-                       $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
-                               'deleting-backlinks-warning' );
-               }
-               $outputPage->addWikiMsg( 'confirmdeletetext' );
-
-               wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) );
-
-               $user = $this->getContext()->getUser();
-
-               if ( $user->isAllowed( 'suppressrevision' ) ) {
-                       $suppress = "<tr id=\"wpDeleteSuppressRow\">
-                                       <td></td>
-                                       <td class='mw-input'><strong>" .
-                                               Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
-                                                       'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
-                                       "</strong></td>
-                               </tr>";
-               } else {
-                       $suppress = '';
-               }
-               $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $this->getTitle() );
-
-               $form = Xml::openElement( 'form', array( 'method' => 'post',
-                       'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
-                       Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
-                       Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) .
-                       Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
-                       "<tr id=\"wpDeleteReasonListRow\">
-                               <td class='mw-label'>" .
-                                       Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
-                               "</td>
-                               <td class='mw-input'>" .
-                                       Xml::listDropDown(
-                                               'wpDeleteReasonList',
-                                               wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
-                                               wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
-                                               '',
-                                               'wpReasonDropDown',
-                                               1
-                                       ) .
-                               "</td>
-                       </tr>
-                       <tr id=\"wpDeleteReasonRow\">
-                               <td class='mw-label'>" .
-                                       Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
-                               "</td>
-                               <td class='mw-input'>" .
-                               Html::input( 'wpReason', $reason, 'text', array(
-                                       'size' => '60',
-                                       'maxlength' => '255',
-                                       'tabindex' => '2',
-                                       'id' => 'wpReason',
-                                       'autofocus'
-                               ) ) .
-                               "</td>
-                       </tr>";
-
-               # Disallow watching if user is not logged in
-               if ( $user->isLoggedIn() ) {
-                       $form .= "
-                       <tr>
-                               <td></td>
-                               <td class='mw-input'>" .
-                                       Xml::checkLabel( wfMessage( 'watchthis' )->text(),
-                                               'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
-                               "</td>
-                       </tr>";
-               }
-
-               $form .= "
-                       $suppress
-                       <tr>
-                               <td></td>
-                               <td class='mw-submit'>" .
-                                       Xml::submitButton( wfMessage( 'deletepage' )->text(),
-                                               array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
-                               "</td>
-                       </tr>" .
-                       Xml::closeElement( 'table' ) .
-                       Xml::closeElement( 'fieldset' ) .
-                       Html::hidden(
-                               'wpEditToken',
-                               $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) )
-                       ) .
-                       Xml::closeElement( 'form' );
-
-                       if ( $user->isAllowed( 'editinterface' ) ) {
-                               $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
-                               $link = Linker::link(
-                                       $title,
-                                       wfMessage( 'delete-edit-reasonlist' )->escaped(),
-                                       array(),
-                                       array( 'action' => 'edit' )
-                               );
-                               $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
-                       }
-
-               $outputPage->addHTML( $form );
-
-               $deleteLogPage = new LogPage( 'delete' );
-               $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
-               LogEventsList::showLogExtract( $outputPage, 'delete',
-                       $this->getTitle()
-               );
-       }
-
-       /**
-        * Perform a deletion and output success or failure messages
-        * @param string $reason
-        * @param bool $suppress
-        */
-       public function doDelete( $reason, $suppress = false ) {
-               $error = '';
-               $outputPage = $this->getContext()->getOutput();
-               $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error );
-
-               if ( $status->isGood() ) {
-                       $deleted = $this->getTitle()->getPrefixedText();
-
-                       $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
-                       $outputPage->setRobotPolicy( 'noindex,nofollow' );
-
-                       $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
-
-                       $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
-                       $outputPage->returnToMain( false );
-               } else {
-                       $outputPage->setPageTitle(
-                               wfMessage( 'cannotdelete-title',
-                                       $this->getTitle()->getPrefixedText() )
-                       );
-
-                       if ( $error == '' ) {
-                               $outputPage->addWikiText(
-                                       "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
-                               );
-                               $deleteLogPage = new LogPage( 'delete' );
-                               $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
-
-                               LogEventsList::showLogExtract(
-                                       $outputPage,
-                                       'delete',
-                                       $this->getTitle()
-                               );
-                       } else {
-                               $outputPage->addHTML( $error );
-                       }
-               }
-       }
-
-       /* Caching functions */
-
-       /**
-        * 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 bool true if cached version send, false otherwise
-        */
-       protected function tryFileCache() {
-               static $called = false;
-
-               if ( $called ) {
-                       wfDebug( "Article::tryFileCache(): called twice!?\n" );
-                       return false;
-               }
-
-               $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' ) );
-                       }
-               } else {
-                       wfDebug( "Article::tryFileCache(): not cacheable\n" );
-               }
-
-               return false;
-       }
-
-       /**
-        * 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 ) );
-                       }
-               }
-
-               return $cacheable;
-       }
-
-       /**#@-*/
-
-       /**
-        * 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 int|null $oldid Revision ID or null
-        * @param User $user The relevant user
-        * @return ParserOutput|bool ParserOutput or false if the given revision ID is not found
-        */
-       public function getParserOutput( $oldid = null, User $user = null ) {
-               //XXX: bypasses mParserOptions and thus setParserOptions()
-
-               if ( $user === null ) {
-                       $parserOptions = $this->getParserOptions();
-               } else {
-                       $parserOptions = $this->mPage->makeParserOptions( $user );
-               }
-
-               return $this->mPage->getParserOutput( $parserOptions, $oldid );
-       }
-
-       /**
-        * Override the ParserOptions used to render the primary article wikitext.
-        *
-        * @param ParserOptions $options
-        * @throws MWException if the parser options where already initialized.
-        */
-       public function setParserOptions( ParserOptions $options ) {
-               if ( $this->mParserOptions ) {
-                       throw new MWException( "can't change parser options after they have already been set" );
-               }
-
-               // clone, so if $options is modified later, it doesn't confuse the parser cache.
-               $this->mParserOptions = clone $options;
-       }
-
-       /**
-        * Get parser options suitable for rendering the primary article wikitext
-        * @return ParserOptions
-        */
-       public function getParserOptions() {
-               if ( !$this->mParserOptions ) {
-                       $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
-               }
-               // Clone to allow modifications of the return value without affecting cache
-               return clone $this->mParserOptions;
-       }
-
-       /**
-        * Sets the context this Article is executed in
-        *
-        * @param IContextSource $context
-        * @since 1.18
-        */
-       public function setContext( $context ) {
-               $this->mContext = $context;
-       }
-
-       /**
-        * Gets the context this Article is executed in
-        *
-        * @return IContextSource
-        * @since 1.18
-        */
-       public function getContext() {
-               if ( $this->mContext instanceof IContextSource ) {
-                       return $this->mContext;
-               } else {
-                       wfDebug( __METHOD__ . " called and \$mContext is null. " .
-                               "Return RequestContext::getMain(); for sanity\n" );
-                       return RequestContext::getMain();
-               }
-       }
-
-       /**
-        * Use PHP's magic __get handler to handle accessing of
-        * raw WikiPage fields for backwards compatibility.
-        *
-        * @param string $fname Field name
-        */
-       public function __get( $fname ) {
-               if ( property_exists( $this->mPage, $fname ) ) {
-                       #wfWarn( "Access to raw $fname field " . __CLASS__ );
-                       return $this->mPage->$fname;
-               }
-               trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
-       }
-
-       /**
-        * Use PHP's magic __set handler to handle setting of
-        * raw WikiPage fields for backwards compatibility.
-        *
-        * @param string $fname Field name
-        * @param mixed $fvalue New value
-        */
-       public function __set( $fname, $fvalue ) {
-               if ( property_exists( $this->mPage, $fname ) ) {
-                       #wfWarn( "Access to raw $fname field of " . __CLASS__ );
-                       $this->mPage->$fname = $fvalue;
-               // Note: extensions may want to toss on new fields
-               } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
-                       $this->mPage->$fname = $fvalue;
-               } else {
-                       trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
-               }
-       }
-
-       /**
-        * Use PHP's magic __call handler to transform instance calls to
-        * WikiPage functions for backwards compatibility.
-        *
-        * @param string $fname Name of called method
-        * @param array $args Arguments to the method
-        * @return mixed
-        */
-       public function __call( $fname, $args ) {
-               if ( is_callable( array( $this->mPage, $fname ) ) ) {
-                       #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 );
-       }
-
-       // ****** B/C functions to work-around PHP silliness with __call and references ****** //
-
-       /**
-        * @param array $limit
-        * @param array $expiry
-        * @param bool $cascade
-        * @param string $reason
-        * @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 array $limit
-        * @param string $reason
-        * @param int $cascade
-        * @param array $expiry
-        * @return bool
-        */
-       public function updateRestrictions( $limit = array(), $reason = '',
-               &$cascade = 0, $expiry = array()
-       ) {
-               return $this->mPage->doUpdateRestrictions(
-                       $limit,
-                       $expiry,
-                       $cascade,
-                       $reason,
-                       $this->getContext()->getUser()
-               );
-       }
-
-       /**
-        * @param string $reason
-        * @param bool $suppress
-        * @param int $id
-        * @param bool $commit
-        * @param string $error
-        * @return bool
-        */
-       public function doDeleteArticle( $reason, $suppress = false, $id = 0,
-               $commit = true, &$error = ''
-       ) {
-               return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
-       }
-
-       /**
-        * @param string $fromP
-        * @param string $summary
-        * @param string $token
-        * @param bool $bot
-        * @param array $resultDetails
-        * @param User|null $user
-        * @return array
-        */
-       public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
-               $user = is_null( $user ) ? $this->getContext()->getUser() : $user;
-               return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
-       }
-
-       /**
-        * @param string $fromP
-        * @param string $summary
-        * @param bool $bot
-        * @param array $resultDetails
-        * @param User|null $guser
-        * @return array
-        */
-       public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
-               $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser;
-               return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
-       }
-
-       /**
-        * @param bool $hasHistory
-        * @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 ) {
-               WikiPage::onArticleCreate( $title );
-       }
-
-       /**
-        * @param Title $title
-        */
-       public static function onArticleDelete( $title ) {
-               WikiPage::onArticleDelete( $title );
-       }
-
-       /**
-        * @param Title $title
-        */
-       public static function onArticleEdit( $title ) {
-               WikiPage::onArticleEdit( $title );
-       }
-
-       /**
-        * @param string $oldtext
-        * @param string $newtext
-        * @param int $flags
-        * @return string
-        * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
-        */
-       public static function getAutosummary( $oldtext, $newtext, $flags ) {
-               return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
-       }
-       // ******
-}
index 5442507..94264ae 100644 (file)
@@ -32,7 +32,6 @@ $wgAutoloadLocalClasses = array(
        'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
        'AjaxResponse' => 'includes/AjaxResponse.php',
        'AlphabeticPager' => 'includes/Pager.php',
-       'Article' => 'includes/Article.php',
        'AtomFeed' => 'includes/Feed.php',
        'AuthPlugin' => 'includes/AuthPlugin.php',
        'AuthPluginUser' => 'includes/AuthPlugin.php',
@@ -42,7 +41,6 @@ $wgAutoloadLocalClasses = array(
        'CacheHelper' => 'includes/CacheHelper.php',
        'Category' => 'includes/Category.php',
        'Categoryfinder' => 'includes/Categoryfinder.php',
-       'CategoryPage' => 'includes/CategoryPage.php',
        'CategoryViewer' => 'includes/CategoryViewer.php',
        'ChangesFeed' => 'includes/ChangesFeed.php',
        'ChangeTags' => 'includes/ChangeTags.php',
@@ -112,9 +110,6 @@ $wgAutoloadLocalClasses = array(
        'ICacheHelper' => 'includes/CacheHelper.php',
        'IcuCollation' => 'includes/Collation.php',
        'IdentityCollation' => 'includes/Collation.php',
-       'ImageHistoryList' => 'includes/ImagePage.php',
-       'ImageHistoryPseudoPager' => 'includes/ImagePage.php',
-       'ImagePage' => 'includes/ImagePage.php',
        'ImageQueryPage' => 'includes/ImageQueryPage.php',
        'ImportStreamSource' => 'includes/Import.php',
        'ImportStringSource' => 'includes/Import.php',
@@ -145,7 +140,6 @@ $wgAutoloadLocalClasses = array(
        'MWInit' => 'includes/Init.php',
        'MWNamespace' => 'includes/Namespace.php',
        'OutputPage' => 'includes/OutputPage.php',
-       'Page' => 'includes/WikiPage.php',
        'PageQueryPage' => 'includes/PageQueryPage.php',
        'Pager' => 'includes/Pager.php',
        'PasswordError' => 'includes/User.php',
@@ -157,7 +151,7 @@ $wgAutoloadLocalClasses = array(
        'PoolCounterRedis' => 'includes/poolcounter/PoolCounterRedis.php',
        'PoolCounterWork' => 'includes/poolcounter/PoolCounterWork.php',
        'PoolCounterWorkViaCallback' => 'includes/poolcounter/PoolCounterWork.php',
-       'PoolWorkArticleView' => 'includes/WikiPage.php',
+       'PoolWorkArticleView' => 'includes/poolcounter/PoolWorkArticleView.php',
        'Preferences' => 'includes/Preferences.php',
        'PreferencesForm' => 'includes/Preferences.php',
        'PrefixSearch' => 'includes/PrefixSearch.php',
@@ -207,11 +201,8 @@ $wgAutoloadLocalClasses = array(
        'WebRequest' => 'includes/WebRequest.php',
        'WebRequestUpload' => 'includes/WebRequest.php',
        'WebResponse' => 'includes/WebResponse.php',
-       'WikiCategoryPage' => 'includes/WikiCategoryPage.php',
        'WikiExporter' => 'includes/Export.php',
-       'WikiFilePage' => 'includes/WikiFilePage.php',
        'WikiImporter' => 'includes/Import.php',
-       'WikiPage' => 'includes/WikiPage.php',
        'WikiRevision' => 'includes/Import.php',
        'WikiMap' => 'includes/WikiMap.php',
        'WikiReference' => 'includes/WikiMap.php',
@@ -796,6 +787,17 @@ $wgAutoloadLocalClasses = array(
        'WinCacheBagOStuff' => 'includes/objectcache/WinCacheBagOStuff.php',
        'XCacheBagOStuff' => 'includes/objectcache/XCacheBagOStuff.php',
 
+       # includes/page
+       'Article' => 'includes/page/Article.php',
+       'CategoryPage' => 'includes/page/CategoryPage.php',
+       'ImageHistoryList' => 'includes/page/ImagePage.php',
+       'ImageHistoryPseudoPager' => 'includes/page/ImagePage.php',
+       'ImagePage' => 'includes/page/ImagePage.php',
+       'Page' => 'includes/page/WikiPage.php',
+       'WikiCategoryPage' => 'includes/page/WikiCategoryPage.php',
+       'WikiFilePage' => 'includes/page/WikiFilePage.php',
+       'WikiPage' => 'includes/page/WikiPage.php',
+
        # includes/parser
        'CacheTime' => 'includes/parser/CacheTime.php',
        'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php',
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
deleted file mode 100644 (file)
index 9abc6a8..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-/**
- * Special handling for category description pages.
- * Modelled after ImagePage.php.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Special handling for category description pages, showing pages,
- * subcategories and file that belong to the category
- */
-class CategoryPage extends Article {
-       # Subclasses can change this to override the viewer class.
-       protected $mCategoryViewerClass = 'CategoryViewer';
-
-       /**
-        * @param Title $title
-        * @return WikiCategoryPage
-        */
-       protected function newPage( Title $title ) {
-               // Overload mPage with a category-specific page
-               return new WikiCategoryPage( $title );
-       }
-
-       /**
-        * Constructor from a page id
-        * @param int $id Article ID to load
-        * @return CategoryPage|null
-        */
-       public static function newFromID( $id ) {
-               $t = Title::newFromID( $id );
-               # @todo FIXME: Doesn't inherit right
-               return $t == null ? null : new self( $t );
-               # return $t == null ? null : new static( $t ); // PHP 5.3
-       }
-
-       function view() {
-               $request = $this->getContext()->getRequest();
-               $diff = $request->getVal( 'diff' );
-               $diffOnly = $request->getBool( 'diffonly',
-                       $this->getContext()->getUser()->getOption( 'diffonly' ) );
-
-               if ( $diff !== null && $diffOnly ) {
-                       parent::view();
-                       return;
-               }
-
-               if ( !wfRunHooks( 'CategoryPageView', array( &$this ) ) ) {
-                       return;
-               }
-
-               $title = $this->getTitle();
-               if ( NS_CATEGORY == $title->getNamespace() ) {
-                       $this->openShowCategory();
-               }
-
-               parent::view();
-
-               if ( NS_CATEGORY == $title->getNamespace() ) {
-                       $this->closeShowCategory();
-               }
-       }
-
-       function openShowCategory() {
-               # For overloading
-       }
-
-       function closeShowCategory() {
-               // Use these as defaults for back compat --catrope
-               $request = $this->getContext()->getRequest();
-               $oldFrom = $request->getVal( 'from' );
-               $oldUntil = $request->getVal( 'until' );
-
-               $reqArray = $request->getValues();
-
-               $from = $until = array();
-               foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
-                       $from[$type] = $request->getVal( "{$type}from", $oldFrom );
-                       $until[$type] = $request->getVal( "{$type}until", $oldUntil );
-
-                       // Do not want old-style from/until propagating in nav links.
-                       if ( !isset( $reqArray["{$type}from"] ) && isset( $reqArray["from"] ) ) {
-                               $reqArray["{$type}from"] = $reqArray["from"];
-                       }
-                       if ( !isset( $reqArray["{$type}to"] ) && isset( $reqArray["to"] ) ) {
-                               $reqArray["{$type}to"] = $reqArray["to"];
-                       }
-               }
-
-               unset( $reqArray["from"] );
-               unset( $reqArray["to"] );
-
-               $viewer = new $this->mCategoryViewerClass(
-                       $this->getContext()->getTitle(),
-                       $this->getContext(),
-                       $from,
-                       $until,
-                       $reqArray
-               );
-               $this->getContext()->getOutput()->addHTML( $viewer->getHTML() );
-       }
-}
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
deleted file mode 100644 (file)
index 60db202..0000000
+++ /dev/null
@@ -1,1567 +0,0 @@
-<?php
-/**
- * Special handling for file description pages.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Class for viewing MediaWiki file description pages
- *
- * @ingroup Media
- */
-class ImagePage extends Article {
-       /** @var File */
-       private $displayImg;
-
-       /** @var FileRepo */
-       private $repo;
-
-       /** @var bool */
-       private $fileLoaded;
-
-       /** @var bool */
-       protected $mExtraDescription = false;
-
-       /**
-        * @param Title $title
-        * @return WikiFilePage
-        */
-       protected function newPage( Title $title ) {
-               // Overload mPage with a file-specific page
-               return new WikiFilePage( $title );
-       }
-
-       /**
-        * Constructor from a page id
-        * @param int $id Article ID to load
-        * @return ImagePage|null
-        */
-       public static function newFromID( $id ) {
-               $t = Title::newFromID( $id );
-               # @todo FIXME: Doesn't inherit right
-               return $t == null ? null : new self( $t );
-               # return $t == null ? null : new static( $t ); // PHP 5.3
-       }
-
-       /**
-        * @param File $file
-        * @return void
-        */
-       public function setFile( $file ) {
-               $this->mPage->setFile( $file );
-               $this->displayImg = $file;
-               $this->fileLoaded = true;
-       }
-
-       protected function loadFile() {
-               if ( $this->fileLoaded ) {
-                       return;
-               }
-               $this->fileLoaded = true;
-
-               $this->displayImg = $img = false;
-               wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) );
-               if ( !$img ) { // not set by hook?
-                       $img = wfFindFile( $this->getTitle() );
-                       if ( !$img ) {
-                               $img = wfLocalFile( $this->getTitle() );
-                       }
-               }
-               $this->mPage->setFile( $img );
-               if ( !$this->displayImg ) { // not set by hook?
-                       $this->displayImg = $img;
-               }
-               $this->repo = $img->getRepo();
-       }
-
-       /**
-        * Handler for action=render
-        * Include body text only; none of the image extras
-        */
-       public function render() {
-               $this->getContext()->getOutput()->setArticleBodyOnly( true );
-               parent::view();
-       }
-
-       public function view() {
-               global $wgShowEXIF;
-
-               $out = $this->getContext()->getOutput();
-               $request = $this->getContext()->getRequest();
-               $diff = $request->getVal( 'diff' );
-               $diffOnly = $request->getBool(
-                       'diffonly',
-                       $this->getContext()->getUser()->getOption( 'diffonly' )
-               );
-
-               if ( $this->getTitle()->getNamespace() != NS_FILE || ( $diff !== null && $diffOnly ) ) {
-                       parent::view();
-                       return;
-               }
-
-               $this->loadFile();
-
-               if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) {
-                       if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || $diff !== null ) {
-                               // mTitle is the same as the redirect target so ask Article
-                               // to perform the redirect for us.
-                               $request->setVal( 'diffonly', 'true' );
-                               parent::view();
-                               return;
-                       } else {
-                               // mTitle is not the same as the redirect target so it is
-                               // probably the redirect page itself. Fake the redirect symbol
-                               $out->setPageTitle( $this->getTitle()->getPrefixedText() );
-                               $out->addHTML( $this->viewRedirect(
-                                       Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
-                                       /* $appendSubtitle */ true,
-                                       /* $forceKnown */ true )
-                               );
-                               $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
-                               return;
-                       }
-               }
-
-               if ( $wgShowEXIF && $this->displayImg->exists() ) {
-                       // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
-                       $formattedMetadata = $this->displayImg->formatMetadata();
-                       $showmeta = $formattedMetadata !== false;
-               } else {
-                       $showmeta = false;
-               }
-
-               if ( !$diff && $this->displayImg->exists() ) {
-                       $out->addHTML( $this->showTOC( $showmeta ) );
-               }
-
-               if ( !$diff ) {
-                       $this->openShowImage();
-               }
-
-               # No need to display noarticletext, we use our own message, output in openShowImage()
-               if ( $this->mPage->getID() ) {
-                       # NS_FILE is in the user language, but this section (the actual wikitext)
-                       # should be in page content language
-                       $pageLang = $this->getTitle()->getPageViewLanguage();
-                       $out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
-                               'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
-                               'class' => 'mw-content-' . $pageLang->getDir() ) ) );
-
-                       parent::view();
-
-                       $out->addHTML( Xml::closeElement( 'div' ) );
-               } else {
-                       # Just need to set the right headers
-                       $out->setArticleFlag( true );
-                       $out->setPageTitle( $this->getTitle()->getPrefixedText() );
-                       $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
-               }
-
-               # Show shared description, if needed
-               if ( $this->mExtraDescription ) {
-                       $fol = wfMessage( 'shareddescriptionfollows' );
-                       if ( !$fol->isDisabled() ) {
-                               $out->addWikiText( $fol->plain() );
-                       }
-                       $out->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
-               }
-
-               $this->closeShowImage();
-               $this->imageHistory();
-               // TODO: Cleanup the following
-
-               $out->addHTML( Xml::element( 'h2',
-                       array( 'id' => 'filelinks' ),
-                       wfMessage( 'imagelinks' )->text() ) . "\n" );
-               $this->imageDupes();
-               # @todo FIXME: For some freaky reason, we can't redirect to foreign images.
-               # Yet we return metadata about the target. Definitely an issue in the FileRepo
-               $this->imageLinks();
-
-               # Allow extensions to add something after the image links
-               $html = '';
-               wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) );
-               if ( $html ) {
-                       $out->addHTML( $html );
-               }
-
-               if ( $showmeta ) {
-                       $out->addHTML( Xml::element(
-                               'h2',
-                               array( 'id' => 'metadata' ),
-                               wfMessage( 'metadata' )->text() ) . "\n" );
-                       $out->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
-                       $out->addModules( array( 'mediawiki.action.view.metadata' ) );
-               }
-
-               // Add remote Filepage.css
-               if ( !$this->repo->isLocal() ) {
-                       $css = $this->repo->getDescriptionStylesheetUrl();
-                       if ( $css ) {
-                               $out->addStyle( $css );
-                       }
-               }
-               // always show the local local Filepage.css, bug 29277
-               $out->addModuleStyles( 'filepage' );
-       }
-
-       /**
-        * @return File
-        */
-       public function getDisplayedFile() {
-               $this->loadFile();
-               return $this->displayImg;
-       }
-
-       /**
-        * Create the TOC
-        *
-        * @param bool $metadata Whether or not to show the metadata link
-        * @return string
-        */
-       protected function showTOC( $metadata ) {
-               $r = array(
-                       '<li><a href="#file">' . wfMessage( 'file-anchor-link' )->escaped() . '</a></li>',
-                       '<li><a href="#filehistory">' . wfMessage( 'filehist' )->escaped() . '</a></li>',
-                       '<li><a href="#filelinks">' . wfMessage( 'imagelinks' )->escaped() . '</a></li>',
-               );
-               if ( $metadata ) {
-                       $r[] = '<li><a href="#metadata">' . wfMessage( 'metadata' )->escaped() . '</a></li>';
-               }
-
-               wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) );
-
-               return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
-       }
-
-       /**
-        * Make a table with metadata to be shown in the output page.
-        *
-        * @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
-        *
-        * @param array $metadata The array containing the Exif data
-        * @return string The metadata table. This is treated as Wikitext (!)
-        */
-       protected function makeMetadataTable( $metadata ) {
-               $r = "<div class=\"mw-imagepage-section-metadata\">";
-               $r .= wfMessage( 'metadata-help' )->plain();
-               $r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n";
-               foreach ( $metadata as $type => $stuff ) {
-                       foreach ( $stuff as $v ) {
-                               # @todo FIXME: Why is this using escapeId for a class?!
-                               $class = Sanitizer::escapeId( $v['id'] );
-                               if ( $type == 'collapsed' ) {
-                                       // Handled by mediawiki.action.view.metadata module
-                                       // and skins/common/shared.css.
-                                       $class .= ' collapsable';
-                               }
-                               $r .= "<tr class=\"$class\">\n";
-                               $r .= "<th>{$v['name']}</th>\n";
-                               $r .= "<td>{$v['value']}</td>\n</tr>";
-                       }
-               }
-               $r .= "</table>\n</div>\n";
-               return $r;
-       }
-
-       /**
-        * Overloading Article's getContentObject method.
-        *
-        * Omit noarticletext if sharedupload; text will be fetched from the
-        * shared upload server if possible.
-        * @return string
-        */
-       public function getContentObject() {
-               $this->loadFile();
-               if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
-                       return null;
-               }
-               return parent::getContentObject();
-       }
-
-       protected function openShowImage() {
-               global $wgImageLimits, $wgEnableUploads, $wgSend404Code;
-
-               $this->loadFile();
-               $out = $this->getContext()->getOutput();
-               $user = $this->getContext()->getUser();
-               $lang = $this->getContext()->getLanguage();
-               $dirmark = $lang->getDirMarkEntity();
-               $request = $this->getContext()->getRequest();
-
-               $max = $this->getImageLimitsFromOption( $user, 'imagesize' );
-               $maxWidth = $max[0];
-               $maxHeight = $max[1];
-
-               if ( $this->displayImg->exists() ) {
-                       # image
-                       $page = $request->getIntOrNull( 'page' );
-                       if ( is_null( $page ) ) {
-                               $params = array();
-                               $page = 1;
-                       } else {
-                               $params = array( 'page' => $page );
-                       }
-
-                       $renderLang = $request->getVal( 'lang' );
-                       if ( !is_null( $renderLang ) ) {
-                               $handler = $this->displayImg->getHandler();
-                               if ( $handler && $handler->validateParam( 'lang', $renderLang ) ) {
-                                       $params['lang'] = $renderLang;
-                               } else {
-                                       $renderLang = null;
-                               }
-                       }
-
-                       $width_orig = $this->displayImg->getWidth( $page );
-                       $width = $width_orig;
-                       $height_orig = $this->displayImg->getHeight( $page );
-                       $height = $height_orig;
-
-                       $filename = wfEscapeWikiText( $this->displayImg->getName() );
-                       $linktext = $filename;
-
-                       wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) );
-
-                       if ( $this->displayImg->allowInlineDisplay() ) {
-                               # image
-
-                               # "Download high res version" link below the image
-                               # $msgsize = wfMessage( 'file-info-size', $width_orig, $height_orig,
-                               #   Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
-                               # We'll show a thumbnail of this image
-                               if ( $width > $maxWidth || $height > $maxHeight ) {
-                                       # Calculate the thumbnail size.
-                                       # First case, the limiting factor is the width, not the height.
-                                       /** @todo // FIXME: Possible division by 0. bug 36911 */
-                                       if ( $width / $height >= $maxWidth / $maxHeight ) {
-                                               /** @todo // FIXME: Possible division by 0. bug 36911 */
-                                               $height = round( $height * $maxWidth / $width );
-                                               $width = $maxWidth;
-                                               # Note that $height <= $maxHeight now.
-                                       } else {
-                                               /** @todo // FIXME: Possible division by 0. bug 36911 */
-                                               $newwidth = floor( $width * $maxHeight / $height );
-                                               /** @todo // FIXME: Possible division by 0. bug 36911 */
-                                               $height = round( $height * $newwidth / $width );
-                                               $width = $newwidth;
-                                               # Note that $height <= $maxHeight now, but might not be identical
-                                               # because of rounding.
-                                       }
-                                       $linktext = wfMessage( 'show-big-image' )->escaped();
-                                       if ( $this->displayImg->getRepo()->canTransformVia404() ) {
-                                               $thumbSizes = $wgImageLimits;
-                                               // Also include the full sized resolution in the list, so
-                                               // that users know they can get it. This will link to the
-                                               // original file asset if mustRender() === false. In the case
-                                               // that we mustRender, some users have indicated that they would
-                                               // find it useful to have the full size image in the rendered
-                                               // image format.
-                                               $thumbSizes[] = array( $width_orig, $height_orig );
-                                       } else {
-                                               # Creating thumb links triggers thumbnail generation.
-                                               # Just generate the thumb for the current users prefs.
-                                               $thumbSizes = array( $this->getImageLimitsFromOption( $user, 'thumbsize' ) );
-                                               if ( !$this->displayImg->mustRender() ) {
-                                                       // We can safely include a link to the "full-size" preview,
-                                                       // without actually rendering.
-                                                       $thumbSizes[] = array( $width_orig, $height_orig );
-                                               }
-                                       }
-                                       # Generate thumbnails or thumbnail links as needed...
-                                       $otherSizes = array();
-                                       foreach ( $thumbSizes as $size ) {
-                                               // We include a thumbnail size in the list, if it is
-                                               // less than or equal to the original size of the image
-                                               // asset ($width_orig/$height_orig). We also exclude
-                                               // the current thumbnail's size ($width/$height)
-                                               // since that is added to the message separately, so
-                                               // it can be denoted as the current size being shown.
-                                               if ( $size[0] <= $width_orig && $size[1] <= $height_orig
-                                                       && $size[0] != $width && $size[1] != $height
-                                               ) {
-                                                       $sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
-                                                       if ( $sizeLink ) {
-                                                               $otherSizes[] = $sizeLink;
-                                                       }
-                                               }
-                                       }
-                                       $otherSizes = array_unique( $otherSizes );
-                                       $msgsmall = '';
-                                       $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
-                                       if ( $sizeLinkBigImagePreview ) {
-                                               $msgsmall .= wfMessage( 'show-big-image-preview' )->
-                                                       rawParams( $sizeLinkBigImagePreview )->
-                                                       parse();
-                                       }
-                                       if ( count( $otherSizes ) ) {
-                                               $msgsmall .= ' ' .
-                                               Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ),
-                                                       wfMessage( 'show-big-image-other' )->rawParams( $lang->pipeList( $otherSizes ) )->
-                                                       params( count( $otherSizes ) )->parse()
-                                               );
-                                       }
-                               } elseif ( $width == 0 && $height == 0 ) {
-                                       # Some sort of audio file that doesn't have dimensions
-                                       # Don't output a no hi res message for such a file
-                                       $msgsmall = '';
-                               } elseif ( $this->displayImg->isVectorized() ) {
-                                       # For vectorized images, full size is just the frame size
-                                       $msgsmall = '';
-                               } else {
-                                       # Image is small enough to show full size on image page
-                                       $msgsmall = wfMessage( 'file-nohires' )->parse();
-                               }
-
-                               $params['width'] = $width;
-                               $params['height'] = $height;
-                               $thumbnail = $this->displayImg->transform( $params );
-                               Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params );
-
-                               $anchorclose = Html::rawElement(
-                                       'div',
-                                       array( 'class' => 'mw-filepage-resolutioninfo' ),
-                                       $msgsmall
-                               );
-
-                               $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
-                               if ( $isMulti ) {
-                                       $out->addModules( 'mediawiki.page.image.pagination' );
-                                       $out->addHTML( '<table class="multipageimage"><tr><td>' );
-                               }
-
-                               if ( $thumbnail ) {
-                                       $options = array(
-                                               'alt' => $this->displayImg->getTitle()->getPrefixedText(),
-                                               'file-link' => true,
-                                       );
-                                       $out->addHTML( '<div class="fullImageLink" id="file">' .
-                                               $thumbnail->toHtml( $options ) .
-                                               $anchorclose . "</div>\n" );
-                               }
-
-                               if ( $isMulti ) {
-                                       $count = $this->displayImg->pageCount();
-
-                                       if ( $page > 1 ) {
-                                               $label = $out->parse( wfMessage( 'imgmultipageprev' )->text(), false );
-                                               $link = Linker::linkKnown(
-                                                       $this->getTitle(),
-                                                       $label,
-                                                       array(),
-                                                       array( 'page' => $page - 1 )
-                                               );
-                                               $thumb1 = Linker::makeThumbLinkObj(
-                                                       $this->getTitle(),
-                                                       $this->displayImg,
-                                                       $link,
-                                                       $label,
-                                                       'none',
-                                                       array( 'page' => $page - 1 )
-                                               );
-                                       } else {
-                                               $thumb1 = '';
-                                       }
-
-                                       if ( $page < $count ) {
-                                               $label = wfMessage( 'imgmultipagenext' )->text();
-                                               $link = Linker::linkKnown(
-                                                       $this->getTitle(),
-                                                       $label,
-                                                       array(),
-                                                       array( 'page' => $page + 1 )
-                                               );
-                                               $thumb2 = Linker::makeThumbLinkObj(
-                                                       $this->getTitle(),
-                                                       $this->displayImg,
-                                                       $link,
-                                                       $label,
-                                                       'none',
-                                                       array( 'page' => $page + 1 )
-                                               );
-                                       } else {
-                                               $thumb2 = '';
-                                       }
-
-                                       global $wgScript;
-
-                                       $formParams = array(
-                                               'name' => 'pageselector',
-                                               'action' => $wgScript,
-                                       );
-                                       $options = array();
-                                       for ( $i = 1; $i <= $count; $i++ ) {
-                                               $options[] = Xml::option( $lang->formatNum( $i ), $i, $i == $page );
-                                       }
-                                       $select = Xml::tags( 'select',
-                                               array( 'id' => 'pageselector', 'name' => 'page' ),
-                                               implode( "\n", $options ) );
-
-                                       $out->addHTML(
-                                               '</td><td><div class="multipageimagenavbox">' .
-                                               Xml::openElement( 'form', $formParams ) .
-                                               Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
-                                                       wfMessage( 'imgmultigoto' )->rawParams( $select )->parse() .
-                                               Xml::submitButton( wfMessage( 'imgmultigo' )->text() ) .
-                                               Xml::closeElement( 'form' ) .
-                                               "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
-                                       );
-                               }
-                       } elseif ( $this->displayImg->isSafeFile() ) {
-                               # if direct link is allowed but it's not a renderable image, show an icon.
-                               $icon = $this->displayImg->iconThumb();
-
-                               $out->addHTML( '<div class="fullImageLink" id="file">' .
-                                       $icon->toHtml( array( 'file-link' => true ) ) .
-                                       "</div>\n" );
-                       }
-
-                       $longDesc = wfMessage( 'parentheses', $this->displayImg->getLongDesc() )->text();
-
-                       $medialink = "[[Media:$filename|$linktext]]";
-
-                       if ( !$this->displayImg->isSafeFile() ) {
-                               $warning = wfMessage( 'mediawarning' )->plain();
-                               // dirmark is needed here to separate the file name, which
-                               // most likely ends in Latin characters, from the description,
-                               // which may begin with the file type. In RTL environment
-                               // this will get messy.
-                               // The dirmark, however, must not be immediately adjacent
-                               // to the filename, because it can get copied with it.
-                               // See bug 25277.
-                               // @codingStandardsIgnoreStart Ignore long line
-                               $out->addWikiText( <<<EOT
-<div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
-<div class="mediaWarning">$warning</div>
-EOT
-                               );
-                               // @codingStandardsIgnoreEnd
-                       } else {
-                               $out->addWikiText( <<<EOT
-<div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
-</div>
-EOT
-                               );
-                       }
-
-                       $renderLangOptions = $this->displayImg->getAvailableLanguages();
-                       if ( count( $renderLangOptions ) >= 1 ) {
-                               $currentLanguage = $renderLang;
-                               $defaultLang = $this->displayImg->getDefaultRenderLanguage();
-                               if ( is_null( $currentLanguage ) ) {
-                                       $currentLanguage = $defaultLang;
-                               }
-                               $out->addHtml( $this->doRenderLangOpt( $renderLangOptions, $currentLanguage, $defaultLang ) );
-                       }
-
-                       // Add cannot animate thumbnail warning
-                       if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) {
-                               // Include the extension so wiki admins can
-                               // customize it on a per file-type basis
-                               // (aka say things like use format X instead).
-                               // additionally have a specific message for
-                               // file-no-thumb-animation-gif
-                               $ext = $this->displayImg->getExtension();
-                               $noAnimMesg = wfMessageFallback(
-                                       'file-no-thumb-animation-' . $ext,
-                                       'file-no-thumb-animation'
-                               )->plain();
-
-                               $out->addWikiText( <<<EOT
-<div class="mw-noanimatethumb">{$noAnimMesg}</div>
-EOT
-                               );
-                       }
-
-                       if ( !$this->displayImg->isLocal() ) {
-                               $this->printSharedImageText();
-                       }
-               } else {
-                       # Image does not exist
-                       if ( !$this->getID() ) {
-                               # No article exists either
-                               # Show deletion log to be consistent with normal articles
-                               LogEventsList::showLogExtract(
-                                       $out,
-                                       array( 'delete', 'move' ),
-                                       $this->getTitle()->getPrefixedText(),
-                                       '',
-                                       array( 'lim' => 10,
-                                               'conds' => array( "log_action != 'revision'" ),
-                                               'showIfEmpty' => false,
-                                               'msgKey' => array( 'moveddeleted-notice' )
-                                       )
-                               );
-                       }
-
-                       if ( $wgEnableUploads && $user->isAllowed( 'upload' ) ) {
-                               // Only show an upload link if the user can upload
-                               $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
-                               $nofile = array(
-                                       'filepage-nofile-link',
-                                       $uploadTitle->getFullURL( array( 'wpDestFile' => $this->mPage->getFile()->getName() ) )
-                               );
-                       } else {
-                               $nofile = 'filepage-nofile';
-                       }
-                       // Note, if there is an image description page, but
-                       // no image, then this setRobotPolicy is overridden
-                       // by Article::View().
-                       $out->setRobotPolicy( 'noindex,nofollow' );
-                       $out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
-                       if ( !$this->getID() && $wgSend404Code ) {
-                               // If there is no image, no shared image, and no description page,
-                               // output a 404, to be consistent with articles.
-                               $request->response()->header( 'HTTP/1.1 404 Not Found' );
-                       }
-               }
-               $out->setFileVersion( $this->displayImg );
-       }
-
-       /**
-        * Creates an thumbnail of specified size and returns an HTML link to it
-        * @param array $params Scaler parameters
-        * @param int $width
-        * @param int $height
-        * @return string
-        */
-       private function makeSizeLink( $params, $width, $height ) {
-               $params['width'] = $width;
-               $params['height'] = $height;
-               $thumbnail = $this->displayImg->transform( $params );
-               if ( $thumbnail && !$thumbnail->isError() ) {
-                       return Html::rawElement( 'a', array(
-                               'href' => $thumbnail->getUrl(),
-                               'class' => 'mw-thumbnail-link'
-                               ), wfMessage( 'show-big-image-size' )->numParams(
-                                       $thumbnail->getWidth(), $thumbnail->getHeight()
-                               )->parse() );
-               } else {
-                       return '';
-               }
-       }
-
-       /**
-        * Show a notice that the file is from a shared repository
-        */
-       protected function printSharedImageText() {
-               $out = $this->getContext()->getOutput();
-               $this->loadFile();
-
-               $descUrl = $this->mPage->getFile()->getDescriptionUrl();
-               $descText = $this->mPage->getFile()->getDescriptionText( $this->getContext()->getLanguage() );
-
-               /* Add canonical to head if there is no local page for this shared file */
-               if ( $descUrl && $this->mPage->getID() == 0 ) {
-                       $out->setCanonicalUrl( $descUrl );
-               }
-
-               $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
-               $repo = $this->mPage->getFile()->getRepo()->getDisplayName();
-
-               if ( $descUrl && $descText && wfMessage( 'sharedupload-desc-here' )->plain() !== '-' ) {
-                       $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
-               } elseif ( $descUrl && wfMessage( 'sharedupload-desc-there' )->plain() !== '-' ) {
-                       $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) );
-               } else {
-                       $out->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), ''/*BACKCOMPAT*/ );
-               }
-
-               if ( $descText ) {
-                       $this->mExtraDescription = $descText;
-               }
-       }
-
-       public function getUploadUrl() {
-               $this->loadFile();
-               $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
-               return $uploadTitle->getFullURL( array(
-                       'wpDestFile' => $this->mPage->getFile()->getName(),
-                       'wpForReUpload' => 1
-               ) );
-       }
-
-       /**
-        * Print out the various links at the bottom of the image page, e.g. reupload,
-        * external editing (and instructions link) etc.
-        */
-       protected function uploadLinksBox() {
-               global $wgEnableUploads;
-
-               if ( !$wgEnableUploads ) {
-                       return;
-               }
-
-               $this->loadFile();
-               if ( !$this->mPage->getFile()->isLocal() ) {
-                       return;
-               }
-
-               $out = $this->getContext()->getOutput();
-               $out->addHTML( "<ul>\n" );
-
-               # "Upload a new version of this file" link
-               $canUpload = $this->getTitle()->userCan( 'upload', $this->getContext()->getUser() );
-               if ( $canUpload && UploadBase::userCanReUpload(
-                               $this->getContext()->getUser(),
-                               $this->mPage->getFile()->name )
-               ) {
-                       $ulink = Linker::makeExternalLink(
-                               $this->getUploadUrl(),
-                               wfMessage( 'uploadnewversion-linktext' )->text()
-                       );
-                       $out->addHTML( "<li id=\"mw-imagepage-reupload-link\">"
-                               . "<div class=\"plainlinks\">{$ulink}</div></li>\n" );
-               } else {
-                       $out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">"
-                               . $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" );
-               }
-
-               $out->addHTML( "</ul>\n" );
-       }
-
-       /**
-        * For overloading
-        */
-       protected function closeShowImage() {
-       }
-
-       /**
-        * If the page we've just displayed is in the "Image" namespace,
-        * we follow it with an upload history of the image and its usage.
-        */
-       protected function imageHistory() {
-               $this->loadFile();
-               $out = $this->getContext()->getOutput();
-               $pager = new ImageHistoryPseudoPager( $this );
-               $out->addHTML( $pager->getBody() );
-               $out->preventClickjacking( $pager->getPreventClickjacking() );
-
-               $this->mPage->getFile()->resetHistory(); // free db resources
-
-               # Exist check because we don't want to show this on pages where an image
-               # doesn't exist along with the noimage message, that would suck. -ævar
-               if ( $this->mPage->getFile()->exists() ) {
-                       $this->uploadLinksBox();
-               }
-       }
-
-       /**
-        * @param string $target
-        * @param int $limit
-        * @return ResultWrapper
-        */
-       protected function queryImageLinks( $target, $limit ) {
-               $dbr = wfGetDB( DB_SLAVE );
-
-               return $dbr->select(
-                       array( 'imagelinks', 'page' ),
-                       array( 'page_namespace', 'page_title', 'il_to' ),
-                       array( 'il_to' => $target, 'il_from = page_id' ),
-                       __METHOD__,
-                       array( 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', )
-               );
-       }
-
-       protected function imageLinks() {
-               $limit = 100;
-
-               $out = $this->getContext()->getOutput();
-
-               $rows = array();
-               $redirects = array();
-               foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) {
-                       $redirects[$redir->getDBkey()] = array();
-                       $rows[] = (object)array(
-                               'page_namespace' => NS_FILE,
-                               'page_title' => $redir->getDBkey(),
-                       );
-               }
-
-               $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
-               foreach ( $res as $row ) {
-                       $rows[] = $row;
-               }
-               $count = count( $rows );
-
-               $hasMore = $count > $limit;
-               if ( !$hasMore && count( $redirects ) ) {
-                       $res = $this->queryImageLinks( array_keys( $redirects ),
-                               $limit - count( $rows ) + 1 );
-                       foreach ( $res as $row ) {
-                               $redirects[$row->il_to][] = $row;
-                               $count++;
-                       }
-                       $hasMore = ( $res->numRows() + count( $rows ) ) > $limit;
-               }
-
-               if ( $count == 0 ) {
-                       $out->wrapWikiMsg(
-                               Html::rawElement( 'div',
-                                       array( 'id' => 'mw-imagepage-nolinkstoimage' ), "\n$1\n" ),
-                               'nolinkstoimage'
-                       );
-                       return;
-               }
-
-               $out->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
-               if ( !$hasMore ) {
-                       $out->addWikiMsg( 'linkstoimage', $count );
-               } else {
-                       // More links than the limit. Add a link to [[Special:Whatlinkshere]]
-                       $out->addWikiMsg( 'linkstoimage-more',
-                               $this->getContext()->getLanguage()->formatNum( $limit ),
-                               $this->getTitle()->getPrefixedDBkey()
-                       );
-               }
-
-               $out->addHTML(
-                       Html::openElement( 'ul',
-                               array( 'class' => 'mw-imagepage-linkstoimage' ) ) . "\n"
-               );
-               $count = 0;
-
-               // Sort the list by namespace:title
-               usort( $rows, array( $this, 'compare' ) );
-
-               // Create links for every element
-               $currentCount = 0;
-               foreach ( $rows as $element ) {
-                       $currentCount++;
-                       if ( $currentCount > $limit ) {
-                               break;
-                       }
-
-                       $query = array();
-                       # Add a redirect=no to make redirect pages reachable
-                       if ( isset( $redirects[$element->page_title] ) ) {
-                               $query['redirect'] = 'no';
-                       }
-                       $link = Linker::linkKnown(
-                               Title::makeTitle( $element->page_namespace, $element->page_title ),
-                               null, array(), $query
-                       );
-                       if ( !isset( $redirects[$element->page_title] ) ) {
-                               # No redirects
-                               $liContents = $link;
-                       } elseif ( count( $redirects[$element->page_title] ) === 0 ) {
-                               # Redirect without usages
-                               $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse();
-                       } else {
-                               # Redirect with usages
-                               $li = '';
-                               foreach ( $redirects[$element->page_title] as $row ) {
-                                       $currentCount++;
-                                       if ( $currentCount > $limit ) {
-                                               break;
-                                       }
-
-                                       $link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) );
-                                       $li .= Html::rawElement(
-                                               'li',
-                                               array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
-                                               $link2
-                                               ) . "\n";
-                               }
-
-                               $ul = Html::rawElement(
-                                       'ul',
-                                       array( 'class' => 'mw-imagepage-redirectstofile' ),
-                                       $li
-                                       ) . "\n";
-                               $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams(
-                                       $link, $ul )->parse();
-                       }
-                       $out->addHTML( Html::rawElement(
-                                       'li',
-                                       array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
-                                       $liContents
-                               ) . "\n"
-                       );
-
-               };
-               $out->addHTML( Html::closeElement( 'ul' ) . "\n" );
-               $res->free();
-
-               // Add a links to [[Special:Whatlinkshere]]
-               if ( $count > $limit ) {
-                       $out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
-               }
-               $out->addHTML( Html::closeElement( 'div' ) . "\n" );
-       }
-
-       protected function imageDupes() {
-               $this->loadFile();
-               $out = $this->getContext()->getOutput();
-
-               $dupes = $this->mPage->getDuplicates();
-               if ( count( $dupes ) == 0 ) {
-                       return;
-               }
-
-               $out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
-               $out->addWikiMsg( 'duplicatesoffile',
-                       $this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
-               );
-               $out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
-
-               /**
-                * @var $file File
-                */
-               foreach ( $dupes as $file ) {
-                       $fromSrc = '';
-                       if ( $file->isLocal() ) {
-                               $link = Linker::linkKnown( $file->getTitle() );
-                       } else {
-                               $link = Linker::makeExternalLink( $file->getDescriptionUrl(),
-                                       $file->getTitle()->getPrefixedText() );
-                               $fromSrc = wfMessage( 'shared-repo-from', $file->getRepo()->getDisplayName() )->text();
-                       }
-                       $out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
-               }
-               $out->addHTML( "</ul></div>\n" );
-       }
-
-       /**
-        * Delete the file, or an earlier version of it
-        */
-       public function delete() {
-               $file = $this->mPage->getFile();
-               if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
-                       // Standard article deletion
-                       parent::delete();
-                       return;
-               }
-
-               $deleter = new FileDeleteForm( $file );
-               $deleter->execute();
-       }
-
-       /**
-        * Display an error with a wikitext description
-        *
-        * @param string $description
-        */
-       function showError( $description ) {
-               $out = $this->getContext()->getOutput();
-               $out->setPageTitle( wfMessage( 'internalerror' ) );
-               $out->setRobotPolicy( 'noindex,nofollow' );
-               $out->setArticleRelated( false );
-               $out->enableClientCache( false );
-               $out->addWikiText( $description );
-       }
-
-       /**
-        * Callback for usort() to do link sorts by (namespace, title)
-        * Function copied from Title::compare()
-        *
-        * @param object $a Object page to compare with
-        * @param object $b Object page to compare with
-        * @return int Result of string comparison, or namespace comparison
-        */
-       protected function compare( $a, $b ) {
-               if ( $a->page_namespace == $b->page_namespace ) {
-                       return strcmp( $a->page_title, $b->page_title );
-               } else {
-                       return $a->page_namespace - $b->page_namespace;
-               }
-       }
-
-       /**
-        * Returns the corresponding $wgImageLimits entry for the selected user option
-        *
-        * @param User $user
-        * @param string $optionName Name of a option to check, typically imagesize or thumbsize
-        * @return array
-        * @since 1.21
-        */
-       public function getImageLimitsFromOption( $user, $optionName ) {
-               global $wgImageLimits;
-
-               $option = $user->getIntOption( $optionName );
-               if ( !isset( $wgImageLimits[$option] ) ) {
-                       $option = User::getDefaultOption( $optionName );
-               }
-
-               // The user offset might still be incorrect, specially if
-               // $wgImageLimits got changed (see bug #8858).
-               if ( !isset( $wgImageLimits[$option] ) ) {
-                       // Default to the first offset in $wgImageLimits
-                       $option = 0;
-               }
-
-               return isset( $wgImageLimits[$option] )
-                       ? $wgImageLimits[$option]
-                       : array( 800, 600 ); // if nothing is set, fallback to a hardcoded default
-       }
-
-       /**
-        * Output a drop-down box for language options for the file
-        *
-        * @param array $langChoices Array of string language codes
-        * @param string $curLang Language code file is being viewed in.
-        * @param string $defaultLang Language code that image is rendered in by default
-        * @return string HTML to insert underneath image.
-        */
-       protected function doRenderLangOpt( array $langChoices, $curLang, $defaultLang ) {
-               global $wgScript;
-               sort( $langChoices );
-               $curLang = wfBCP47( $curLang );
-               $defaultLang = wfBCP47( $defaultLang );
-               $opts = '';
-               $haveCurrentLang = false;
-               $haveDefaultLang = false;
-
-               // We make a list of all the language choices in the file.
-               // Additionally if the default language to render this file
-               // is not included as being in this file (for example, in svgs
-               // usually the fallback content is the english content) also
-               // include a choice for that. Last of all, if we're viewing
-               // the file in a language not on the list, add it as a choice.
-               foreach ( $langChoices as $lang ) {
-                       $code = wfBCP47( $lang );
-                       $name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
-                       if ( $name !== '' ) {
-                               $display = wfMessage( 'img-lang-opt', $code, $name )->text();
-                       } else {
-                               $display = $code;
-                       }
-                       $opts .= "\n" . Xml::option( $display, $code, $curLang === $code );
-                       if ( $curLang === $code ) {
-                               $haveCurrentLang = true;
-                       }
-                       if ( $defaultLang === $code ) {
-                               $haveDefaultLang = true;
-                       }
-               }
-               if ( !$haveDefaultLang ) {
-                       // Its hard to know if the content is really in the default language, or
-                       // if its just unmarked content that could be in any language.
-                       $opts = Xml::option(
-                               wfMessage( 'img-lang-default' )->text(),
-                               $defaultLang,
-                               $defaultLang === $curLang
-                       ) . $opts;
-               }
-               if ( !$haveCurrentLang && $defaultLang !== $curLang ) {
-                       $name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() );
-                       if ( $name !== '' ) {
-                               $display = wfMessage( 'img-lang-opt', $curLang, $name )->text();
-                       } else {
-                               $display = $curLang;
-                       }
-                       $opts = Xml::option( $display, $curLang, true ) . $opts;
-               }
-
-               $select = Html::rawElement(
-                       'select',
-                       array( 'id' => 'mw-imglangselector', 'name' => 'lang' ),
-                       $opts
-               );
-               $submit = Xml::submitButton( wfMessage( 'img-lang-go' )->text() );
-
-               $formContents = wfMessage( 'img-lang-info' )->rawParams( $select, $submit )->parse()
-                       . Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
-
-               $langSelectLine = Html::rawElement( 'div', array( 'id' => 'mw-imglangselector-line' ),
-                       Html::rawElement( 'form', array( 'action' => $wgScript ), $formContents )
-               );
-               return $langSelectLine;
-       }
-}
-
-/**
- * Builds the image revision log shown on image pages
- *
- * @ingroup Media
- */
-class ImageHistoryList extends ContextSource {
-
-       /**
-        * @var Title
-        */
-       protected $title;
-
-       /**
-        * @var File
-        */
-       protected $img;
-
-       /**
-        * @var ImagePage
-        */
-       protected $imagePage;
-
-       /**
-        * @var File
-        */
-       protected $current;
-
-       protected $repo, $showThumb;
-       protected $preventClickjacking = false;
-
-       /**
-        * @param ImagePage $imagePage
-        */
-       public function __construct( $imagePage ) {
-               global $wgShowArchiveThumbnails;
-               $this->current = $imagePage->getFile();
-               $this->img = $imagePage->getDisplayedFile();
-               $this->title = $imagePage->getTitle();
-               $this->imagePage = $imagePage;
-               $this->showThumb = $wgShowArchiveThumbnails && $this->img->canRender();
-               $this->setContext( $imagePage->getContext() );
-       }
-
-       /**
-        * @return ImagePage
-        */
-       public function getImagePage() {
-               return $this->imagePage;
-       }
-
-       /**
-        * @return File
-        */
-       public function getFile() {
-               return $this->img;
-       }
-
-       /**
-        * @param string $navLinks
-        * @return string
-        */
-       public function beginImageHistoryList( $navLinks = '' ) {
-               return Xml::element( 'h2', array( 'id' => 'filehistory' ), $this->msg( 'filehist' )->text() )
-                       . "\n"
-                       . "<div id=\"mw-imagepage-section-filehistory\">\n"
-                       . $this->msg( 'filehist-help' )->parseAsBlock()
-                       . $navLinks . "\n"
-                       . Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n"
-                       . '<tr><td></td>'
-                       . ( $this->current->isLocal()
-                               && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' )
-                       . '<th>' . $this->msg( 'filehist-datetime' )->escaped() . '</th>'
-                       . ( $this->showThumb ? '<th>' . $this->msg( 'filehist-thumb' )->escaped() . '</th>' : '' )
-                       . '<th>' . $this->msg( 'filehist-dimensions' )->escaped() . '</th>'
-                       . '<th>' . $this->msg( 'filehist-user' )->escaped() . '</th>'
-                       . '<th>' . $this->msg( 'filehist-comment' )->escaped() . '</th>'
-                       . "</tr>\n";
-       }
-
-       /**
-        * @param string $navLinks
-        * @return string
-        */
-       public function endImageHistoryList( $navLinks = '' ) {
-               return "</table>\n$navLinks\n</div>\n";
-       }
-
-       /**
-        * @param bool $iscur
-        * @param File $file
-        * @return string
-        */
-       public function imageHistoryLine( $iscur, $file ) {
-               global $wgContLang;
-
-               $user = $this->getUser();
-               $lang = $this->getLanguage();
-               $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
-               $img = $iscur ? $file->getName() : $file->getArchiveName();
-               $userId = $file->getUser( 'id' );
-               $userText = $file->getUser( 'text' );
-               $description = $file->getDescription( File::FOR_THIS_USER, $user );
-
-               $local = $this->current->isLocal();
-               $row = $selected = '';
-
-               // Deletion link
-               if ( $local && ( $user->isAllowedAny( 'delete', 'deletedhistory' ) ) ) {
-                       $row .= '<td>';
-                       # Link to remove from history
-                       if ( $user->isAllowed( 'delete' ) ) {
-                               $q = array( 'action' => 'delete' );
-                               if ( !$iscur ) {
-                                       $q['oldimage'] = $img;
-                               }
-                               $row .= Linker::linkKnown(
-                                       $this->title,
-                                       $this->msg( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' )->escaped(),
-                                       array(), $q
-                               );
-                       }
-                       # Link to hide content. Don't show useless link to people who cannot hide revisions.
-                       $canHide = $user->isAllowed( 'deleterevision' );
-                       if ( $canHide || ( $user->isAllowed( 'deletedhistory' ) && $file->getVisibility() ) ) {
-                               if ( $user->isAllowed( 'delete' ) ) {
-                                       $row .= '<br />';
-                               }
-                               // If file is top revision or locked from this user, don't link
-                               if ( $iscur || !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
-                                       $del = Linker::revDeleteLinkDisabled( $canHide );
-                               } else {
-                                       list( $ts, ) = explode( '!', $img, 2 );
-                                       $query = array(
-                                               'type' => 'oldimage',
-                                               'target' => $this->title->getPrefixedText(),
-                                               'ids' => $ts,
-                                       );
-                                       $del = Linker::revDeleteLink( $query,
-                                               $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
-                               }
-                               $row .= $del;
-                       }
-                       $row .= '</td>';
-               }
-
-               // Reversion link/current indicator
-               $row .= '<td>';
-               if ( $iscur ) {
-                       $row .= $this->msg( 'filehist-current' )->escaped();
-               } elseif ( $local && $this->title->quickUserCan( 'edit', $user )
-                       && $this->title->quickUserCan( 'upload', $user )
-               ) {
-                       if ( $file->isDeleted( File::DELETED_FILE ) ) {
-                               $row .= $this->msg( 'filehist-revert' )->escaped();
-                       } else {
-                               $row .= Linker::linkKnown(
-                                       $this->title,
-                                       $this->msg( 'filehist-revert' )->escaped(),
-                                       array(),
-                                       array(
-                                               'action' => 'revert',
-                                               'oldimage' => $img,
-                                               'wpEditToken' => $user->getEditToken( $img )
-                                       )
-                               );
-                       }
-               }
-               $row .= '</td>';
-
-               // Date/time and image link
-               if ( $file->getTimestamp() === $this->img->getTimestamp() ) {
-                       $selected = "class='filehistory-selected'";
-               }
-               $row .= "<td $selected style='white-space: nowrap;'>";
-               if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
-                       # Don't link to unviewable files
-                       $row .= '<span class="history-deleted">'
-                               . $lang->userTimeAndDate( $timestamp, $user ) . '</span>';
-               } elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
-                       if ( $local ) {
-                               $this->preventClickjacking();
-                               $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
-                               # Make a link to review the image
-                               $url = Linker::linkKnown(
-                                       $revdel,
-                                       $lang->userTimeAndDate( $timestamp, $user ),
-                                       array(),
-                                       array(
-                                               'target' => $this->title->getPrefixedText(),
-                                               'file' => $img,
-                                               'token' => $user->getEditToken( $img )
-                                       )
-                               );
-                       } else {
-                               $url = $lang->userTimeAndDate( $timestamp, $user );
-                       }
-                       $row .= '<span class="history-deleted">' . $url . '</span>';
-               } else {
-                       $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
-                       $row .= Xml::element(
-                               'a',
-                               array( 'href' => $url ),
-                               $lang->userTimeAndDate( $timestamp, $user )
-                       );
-               }
-               $row .= "</td>";
-
-               // Thumbnail
-               if ( $this->showThumb ) {
-                       $row .= '<td>' . $this->getThumbForLine( $file ) . '</td>';
-               }
-
-               // Image dimensions + size
-               $row .= '<td>';
-               $row .= htmlspecialchars( $file->getDimensionsString() );
-               $row .= $this->msg( 'word-separator' )->escaped();
-               $row .= '<span style="white-space: nowrap;">';
-               $row .= $this->msg( 'parentheses' )->sizeParams( $file->getSize() )->escaped();
-               $row .= '</span>';
-               $row .= '</td>';
-
-               // Uploading user
-               $row .= '<td>';
-               // Hide deleted usernames
-               if ( $file->isDeleted( File::DELETED_USER ) ) {
-                       $row .= '<span class="history-deleted">'
-                               . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
-               } else {
-                       if ( $local ) {
-                               $row .= Linker::userLink( $userId, $userText );
-                               $row .= $this->msg( 'word-separator' )->escaped();
-                               $row .= '<span style="white-space: nowrap;">';
-                               $row .= Linker::userToolLinks( $userId, $userText );
-                               $row .= '</span>';
-                       } else {
-                               $row .= htmlspecialchars( $userText );
-                       }
-               }
-               $row .= '</td>';
-
-               // Don't show deleted descriptions
-               if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
-                       $row .= '<td><span class="history-deleted">' .
-                               $this->msg( 'rev-deleted-comment' )->escaped() . '</span></td>';
-               } else {
-                       $row .= '<td dir="' . $wgContLang->getDir() . '">' .
-                               Linker::formatComment( $description, $this->title ) . '</td>';
-               }
-
-               $rowClass = null;
-               wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
-               $classAttr = $rowClass ? " class='$rowClass'" : '';
-
-               return "<tr{$classAttr}>{$row}</tr>\n";
-       }
-
-       /**
-        * @param File $file
-        * @return string
-        */
-       protected function getThumbForLine( $file ) {
-               $lang = $this->getLanguage();
-               $user = $this->getUser();
-               if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE, $user )
-                       && !$file->isDeleted( File::DELETED_FILE )
-               ) {
-                       $params = array(
-                               'width' => '120',
-                               'height' => '120',
-                       );
-                       $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
-
-                       $thumbnail = $file->transform( $params );
-                       $options = array(
-                               'alt' => $this->msg( 'filehist-thumbtext',
-                                       $lang->userTimeAndDate( $timestamp, $user ),
-                                       $lang->userDate( $timestamp, $user ),
-                                       $lang->userTime( $timestamp, $user ) )->text(),
-                               'file-link' => true,
-                       );
-
-                       if ( !$thumbnail ) {
-                               return $this->msg( 'filehist-nothumb' )->escaped();
-                       }
-
-                       return $thumbnail->toHtml( $options );
-               } else {
-                       return $this->msg( 'filehist-nothumb' )->escaped();
-               }
-       }
-
-       /**
-        * @param bool $enable
-        */
-       protected function preventClickjacking( $enable = true ) {
-               $this->preventClickjacking = $enable;
-       }
-
-       /**
-        * @return bool
-        */
-       public function getPreventClickjacking() {
-               return $this->preventClickjacking;
-       }
-}
-
-class ImageHistoryPseudoPager extends ReverseChronologicalPager {
-       protected $preventClickjacking = false;
-
-       /**
-        * @var File
-        */
-       protected $mImg;
-
-       /**
-        * @var Title
-        */
-       protected $mTitle;
-
-       /**
-        * @param ImagePage $imagePage
-        */
-       function __construct( $imagePage ) {
-               parent::__construct( $imagePage->getContext() );
-               $this->mImagePage = $imagePage;
-               $this->mTitle = clone ( $imagePage->getTitle() );
-               $this->mTitle->setFragment( '#filehistory' );
-               $this->mImg = null;
-               $this->mHist = array();
-               $this->mRange = array( 0, 0 ); // display range
-       }
-
-       /**
-        * @return Title
-        */
-       function getTitle() {
-               return $this->mTitle;
-       }
-
-       function getQueryInfo() {
-               return false;
-       }
-
-       /**
-        * @return string
-        */
-       function getIndexField() {
-               return '';
-       }
-
-       /**
-        * @param object $row
-        * @return string
-        */
-       function formatRow( $row ) {
-               return '';
-       }
-
-       /**
-        * @return string
-        */
-       function getBody() {
-               $s = '';
-               $this->doQuery();
-               if ( count( $this->mHist ) ) {
-                       $list = new ImageHistoryList( $this->mImagePage );
-                       # Generate prev/next links
-                       $navLink = $this->getNavigationBar();
-                       $s = $list->beginImageHistoryList( $navLink );
-                       // Skip rows there just for paging links
-                       for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
-                               $file = $this->mHist[$i];
-                               $s .= $list->imageHistoryLine( !$file->isOld(), $file );
-                       }
-                       $s .= $list->endImageHistoryList( $navLink );
-
-                       if ( $list->getPreventClickjacking() ) {
-                               $this->preventClickjacking();
-                       }
-               }
-               return $s;
-       }
-
-       function doQuery() {
-               if ( $this->mQueryDone ) {
-                       return;
-               }
-               $this->mImg = $this->mImagePage->getFile(); // ensure loading
-               if ( !$this->mImg->exists() ) {
-                       return;
-               }
-               $queryLimit = $this->mLimit + 1; // limit plus extra row
-               if ( $this->mIsBackwards ) {
-                       // Fetch the file history
-                       $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false );
-                       // The current rev may not meet the offset/limit
-                       $numRows = count( $this->mHist );
-                       if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) {
-                               $this->mHist = array_merge( array( $this->mImg ), $this->mHist );
-                       }
-               } else {
-                       // The current rev may not meet the offset
-                       if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) {
-                               $this->mHist[] = $this->mImg;
-                       }
-                       // Old image versions (fetch extra row for nav links)
-                       $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1;
-                       // Fetch the file history
-                       $this->mHist = array_merge( $this->mHist,
-                               $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) );
-               }
-               $numRows = count( $this->mHist ); // Total number of query results
-               if ( $numRows ) {
-                       # Index value of top item in the list
-                       $firstIndex = $this->mIsBackwards ?
-                               $this->mHist[$numRows - 1]->getTimestamp() : $this->mHist[0]->getTimestamp();
-                       # Discard the extra result row if there is one
-                       if ( $numRows > $this->mLimit && $numRows > 1 ) {
-                               if ( $this->mIsBackwards ) {
-                                       # Index value of item past the index
-                                       $this->mPastTheEndIndex = $this->mHist[0]->getTimestamp();
-                                       # Index value of bottom item in the list
-                                       $lastIndex = $this->mHist[1]->getTimestamp();
-                                       # Display range
-                                       $this->mRange = array( 1, $numRows - 1 );
-                               } else {
-                                       # Index value of item past the index
-                                       $this->mPastTheEndIndex = $this->mHist[$numRows - 1]->getTimestamp();
-                                       # Index value of bottom item in the list
-                                       $lastIndex = $this->mHist[$numRows - 2]->getTimestamp();
-                                       # Display range
-                                       $this->mRange = array( 0, $numRows - 2 );
-                               }
-                       } else {
-                               # Setting indexes to an empty string means that they will be
-                               # omitted if they would otherwise appear in URLs. It just so
-                               # happens that this  is the right thing to do in the standard
-                               # UI, in all the relevant cases.
-                               $this->mPastTheEndIndex = '';
-                               # Index value of bottom item in the list
-                               $lastIndex = $this->mIsBackwards ?
-                                       $this->mHist[0]->getTimestamp() : $this->mHist[$numRows - 1]->getTimestamp();
-                               # Display range
-                               $this->mRange = array( 0, $numRows - 1 );
-                       }
-               } else {
-                       $firstIndex = '';
-                       $lastIndex = '';
-                       $this->mPastTheEndIndex = '';
-               }
-               if ( $this->mIsBackwards ) {
-                       $this->mIsFirst = ( $numRows < $queryLimit );
-                       $this->mIsLast = ( $this->mOffset == '' );
-                       $this->mLastShown = $firstIndex;
-                       $this->mFirstShown = $lastIndex;
-               } else {
-                       $this->mIsFirst = ( $this->mOffset == '' );
-                       $this->mIsLast = ( $numRows < $queryLimit );
-                       $this->mLastShown = $lastIndex;
-                       $this->mFirstShown = $firstIndex;
-               }
-               $this->mQueryDone = true;
-       }
-
-       /**
-        * @param bool $enable
-        */
-       protected function preventClickjacking( $enable = true ) {
-               $this->preventClickjacking = $enable;
-       }
-
-       /**
-        * @return bool
-        */
-       public function getPreventClickjacking() {
-               return $this->preventClickjacking;
-       }
-
-}
diff --git a/includes/WikiCategoryPage.php b/includes/WikiCategoryPage.php
deleted file mode 100644 (file)
index d382001..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-/**
- * Special handling for category pages.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Special handling for category pages
- */
-class WikiCategoryPage extends WikiPage {
-
-       /**
-        * Don't return a 404 for categories in use.
-        * In use defined as: either the actual page exists
-        * or the category currently has members.
-        *
-        * @return bool
-        */
-       public function hasViewableContent() {
-               if ( parent::hasViewableContent() ) {
-                       return true;
-               } else {
-                       $cat = Category::newFromTitle( $this->mTitle );
-                       // If any of these are not 0, then has members
-                       if ( $cat->getPageCount()
-                               || $cat->getSubcatCount()
-                               || $cat->getFileCount()
-                       ) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-}
diff --git a/includes/WikiFilePage.php b/includes/WikiFilePage.php
deleted file mode 100644 (file)
index 34f15c3..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-<?php
-/**
- * Special handling for file pages.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Special handling for file pages
- *
- * @ingroup Media
- */
-class WikiFilePage extends WikiPage {
-       /**
-        * @var File
-        */
-       protected $mFile = false;                               // !< File object
-       protected $mRepo = null;                            // !<
-       protected $mFileLoaded = false;             // !<
-       protected $mDupes = null;                               // !<
-
-       public function __construct( $title ) {
-               parent::__construct( $title );
-               $this->mDupes = null;
-               $this->mRepo = null;
-       }
-
-       public function getActionOverrides() {
-               $overrides = parent::getActionOverrides();
-               $overrides['revert'] = 'RevertFileAction';
-               return $overrides;
-       }
-
-       /**
-        * @param File $file
-        */
-       public function setFile( $file ) {
-               $this->mFile = $file;
-               $this->mFileLoaded = true;
-       }
-
-       /**
-        * @return bool
-        */
-       protected function loadFile() {
-               if ( $this->mFileLoaded ) {
-                       return true;
-               }
-               $this->mFileLoaded = true;
-
-               $this->mFile = wfFindFile( $this->mTitle );
-               if ( !$this->mFile ) {
-                       $this->mFile = wfLocalFile( $this->mTitle ); // always a File
-               }
-               $this->mRepo = $this->mFile->getRepo();
-               return true;
-       }
-
-       /**
-        * @return mixed|null|Title
-        */
-       public function getRedirectTarget() {
-               $this->loadFile();
-               if ( $this->mFile->isLocal() ) {
-                       return parent::getRedirectTarget();
-               }
-               // Foreign image page
-               $from = $this->mFile->getRedirected();
-               $to = $this->mFile->getName();
-               if ( $from == $to ) {
-                       return null;
-               }
-               $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
-               return $this->mRedirectTarget;
-       }
-
-       /**
-        * @return bool|mixed|Title
-        */
-       public function followRedirect() {
-               $this->loadFile();
-               if ( $this->mFile->isLocal() ) {
-                       return parent::followRedirect();
-               }
-               $from = $this->mFile->getRedirected();
-               $to = $this->mFile->getName();
-               if ( $from == $to ) {
-                       return false;
-               }
-               return Title::makeTitle( NS_FILE, $to );
-       }
-
-       /**
-        * @return bool
-        */
-       public function isRedirect() {
-               $this->loadFile();
-               if ( $this->mFile->isLocal() ) {
-                       return parent::isRedirect();
-               }
-
-               return (bool)$this->mFile->getRedirected();
-       }
-
-       /**
-        * @return bool
-        */
-       public function isLocal() {
-               $this->loadFile();
-               return $this->mFile->isLocal();
-       }
-
-       /**
-        * @return bool|File
-        */
-       public function getFile() {
-               $this->loadFile();
-               return $this->mFile;
-       }
-
-       /**
-        * @return array|null
-        */
-       public function getDuplicates() {
-               $this->loadFile();
-               if ( !is_null( $this->mDupes ) ) {
-                       return $this->mDupes;
-               }
-               $hash = $this->mFile->getSha1();
-               if ( !( $hash ) ) {
-                       $this->mDupes = array();
-                       return $this->mDupes;
-               }
-               $dupes = RepoGroup::singleton()->findBySha1( $hash );
-               // Remove duplicates with self and non matching file sizes
-               $self = $this->mFile->getRepoName() . ':' . $this->mFile->getName();
-               $size = $this->mFile->getSize();
-
-               /**
-                * @var $file File
-                */
-               foreach ( $dupes as $index => $file ) {
-                       $key = $file->getRepoName() . ':' . $file->getName();
-                       if ( $key == $self ) {
-                               unset( $dupes[$index] );
-                       }
-                       if ( $file->getSize() != $size ) {
-                               unset( $dupes[$index] );
-                       }
-               }
-               $this->mDupes = $dupes;
-               return $this->mDupes;
-       }
-
-       /**
-        * Override handling of action=purge
-        * @return bool
-        */
-       public function doPurge() {
-               $this->loadFile();
-               if ( $this->mFile->exists() ) {
-                       wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
-                       $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
-                       $update->doUpdate();
-                       $this->mFile->upgradeRow();
-                       $this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
-               } else {
-                       wfDebug( 'ImagePage::doPurge no image for '
-                               . $this->mFile->getName() . "; limiting purge to cache only\n" );
-                       // even if the file supposedly doesn't exist, force any cached information
-                       // to be updated (in case the cached information is wrong)
-                       $this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
-               }
-               if ( $this->mRepo ) {
-                       // Purge redirect cache
-                       $this->mRepo->invalidateImageRedirect( $this->mTitle );
-               }
-               return parent::doPurge();
-       }
-
-       /**
-        * Get the categories this file is a member of on the wiki where it was uploaded.
-        * For local files, this is the same as getCategories().
-        * For foreign API files (InstantCommons), this is not supported currently.
-        * Results will include hidden categories.
-        *
-        * @return TitleArray|Title[]
-        * @since 1.23
-        */
-       public function getForeignCategories() {
-               $this->loadFile();
-               $title = $this->mTitle;
-               $file = $this->mFile;
-
-               if ( ! $file instanceof LocalFile ) {
-                       wfDebug( __CLASS__ . '::' . __METHOD__ . " is not supported for this file\n" );
-                       return TitleArray::newFromResult( new FakeResultWrapper( array() ) );
-               }
-
-               /** @var LocalRepo $repo */
-               $repo = $file->getRepo();
-               $dbr = $repo->getSlaveDB();
-
-               $res = $dbr->select(
-                       array( 'page', 'categorylinks' ),
-                       array(
-                               'page_title' => 'cl_to',
-                               'page_namespace' => NS_CATEGORY,
-                       ),
-                       array(
-                               'page_namespace' => $title->getNamespace(),
-                               'page_title' => $title->getDBkey(),
-                       ),
-                       __METHOD__,
-                       array(),
-                       array( 'categorylinks' => array( 'INNER JOIN', 'page_id = cl_from' ) )
-               );
-
-               return TitleArray::newFromResult( $res );
-       }
-}
diff --git a/includes/WikiPage.php b/includes/WikiPage.php
deleted file mode 100644 (file)
index c4d1bf3..0000000
+++ /dev/null
@@ -1,3760 +0,0 @@
-<?php
-/**
- * Base representation for a MediaWiki page.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
- */
-interface Page {
-}
-
-/**
- * Class representing a MediaWiki article and history.
- *
- * Some fields are public only for backwards-compatibility. Use accessors.
- * In the past, this class was part of Article.php and everything was public.
- *
- * @internal documentation reviewed 15 Mar 2010
- */
-class WikiPage implements Page, IDBAccessObject {
-       // Constants for $mDataLoadedFrom and related
-
-       /**
-        * @var Title
-        */
-       public $mTitle = null;
-
-       /**@{{
-        * @protected
-        */
-       public $mDataLoaded = false;         // !< Boolean
-       public $mIsRedirect = false;         // !< Boolean
-       public $mLatest = false;             // !< Integer (false means "not loaded")
-       /**@}}*/
-
-       /** @var stdclass Map of cache fields (text, parser output, ect) for a proposed/new edit */
-       public $mPreparedEdit = false;
-
-       /**
-        * @var int
-        */
-       protected $mId = null;
-
-       /**
-        * @var int One of the READ_* constants
-        */
-       protected $mDataLoadedFrom = self::READ_NONE;
-
-       /**
-        * @var Title
-        */
-       protected $mRedirectTarget = null;
-
-       /**
-        * @var Revision
-        */
-       protected $mLastRevision = null;
-
-       /**
-        * @var string Timestamp of the current revision or empty string if not loaded
-        */
-       protected $mTimestamp = '';
-
-       /**
-        * @var string
-        */
-       protected $mTouched = '19700101000000';
-
-       /**
-        * @var string
-        */
-       protected $mLinksUpdated = '19700101000000';
-
-       /**
-        * @var int|null
-        */
-       protected $mCounter = null;
-
-       /**
-        * Constructor and clear the article
-        * @param Title $title Reference to a Title object.
-        */
-       public function __construct( Title $title ) {
-               $this->mTitle = $title;
-       }
-
-       /**
-        * Create a WikiPage object of the appropriate class for the given title.
-        *
-        * @param Title $title
-        *
-        * @throws MWException
-        * @return WikiPage Object of the appropriate type
-        */
-       public static function factory( Title $title ) {
-               $ns = $title->getNamespace();
-
-               if ( $ns == NS_MEDIA ) {
-                       throw new MWException( "NS_MEDIA is a virtual namespace; use NS_FILE." );
-               } elseif ( $ns < 0 ) {
-                       throw new MWException( "Invalid or virtual namespace $ns given." );
-               }
-
-               switch ( $ns ) {
-                       case NS_FILE:
-                               $page = new WikiFilePage( $title );
-                               break;
-                       case NS_CATEGORY:
-                               $page = new WikiCategoryPage( $title );
-                               break;
-                       default:
-                               $page = new WikiPage( $title );
-               }
-
-               return $page;
-       }
-
-       /**
-        * Constructor from a page id
-        *
-        * @param int $id Article ID to load
-        * @param string|int $from One of the following values:
-        *        - "fromdb" or WikiPage::READ_NORMAL to select from a slave database
-        *        - "fromdbmaster" or WikiPage::READ_LATEST to select from the master database
-        *
-        * @return WikiPage|null
-        */
-       public static function newFromID( $id, $from = 'fromdb' ) {
-               // page id's are never 0 or negative, see bug 61166
-               if ( $id < 1 ) {
-                       return null;
-               }
-
-               $from = self::convertSelectType( $from );
-               $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
-               $row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
-               if ( !$row ) {
-                       return null;
-               }
-               return self::newFromRow( $row, $from );
-       }
-
-       /**
-        * Constructor from a database row
-        *
-        * @since 1.20
-        * @param object $row Database row containing at least fields returned by selectFields().
-        * @param string|int $from Source of $data:
-        *        - "fromdb" or WikiPage::READ_NORMAL: from a slave DB
-        *        - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB
-        *        - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE
-        * @return WikiPage
-        */
-       public static function newFromRow( $row, $from = 'fromdb' ) {
-               $page = self::factory( Title::newFromRow( $row ) );
-               $page->loadFromRow( $row, $from );
-               return $page;
-       }
-
-       /**
-        * Convert 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
-        *
-        * @param object|string|int $type
-        * @return mixed
-        */
-       private static function convertSelectType( $type ) {
-               switch ( $type ) {
-               case 'fromdb':
-                       return self::READ_NORMAL;
-               case 'fromdbmaster':
-                       return self::READ_LATEST;
-               case 'forupdate':
-                       return self::READ_LOCKING;
-               default:
-                       // It may already be an integer or whatever else
-                       return $type;
-               }
-       }
-
-       /**
-        * Returns overrides for action handlers.
-        * Classes listed here will be used instead of the default one when
-        * (and only when) $wgActions[$action] === true. This allows subclasses
-        * to override the default behavior.
-        *
-        * @todo Move this UI stuff somewhere else
-        *
-        * @return array
-        */
-       public function getActionOverrides() {
-               $content_handler = $this->getContentHandler();
-               return $content_handler->getActionOverrides();
-       }
-
-       /**
-        * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
-        *
-        * Shorthand for ContentHandler::getForModelID( $this->getContentModel() );
-        *
-        * @return ContentHandler
-        *
-        * @since 1.21
-        */
-       public function getContentHandler() {
-               return ContentHandler::getForModelID( $this->getContentModel() );
-       }
-
-       /**
-        * Get the title object of the article
-        * @return Title Title object of this page
-        */
-       public function getTitle() {
-               return $this->mTitle;
-       }
-
-       /**
-        * Clear the object
-        * @return void
-        */
-       public function clear() {
-               $this->mDataLoaded = false;
-               $this->mDataLoadedFrom = self::READ_NONE;
-
-               $this->clearCacheFields();
-       }
-
-       /**
-        * Clear the object cache fields
-        * @return void
-        */
-       protected function clearCacheFields() {
-               $this->mId = null;
-               $this->mCounter = null;
-               $this->mRedirectTarget = null; // Title object if set
-               $this->mLastRevision = null; // Latest revision
-               $this->mTouched = '19700101000000';
-               $this->mLinksUpdated = '19700101000000';
-               $this->mTimestamp = '';
-               $this->mIsRedirect = false;
-               $this->mLatest = false;
-               // Bug 57026: do not clear mPreparedEdit since prepareTextForEdit() already checks
-               // the requested rev ID and content against the cached one for equality. For most
-               // content types, the output should not change during the lifetime of this cache.
-               // Clearing it can cause extra parses on edit for no reason.
-       }
-
-       /**
-        * Clear the mPreparedEdit cache field, as may be needed by mutable content types
-        * @return void
-        * @since 1.23
-        */
-       public function clearPreparedEdit() {
-               $this->mPreparedEdit = false;
-       }
-
-       /**
-        * Return the list of revision fields that should be selected to create
-        * a new page.
-        *
-        * @return array
-        */
-       public static function selectFields() {
-               global $wgContentHandlerUseDB;
-
-               $fields = array(
-                       'page_id',
-                       'page_namespace',
-                       'page_title',
-                       'page_restrictions',
-                       'page_counter',
-                       'page_is_redirect',
-                       'page_is_new',
-                       'page_random',
-                       'page_touched',
-                       'page_links_updated',
-                       'page_latest',
-                       'page_len',
-               );
-
-               if ( $wgContentHandlerUseDB ) {
-                       $fields[] = 'page_content_model';
-               }
-
-               return $fields;
-       }
-
-       /**
-        * Fetch a page record with the given conditions
-        * @param DatabaseBase $dbr
-        * @param array $conditions
-        * @param array $options
-        * @return object|bool Database result resource, or false on failure
-        */
-       protected function pageData( $dbr, $conditions, $options = array() ) {
-               $fields = self::selectFields();
-
-               wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
-
-               $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options );
-
-               wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
-
-               return $row;
-       }
-
-       /**
-        * Fetch a page record matching the Title object's namespace and title
-        * using a sanitized title string
-        *
-        * @param DatabaseBase $dbr
-        * @param Title $title
-        * @param array $options
-        * @return object|bool Database result resource, or false on failure
-        */
-       public function pageDataFromTitle( $dbr, $title, $options = array() ) {
-               return $this->pageData( $dbr, array(
-                       'page_namespace' => $title->getNamespace(),
-                       'page_title' => $title->getDBkey() ), $options );
-       }
-
-       /**
-        * Fetch a page record matching the requested ID
-        *
-        * @param DatabaseBase $dbr
-        * @param int $id
-        * @param array $options
-        * @return object|bool Database result resource, or false on failure
-        */
-       public function pageDataFromId( $dbr, $id, $options = array() ) {
-               return $this->pageData( $dbr, array( 'page_id' => $id ), $options );
-       }
-
-       /**
-        * Set the general counter, title etc data loaded from
-        * some source.
-        *
-        * @param object|string|int $from One of the following:
-        *   - A DB query result object.
-        *   - "fromdb" or WikiPage::READ_NORMAL to get from a slave DB.
-        *   - "fromdbmaster" or WikiPage::READ_LATEST to get from the master DB.
-        *   - "forupdate"  or WikiPage::READ_LOCKING to get from the master DB
-        *     using SELECT FOR UPDATE.
-        *
-        * @return void
-        */
-       public function loadPageData( $from = 'fromdb' ) {
-               $from = self::convertSelectType( $from );
-               if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
-                       // We already have the data from the correct location, no need to load it twice.
-                       return;
-               }
-
-               if ( $from === self::READ_LOCKING ) {
-                       $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle, array( 'FOR UPDATE' ) );
-               } elseif ( $from === self::READ_LATEST ) {
-                       $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
-               } elseif ( $from === self::READ_NORMAL ) {
-                       $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
-                       // Use a "last rev inserted" timestamp key to diminish the issue of slave lag.
-                       // Note that DB also stores the master position in the session and checks it.
-                       $touched = $this->getCachedLastEditTime();
-                       if ( $touched ) { // key set
-                               if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
-                                       $from = self::READ_LATEST;
-                                       $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
-                               }
-                       }
-               } else {
-                       // No idea from where the caller got this data, assume slave database.
-                       $data = $from;
-                       $from = self::READ_NORMAL;
-               }
-
-               $this->loadFromRow( $data, $from );
-       }
-
-       /**
-        * Load the object from a database row
-        *
-        * @since 1.20
-        * @param object $data Database row containing at least fields returned by selectFields()
-        * @param string|int $from One of the following:
-        *        - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
-        *        - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
-        *        - "forupdate"  or WikiPage::READ_LOCKING if the data comes from from
-        *          the master DB using SELECT FOR UPDATE
-        */
-       public function loadFromRow( $data, $from ) {
-               $lc = LinkCache::singleton();
-               $lc->clearLink( $this->mTitle );
-
-               if ( $data ) {
-                       $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
-
-                       $this->mTitle->loadFromRow( $data );
-
-                       // Old-fashioned restrictions
-                       $this->mTitle->loadRestrictions( $data->page_restrictions );
-
-                       $this->mId = intval( $data->page_id );
-                       $this->mCounter = intval( $data->page_counter );
-                       $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
-                       $this->mLinksUpdated = wfTimestampOrNull( TS_MW, $data->page_links_updated );
-                       $this->mIsRedirect = intval( $data->page_is_redirect );
-                       $this->mLatest = intval( $data->page_latest );
-                       // Bug 37225: $latest may no longer match the cached latest Revision object.
-                       // Double-check the ID of any cached latest Revision object for consistency.
-                       if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
-                               $this->mLastRevision = null;
-                               $this->mTimestamp = '';
-                       }
-               } else {
-                       $lc->addBadLinkObj( $this->mTitle );
-
-                       $this->mTitle->loadFromRow( false );
-
-                       $this->clearCacheFields();
-
-                       $this->mId = 0;
-               }
-
-               $this->mDataLoaded = true;
-               $this->mDataLoadedFrom = self::convertSelectType( $from );
-       }
-
-       /**
-        * @return int Page ID
-        */
-       public function getId() {
-               if ( !$this->mDataLoaded ) {
-                       $this->loadPageData();
-               }
-               return $this->mId;
-       }
-
-       /**
-        * @return bool Whether or not the page exists in the database
-        */
-       public function exists() {
-               if ( !$this->mDataLoaded ) {
-                       $this->loadPageData();
-               }
-               return $this->mId > 0;
-       }
-
-       /**
-        * Check if this page is something we're going to be showing
-        * some sort of sensible content for. If we return false, page
-        * views (plain action=view) will return an HTTP 404 response,
-        * so spiders and robots can know they're following a bad link.
-        *
-        * @return bool
-        */
-       public function hasViewableContent() {
-               return $this->exists() || $this->mTitle->isAlwaysKnown();
-       }
-
-       /**
-        * @return int The view count for the page
-        */
-       public function getCount() {
-               if ( !$this->mDataLoaded ) {
-                       $this->loadPageData();
-               }
-
-               return $this->mCounter;
-       }
-
-       /**
-        * Tests if the article content represents a redirect
-        *
-        * @return bool
-        */
-       public function isRedirect() {
-               $content = $this->getContent();
-               if ( !$content ) {
-                       return false;
-               }
-
-               return $content->isRedirect();
-       }
-
-       /**
-        * Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
-        *
-        * Will use the revisions actual content model if the page exists,
-        * and the page's default if the page doesn't exist yet.
-        *
-        * @return string
-        *
-        * @since 1.21
-        */
-       public function getContentModel() {
-               if ( $this->exists() ) {
-                       // look at the revision's actual content model
-                       $rev = $this->getRevision();
-
-                       if ( $rev !== null ) {
-                               return $rev->getContentModel();
-                       } else {
-                               $title = $this->mTitle->getPrefixedDBkey();
-                               wfWarn( "Page $title exists but has no (visible) revisions!" );
-                       }
-               }
-
-               // use the default model for this page
-               return $this->mTitle->getContentModel();
-       }
-
-       /**
-        * Loads page_touched and returns a value indicating if it should be used
-        * @return bool true if not a redirect
-        */
-       public function checkTouched() {
-               if ( !$this->mDataLoaded ) {
-                       $this->loadPageData();
-               }
-               return !$this->mIsRedirect;
-       }
-
-       /**
-        * Get the page_touched field
-        * @return string Containing GMT timestamp
-        */
-       public function getTouched() {
-               if ( !$this->mDataLoaded ) {
-                       $this->loadPageData();
-               }
-               return $this->mTouched;
-       }
-
-       /**
-        * Get the page_links_updated field
-        * @return string|null Containing GMT timestamp
-        */
-       public function getLinksTimestamp() {
-               if ( !$this->mDataLoaded ) {
-                       $this->loadPageData();
-               }
-               return $this->mLinksUpdated;
-       }
-
-       /**
-        * Get the page_latest field
-        * @return int rev_id of current revision
-        */
-       public function getLatest() {
-               if ( !$this->mDataLoaded ) {
-                       $this->loadPageData();
-               }
-               return (int)$this->mLatest;
-       }
-
-       /**
-        * Get the Revision object of the oldest revision
-        * @return Revision|null
-        */
-       public function getOldestRevision() {
-               wfProfileIn( __METHOD__ );
-
-               // Try using the slave database first, then try the master
-               $continue = 2;
-               $db = wfGetDB( DB_SLAVE );
-               $revSelectFields = Revision::selectFields();
-
-               $row = null;
-               while ( $continue ) {
-                       $row = $db->selectRow(
-                               array( 'page', 'revision' ),
-                               $revSelectFields,
-                               array(
-                                       'page_namespace' => $this->mTitle->getNamespace(),
-                                       'page_title' => $this->mTitle->getDBkey(),
-                                       'rev_page = page_id'
-                               ),
-                               __METHOD__,
-                               array(
-                                       'ORDER BY' => 'rev_timestamp ASC'
-                               )
-                       );
-
-                       if ( $row ) {
-                               $continue = 0;
-                       } else {
-                               $db = wfGetDB( DB_MASTER );
-                               $continue--;
-                       }
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $row ? Revision::newFromRow( $row ) : null;
-       }
-
-       /**
-        * Loads everything except the text
-        * This isn't necessary for all uses, so it's only done if needed.
-        */
-       protected function loadLastEdit() {
-               if ( $this->mLastRevision !== null ) {
-                       return; // already loaded
-               }
-
-               $latest = $this->getLatest();
-               if ( !$latest ) {
-                       return; // page doesn't exist or is missing page_latest info
-               }
-
-               // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the
-               // latest changes committed. This is true even within REPEATABLE-READ transactions, where
-               // S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to
-               // also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row
-               // UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT.
-               // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
-               $flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0;
-               $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
-               if ( $revision ) { // sanity
-                       $this->setLastEdit( $revision );
-               }
-       }
-
-       /**
-        * Set the latest revision
-        * @param Revision $revision
-        */
-       protected function setLastEdit( Revision $revision ) {
-               $this->mLastRevision = $revision;
-               $this->mTimestamp = $revision->getTimestamp();
-       }
-
-       /**
-        * Get the latest revision
-        * @return Revision|null
-        */
-       public function getRevision() {
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision;
-               }
-               return null;
-       }
-
-       /**
-        * Get the content of the current revision. No side-effects...
-        *
-        * @param int $audience int One of:
-        *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to $wgUser
-        *   Revision::RAW              get the text regardless of permissions
-        * @param User $user User object to check for, only if FOR_THIS_USER is passed
-        *   to the $audience parameter
-        * @return Content|null The content of the current revision
-        *
-        * @since 1.21
-        */
-       public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->getContent( $audience, $user );
-               }
-               return null;
-       }
-
-       /**
-        * Get the text of the current revision. No side-effects...
-        *
-        * @param int $audience One of:
-        *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to the given user
-        *   Revision::RAW              get the text regardless of permissions
-        * @param User $user User object to check for, only if FOR_THIS_USER is passed
-        *   to the $audience parameter
-        * @return string|bool The text of the current revision
-        * @deprecated since 1.21, getContent() should be used instead.
-        */
-       public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->getText( $audience, $user );
-               }
-               return false;
-       }
-
-       /**
-        * Get the text of the current revision. No side-effects...
-        *
-        * @return string|bool The text of the current revision. False on failure
-        * @deprecated since 1.21, getContent() should be used instead.
-        */
-       public function getRawText() {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-
-               return $this->getText( Revision::RAW );
-       }
-
-       /**
-        * @return string MW timestamp of last article revision
-        */
-       public function getTimestamp() {
-               // Check if the field has been filled by WikiPage::setTimestamp()
-               if ( !$this->mTimestamp ) {
-                       $this->loadLastEdit();
-               }
-
-               return wfTimestamp( TS_MW, $this->mTimestamp );
-       }
-
-       /**
-        * Set the page timestamp (use only to avoid DB queries)
-        * @param string $ts MW timestamp of last article revision
-        * @return void
-        */
-       public function setTimestamp( $ts ) {
-               $this->mTimestamp = wfTimestamp( TS_MW, $ts );
-       }
-
-       /**
-        * @param int $audience One of:
-        *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to the given user
-        *   Revision::RAW              get the text regardless of permissions
-        * @param User $user User object to check for, only if FOR_THIS_USER is passed
-        *   to the $audience parameter
-        * @return int user ID for the user that made the last article revision
-        */
-       public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->getUser( $audience, $user );
-               } else {
-                       return -1;
-               }
-       }
-
-       /**
-        * Get the User object of the user who created the page
-        * @param int $audience One of:
-        *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to the given user
-        *   Revision::RAW              get the text regardless of permissions
-        * @param User $user User object to check for, only if FOR_THIS_USER is passed
-        *   to the $audience parameter
-        * @return User|null
-        */
-       public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               $revision = $this->getOldestRevision();
-               if ( $revision ) {
-                       $userName = $revision->getUserText( $audience, $user );
-                       return User::newFromName( $userName, false );
-               } else {
-                       return null;
-               }
-       }
-
-       /**
-        * @param int $audience One of:
-        *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to the given user
-        *   Revision::RAW              get the text regardless of permissions
-        * @param User $user User object to check for, only if FOR_THIS_USER is passed
-        *   to the $audience parameter
-        * @return string username of the user that made the last article revision
-        */
-       public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->getUserText( $audience, $user );
-               } else {
-                       return '';
-               }
-       }
-
-       /**
-        * @param int $audience One of:
-        *   Revision::FOR_PUBLIC       to be displayed to all users
-        *   Revision::FOR_THIS_USER    to be displayed to the given user
-        *   Revision::RAW              get the text regardless of permissions
-        * @param User $user User object to check for, only if FOR_THIS_USER is passed
-        *   to the $audience parameter
-        * @return string Comment stored for the last article revision
-        */
-       public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->getComment( $audience, $user );
-               } else {
-                       return '';
-               }
-       }
-
-       /**
-        * Returns true if last revision was marked as "minor edit"
-        *
-        * @return bool Minor edit indicator for the last article revision.
-        */
-       public function getMinorEdit() {
-               $this->loadLastEdit();
-               if ( $this->mLastRevision ) {
-                       return $this->mLastRevision->isMinor();
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Get the cached timestamp for the last time the page changed.
-        * This is only used to help handle slave lag by comparing to page_touched.
-        * @return string MW timestamp
-        */
-       protected function getCachedLastEditTime() {
-               global $wgMemc;
-               $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
-               return $wgMemc->get( $key );
-       }
-
-       /**
-        * Set the cached timestamp for the last time the page changed.
-        * This is only used to help handle slave lag by comparing to page_touched.
-        * @param string $timestamp
-        * @return void
-        */
-       public function setCachedLastEditTime( $timestamp ) {
-               global $wgMemc;
-               $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
-               $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60 * 15 );
-       }
-
-       /**
-        * Determine whether a page would be suitable for being counted as an
-        * article in the site_stats table based on the title & its content
-        *
-        * @param object|bool $editInfo (false): object returned by prepareTextForEdit(),
-        *   if false, the current database state will be used
-        * @return bool
-        */
-       public function isCountable( $editInfo = false ) {
-               global $wgArticleCountMethod;
-
-               if ( !$this->mTitle->isContentPage() ) {
-                       return false;
-               }
-
-               if ( $editInfo ) {
-                       $content = $editInfo->pstContent;
-               } else {
-                       $content = $this->getContent();
-               }
-
-               if ( !$content || $content->isRedirect() ) {
-                       return false;
-               }
-
-               $hasLinks = null;
-
-               if ( $wgArticleCountMethod === 'link' ) {
-                       // nasty special case to avoid re-parsing to detect links
-
-                       if ( $editInfo ) {
-                               // ParserOutput::getLinks() is a 2D array of page links, so
-                               // to be really correct we would need to recurse in the array
-                               // but the main array should only have items in it if there are
-                               // links.
-                               $hasLinks = (bool)count( $editInfo->output->getLinks() );
-                       } else {
-                               $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
-                                       array( 'pl_from' => $this->getId() ), __METHOD__ );
-                       }
-               }
-
-               return $content->isCountable( $hasLinks );
-       }
-
-       /**
-        * If this page is a redirect, get its target
-        *
-        * The target will be fetched from the redirect table if possible.
-        * If this page doesn't have an entry there, call insertRedirect()
-        * @return Title|null Title object, or null if this page is not a redirect
-        */
-       public function getRedirectTarget() {
-               if ( !$this->mTitle->isRedirect() ) {
-                       return null;
-               }
-
-               if ( $this->mRedirectTarget !== null ) {
-                       return $this->mRedirectTarget;
-               }
-
-               // Query the redirect table
-               $dbr = wfGetDB( DB_SLAVE );
-               $row = $dbr->selectRow( 'redirect',
-                       array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
-                       array( 'rd_from' => $this->getId() ),
-                       __METHOD__
-               );
-
-               // rd_fragment and rd_interwiki were added later, populate them if empty
-               if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
-                       $this->mRedirectTarget = Title::makeTitle(
-                               $row->rd_namespace, $row->rd_title,
-                               $row->rd_fragment, $row->rd_interwiki );
-                       return $this->mRedirectTarget;
-               }
-
-               // This page doesn't have an entry in the redirect table
-               $this->mRedirectTarget = $this->insertRedirect();
-               return $this->mRedirectTarget;
-       }
-
-       /**
-        * Insert an entry for this page into the redirect table.
-        *
-        * Don't call this function directly unless you know what you're doing.
-        * @return Title|null Title object or null if not a redirect
-        */
-       public function insertRedirect() {
-               // recurse through to only get the final target
-               $content = $this->getContent();
-               $retval = $content ? $content->getUltimateRedirectTarget() : null;
-               if ( !$retval ) {
-                       return null;
-               }
-               $this->insertRedirectEntry( $retval );
-               return $retval;
-       }
-
-       /**
-        * Insert or update the redirect table entry for this page to indicate
-        * it redirects to $rt .
-        * @param Title $rt Redirect target
-        */
-       public function insertRedirectEntry( $rt ) {
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->replace( 'redirect', array( 'rd_from' ),
-                       array(
-                               'rd_from' => $this->getId(),
-                               'rd_namespace' => $rt->getNamespace(),
-                               'rd_title' => $rt->getDBkey(),
-                               'rd_fragment' => $rt->getFragment(),
-                               'rd_interwiki' => $rt->getInterwiki(),
-                       ),
-                       __METHOD__
-               );
-       }
-
-       /**
-        * Get the Title object or URL this page redirects to
-        *
-        * @return bool|Title|string false, Title of in-wiki target, or string with URL
-        */
-       public function followRedirect() {
-               return $this->getRedirectURL( $this->getRedirectTarget() );
-       }
-
-       /**
-        * Get the Title object or URL to use for a redirect. We use Title
-        * objects for same-wiki, non-special redirects and URLs for everything
-        * else.
-        * @param Title $rt Redirect target
-        * @return bool|Title|string false, Title object of local target, or string with URL
-        */
-       public function getRedirectURL( $rt ) {
-               if ( !$rt ) {
-                       return false;
-               }
-
-               if ( $rt->isExternal() ) {
-                       if ( $rt->isLocal() ) {
-                               // Offsite wikis need an HTTP redirect.
-                               //
-                               // This can be hard to reverse and may produce loops,
-                               // so they may be disabled in the site configuration.
-                               $source = $this->mTitle->getFullURL( 'redirect=no' );
-                               return $rt->getFullURL( array( 'rdfrom' => $source ) );
-                       } else {
-                               // External pages pages without "local" bit set are not valid
-                               // redirect targets
-                               return false;
-                       }
-               }
-
-               if ( $rt->isSpecialPage() ) {
-                       // Gotta handle redirects to special pages differently:
-                       // Fill the HTTP response "Location" header and ignore
-                       // the rest of the page we're on.
-                       //
-                       // Some pages are not valid targets
-                       if ( $rt->isValidRedirectTarget() ) {
-                               return $rt->getFullURL();
-                       } else {
-                               return false;
-                       }
-               }
-
-               return $rt;
-       }
-
-       /**
-        * Get a list of users who have edited this article, not including the user who made
-        * the most recent revision, which you can get from $article->getUser() if you want it
-        * @return UserArrayFromResult
-        */
-       public function getContributors() {
-               // @todo FIXME: This is expensive; cache this info somewhere.
-
-               $dbr = wfGetDB( DB_SLAVE );
-
-               if ( $dbr->implicitGroupby() ) {
-                       $realNameField = 'user_real_name';
-               } else {
-                       $realNameField = 'MIN(user_real_name) AS user_real_name';
-               }
-
-               $tables = array( 'revision', 'user' );
-
-               $fields = array(
-                       'user_id' => 'rev_user',
-                       'user_name' => 'rev_user_text',
-                       $realNameField,
-                       'timestamp' => 'MAX(rev_timestamp)',
-               );
-
-               $conds = array( 'rev_page' => $this->getId() );
-
-               // The user who made the top revision gets credited as "this page was last edited by
-               // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
-               $user = $this->getUser();
-               if ( $user ) {
-                       $conds[] = "rev_user != $user";
-               } else {
-                       $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
-               }
-
-               $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
-
-               $jconds = array(
-                       'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
-               );
-
-               $options = array(
-                       'GROUP BY' => array( 'rev_user', 'rev_user_text' ),
-                       'ORDER BY' => 'timestamp DESC',
-               );
-
-               $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
-               return new UserArrayFromResult( $res );
-       }
-
-       /**
-        * Get the last N authors
-        * @param int $num Number of revisions to get
-        * @param int|string $revLatest The latest rev_id, selected from the master (optional)
-        * @return array Array of authors, duplicates not removed
-        */
-       public function getLastNAuthors( $num, $revLatest = 0 ) {
-               wfProfileIn( __METHOD__ );
-               // First try the slave
-               // If that doesn't have the latest revision, try the master
-               $continue = 2;
-               $db = wfGetDB( DB_SLAVE );
-
-               do {
-                       $res = $db->select( array( 'page', 'revision' ),
-                               array( 'rev_id', 'rev_user_text' ),
-                               array(
-                                       'page_namespace' => $this->mTitle->getNamespace(),
-                                       'page_title' => $this->mTitle->getDBkey(),
-                                       'rev_page = page_id'
-                               ), __METHOD__,
-                               array(
-                                       'ORDER BY' => 'rev_timestamp DESC',
-                                       'LIMIT' => $num
-                               )
-                       );
-
-                       if ( !$res ) {
-                               wfProfileOut( __METHOD__ );
-                               return array();
-                       }
-
-                       $row = $db->fetchObject( $res );
-
-                       if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
-                               $db = wfGetDB( DB_MASTER );
-                               $continue--;
-                       } else {
-                               $continue = 0;
-                       }
-               } while ( $continue );
-
-               $authors = array( $row->rev_user_text );
-
-               foreach ( $res as $row ) {
-                       $authors[] = $row->rev_user_text;
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $authors;
-       }
-
-       /**
-        * Should the parser cache be used?
-        *
-        * @param ParserOptions $parserOptions ParserOptions to check
-        * @param int $oldid
-        * @return bool
-        */
-       public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
-               global $wgEnableParserCache;
-
-               return $wgEnableParserCache
-                       && $parserOptions->getStubThreshold() == 0
-                       && $this->exists()
-                       && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
-                       && $this->getContentHandler()->isParserCacheSupported();
-       }
-
-       /**
-        * Get a ParserOutput for the given ParserOptions and revision ID.
-        * The parser cache will be used if possible.
-        *
-        * @since 1.19
-        * @param ParserOptions $parserOptions ParserOptions to use for the parse operation
-        * @param null|int $oldid Revision ID to get the text from, passing null or 0 will
-        *   get the current revision (default value)
-        *
-        * @return ParserOutput|bool ParserOutput or false if the revision was not found
-        */
-       public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
-               wfProfileIn( __METHOD__ );
-
-               $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
-               wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
-               if ( $parserOptions->getStubThreshold() ) {
-                       wfIncrStats( 'pcache_miss_stub' );
-               }
-
-               if ( $useParserCache ) {
-                       $parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
-                       if ( $parserOutput !== false ) {
-                               wfProfileOut( __METHOD__ );
-                               return $parserOutput;
-                       }
-               }
-
-               if ( $oldid === null || $oldid === 0 ) {
-                       $oldid = $this->getLatest();
-               }
-
-               $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
-               $pool->execute();
-
-               wfProfileOut( __METHOD__ );
-
-               return $pool->getParserOutput();
-       }
-
-       /**
-        * Do standard deferred updates after page view (existing or missing page)
-        * @param User $user The relevant user
-        * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
-        */
-       public function doViewUpdates( User $user, $oldid = 0 ) {
-               global $wgDisableCounters;
-               if ( wfReadOnly() ) {
-                       return;
-               }
-
-               // Don't update page view counters on views from bot users (bug 14044)
-               if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->exists() ) {
-                       DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
-                       DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
-               }
-
-               // Update newtalk / watchlist notification status
-               $user->clearNotification( $this->mTitle, $oldid );
-       }
-
-       /**
-        * Perform the actions of a page purging
-        * @return bool
-        */
-       public function doPurge() {
-               global $wgUseSquid;
-
-               if ( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
-                       return false;
-               }
-
-               // Invalidate the cache
-               $this->mTitle->invalidateCache();
-
-               if ( $wgUseSquid ) {
-                       // Commit the transaction before the purge is sent
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->commit( __METHOD__ );
-
-                       // Send purge
-                       $update = SquidUpdate::newSimplePurge( $this->mTitle );
-                       $update->doUpdate();
-               }
-
-               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-                       // @todo move this logic to MessageCache
-
-                       if ( $this->exists() ) {
-                               // NOTE: use transclusion text for messages.
-                               //       This is consistent with  MessageCache::getMsgFromNamespace()
-
-                               $content = $this->getContent();
-                               $text = $content === null ? null : $content->getWikitextForTransclusion();
-
-                               if ( $text === null ) {
-                                       $text = false;
-                               }
-                       } else {
-                               $text = false;
-                       }
-
-                       MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
-               }
-               return true;
-       }
-
-       /**
-        * Insert a new empty page record for this article.
-        * This *must* be followed up by creating a revision
-        * and running $this->updateRevisionOn( ... );
-        * or else the record will be left in a funky state.
-        * Best if all done inside a transaction.
-        *
-        * @param DatabaseBase $dbw
-        * @return int The newly created page_id key, or false if the title already existed
-        */
-       public function insertOn( $dbw ) {
-               wfProfileIn( __METHOD__ );
-
-               $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
-               $dbw->insert( 'page', array(
-                       'page_id'           => $page_id,
-                       'page_namespace'    => $this->mTitle->getNamespace(),
-                       'page_title'        => $this->mTitle->getDBkey(),
-                       'page_counter'      => 0,
-                       'page_restrictions' => '',
-                       'page_is_redirect'  => 0, // Will set this shortly...
-                       'page_is_new'       => 1,
-                       'page_random'       => wfRandom(),
-                       'page_touched'      => $dbw->timestamp(),
-                       'page_latest'       => 0, // Fill this in shortly...
-                       'page_len'          => 0, // Fill this in shortly...
-               ), __METHOD__, 'IGNORE' );
-
-               $affected = $dbw->affectedRows();
-
-               if ( $affected ) {
-                       $newid = $dbw->insertId();
-                       $this->mId = $newid;
-                       $this->mTitle->resetArticleID( $newid );
-               }
-               wfProfileOut( __METHOD__ );
-
-               return $affected ? $newid : false;
-       }
-
-       /**
-        * Update the page record to point to a newly saved revision.
-        *
-        * @param DatabaseBase $dbw
-        * @param Revision $revision For ID number, and text used to set
-        *   length and redirect status fields
-        * @param int $lastRevision If given, will not overwrite the page field
-        *   when different from the currently set value.
-        *   Giving 0 indicates the new page flag should be set on.
-        * @param bool $lastRevIsRedirect If given, will optimize adding and
-        *   removing rows in redirect table.
-        * @return bool true on success, false on failure
-        */
-       public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
-               $lastRevIsRedirect = null
-       ) {
-               global $wgContentHandlerUseDB;
-
-               wfProfileIn( __METHOD__ );
-
-               $content = $revision->getContent();
-               $len = $content ? $content->getSize() : 0;
-               $rt = $content ? $content->getUltimateRedirectTarget() : null;
-
-               $conditions = array( 'page_id' => $this->getId() );
-
-               if ( !is_null( $lastRevision ) ) {
-                       // An extra check against threads stepping on each other
-                       $conditions['page_latest'] = $lastRevision;
-               }
-
-               $now = wfTimestampNow();
-               $row = array( /* SET */
-                       'page_latest'      => $revision->getId(),
-                       'page_touched'     => $dbw->timestamp( $now ),
-                       'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
-                       'page_is_redirect' => $rt !== null ? 1 : 0,
-                       'page_len'         => $len,
-               );
-
-               if ( $wgContentHandlerUseDB ) {
-                       $row['page_content_model'] = $revision->getContentModel();
-               }
-
-               $dbw->update( 'page',
-                       $row,
-                       $conditions,
-                       __METHOD__ );
-
-               $result = $dbw->affectedRows() > 0;
-               if ( $result ) {
-                       $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
-                       $this->setLastEdit( $revision );
-                       $this->setCachedLastEditTime( $now );
-                       $this->mLatest = $revision->getId();
-                       $this->mIsRedirect = (bool)$rt;
-                       // Update the LinkCache.
-                       LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
-                                                                                                       $this->mLatest, $revision->getContentModel() );
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $result;
-       }
-
-       /**
-        * Add row to the redirect table if this is a redirect, remove otherwise.
-        *
-        * @param DatabaseBase $dbw
-        * @param Title $redirectTitle Title object pointing to the redirect target,
-        *   or NULL if this is not a redirect
-        * @param null|bool $lastRevIsRedirect If given, will optimize adding and
-        *   removing rows in redirect table.
-        * @return bool true on success, false on failure
-        * @private
-        */
-       public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
-               // Always update redirects (target link might have changed)
-               // Update/Insert if we don't know if the last revision was a redirect or not
-               // Delete if changing from redirect to non-redirect
-               $isRedirect = !is_null( $redirectTitle );
-
-               if ( !$isRedirect && $lastRevIsRedirect === false ) {
-                       return true;
-               }
-
-               wfProfileIn( __METHOD__ );
-               if ( $isRedirect ) {
-                       $this->insertRedirectEntry( $redirectTitle );
-               } else {
-                       // This is not a redirect, remove row from redirect table
-                       $where = array( 'rd_from' => $this->getId() );
-                       $dbw->delete( 'redirect', $where, __METHOD__ );
-               }
-
-               if ( $this->getTitle()->getNamespace() == NS_FILE ) {
-                       RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
-               }
-               wfProfileOut( __METHOD__ );
-
-               return ( $dbw->affectedRows() != 0 );
-       }
-
-       /**
-        * If the given revision is newer than the currently set page_latest,
-        * update the page record. Otherwise, do nothing.
-        *
-        * @deprecated since 1.24, use updateRevisionOn instead
-        *
-        * @param DatabaseBase $dbw
-        * @param Revision $revision
-        * @return bool
-        */
-       public function updateIfNewerOn( $dbw, $revision ) {
-               wfProfileIn( __METHOD__ );
-
-               $row = $dbw->selectRow(
-                       array( 'revision', 'page' ),
-                       array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
-                       array(
-                               'page_id' => $this->getId(),
-                               'page_latest=rev_id' ),
-                       __METHOD__ );
-
-               if ( $row ) {
-                       if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
-                               wfProfileOut( __METHOD__ );
-                               return false;
-                       }
-                       $prev = $row->rev_id;
-                       $lastRevIsRedirect = (bool)$row->page_is_redirect;
-               } else {
-                       // No or missing previous revision; mark the page as new
-                       $prev = 0;
-                       $lastRevIsRedirect = null;
-               }
-
-               $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
-
-               wfProfileOut( __METHOD__ );
-               return $ret;
-       }
-
-       /**
-        * Get the content that needs to be saved in order to undo all revisions
-        * between $undo and $undoafter. Revisions must belong to the same page,
-        * must exist and must not be deleted
-        * @param Revision $undo
-        * @param Revision $undoafter Must be an earlier revision than $undo
-        * @return mixed string on success, false on failure
-        * @since 1.21
-        * Before we had the Content object, this was done in getUndoText
-        */
-       public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
-               $handler = $undo->getContentHandler();
-               return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
-       }
-
-       /**
-        * Get the text that needs to be saved in order to undo all revisions
-        * between $undo and $undoafter. Revisions must belong to the same page,
-        * must exist and must not be deleted
-        * @param Revision $undo
-        * @param Revision $undoafter Must be an earlier revision than $undo
-        * @return string|bool string on success, false on failure
-        * @deprecated since 1.21: use ContentHandler::getUndoContent() instead.
-        */
-       public function getUndoText( Revision $undo, Revision $undoafter = null ) {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-
-               $this->loadLastEdit();
-
-               if ( $this->mLastRevision ) {
-                       if ( is_null( $undoafter ) ) {
-                               $undoafter = $undo->getPrevious();
-                       }
-
-                       $handler = $this->getContentHandler();
-                       $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
-
-                       if ( !$undone ) {
-                               return false;
-                       } else {
-                               return ContentHandler::getContentText( $undone );
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * @param string|number|null|bool $sectionId Section identifier as a number or string
-        * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
-        * or 'new' for a new section.
-        * @param string $text New text of the section.
-        * @param string $sectionTitle New section's subject, only if $section is "new".
-        * @param string $edittime Revision timestamp or null to use the current revision.
-        *
-        * @throws MWException
-        * @return string New complete article text, or null if error.
-        *
-        * @deprecated since 1.21, use replaceSectionAtRev() instead
-        */
-       public function replaceSection( $sectionId, $text, $sectionTitle = '',
-               $edittime = null
-       ) {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-
-               //NOTE: keep condition in sync with condition in replaceSectionContent!
-               if ( strval( $sectionId ) === '' ) {
-                       // Whole-page edit; let the whole text through
-                       return $text;
-               }
-
-               if ( !$this->supportsSections() ) {
-                       throw new MWException( "sections not supported for content model " .
-                               $this->getContentHandler()->getModelID() );
-               }
-
-               // could even make section title, but that's not required.
-               $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
-
-               $newContent = $this->replaceSectionContent( $sectionId, $sectionContent, $sectionTitle,
-                       $edittime );
-
-               return ContentHandler::getContentText( $newContent );
-       }
-
-       /**
-        * Returns true if this page's content model supports sections.
-        *
-        * @return bool
-        *
-        * @todo The skin should check this and not offer section functionality if
-        *   sections are not supported.
-        * @todo The EditPage should check this and not offer section functionality
-        *   if sections are not supported.
-        */
-       public function supportsSections() {
-               return $this->getContentHandler()->supportsSections();
-       }
-
-       /**
-        * @param string|number|null|bool $sectionId Section identifier as a number or string
-        * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
-        * or 'new' for a new section.
-        * @param Content $sectionContent New content of the section.
-        * @param string $sectionTitle New section's subject, only if $section is "new".
-        * @param string $edittime Revision timestamp or null to use the current revision.
-        *
-        * @throws MWException
-        * @return Content New complete article content, or null if error.
-        *
-        * @since 1.21
-        * @deprecated since 1.24, use replaceSectionAtRev instead
-        */
-       public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
-               $edittime = null ) {
-               wfProfileIn( __METHOD__ );
-
-               $baseRevId = null;
-               if ( $edittime && $sectionId !== 'new' ) {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
-                       if ( $rev ) {
-                               $baseRevId = $rev->getId();
-                       }
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
-       }
-
-       /**
-        * @param string|number|null|bool $sectionId Section identifier as a number or string
-        * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
-        * or 'new' for a new section.
-        * @param Content $sectionContent New content of the section.
-        * @param string $sectionTitle New section's subject, only if $section is "new".
-        * @param string $baseRevId integer|null
-        *
-        * @throws MWException
-        * @return Content New complete article content, or null if error.
-        *
-        * @since 1.24
-        */
-       public function replaceSectionAtRev( $sectionId, Content $sectionContent,
-               $sectionTitle = '', $baseRevId = null
-       ) {
-               wfProfileIn( __METHOD__ );
-
-               if ( strval( $sectionId ) === '' ) {
-                       // Whole-page edit; let the whole text through
-                       $newContent = $sectionContent;
-               } else {
-                       if ( !$this->supportsSections() ) {
-                               wfProfileOut( __METHOD__ );
-                               throw new MWException( "sections not supported for content model " .
-                                       $this->getContentHandler()->getModelID() );
-                       }
-
-                       // Bug 30711: always use current version when adding a new section
-                       if ( is_null( $baseRevId ) || $sectionId === 'new' ) {
-                               $oldContent = $this->getContent();
-                       } else {
-                               // TODO: try DB_SLAVE first
-                               $dbw = wfGetDB( DB_MASTER );
-                               $rev = Revision::loadFromId( $dbw, $baseRevId );
-
-                               if ( !$rev ) {
-                                       wfDebug( __METHOD__ . " asked for bogus section (page: " .
-                                               $this->getId() . "; section: $sectionId)\n" );
-                                       wfProfileOut( __METHOD__ );
-                                       return null;
-                               }
-
-                               $oldContent = $rev->getContent();
-                       }
-
-                       if ( ! $oldContent ) {
-                               wfDebug( __METHOD__ . ": no page text\n" );
-                               wfProfileOut( __METHOD__ );
-                               return null;
-                       }
-
-                       $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $newContent;
-       }
-
-       /**
-        * Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
-        * @param int $flags
-        * @return int Updated $flags
-        */
-       public function checkFlags( $flags ) {
-               if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
-                       if ( $this->exists() ) {
-                               $flags |= EDIT_UPDATE;
-                       } else {
-                               $flags |= EDIT_NEW;
-                       }
-               }
-
-               return $flags;
-       }
-
-       /**
-        * Change an existing article or create a new article. Updates RC and all necessary caches,
-        * optionally via the deferred update array.
-        *
-        * @param string $text New text
-        * @param string $summary Edit summary
-        * @param int $flags Bitfield:
-        *      EDIT_NEW
-        *          Article is known or assumed to be non-existent, create a new one
-        *      EDIT_UPDATE
-        *          Article is known or assumed to be pre-existing, update it
-        *      EDIT_MINOR
-        *          Mark this edit minor, if the user is allowed to do so
-        *      EDIT_SUPPRESS_RC
-        *          Do not log the change in recentchanges
-        *      EDIT_FORCE_BOT
-        *          Mark the edit a "bot" edit regardless of user rights
-        *      EDIT_DEFER_UPDATES
-        *          Defer some of the updates until the end of index.php
-        *      EDIT_AUTOSUMMARY
-        *          Fill in blank summaries with generated text where possible
-        *
-        * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the
-        * article will be detected. If EDIT_UPDATE is specified and the article
-        * doesn't exist, the function will return an edit-gone-missing error. If
-        * EDIT_NEW is specified and the article does exist, an edit-already-exists
-        * error will be returned. These two conditions are also possible with
-        * auto-detection due to MediaWiki's performance-optimised locking strategy.
-        *
-        * @param bool|int $baseRevId The revision ID this edit was based off, if any
-        * @param User $user The user doing the edit
-        *
-        * @throws MWException
-        * @return Status object. Possible errors:
-        *   edit-hook-aborted: The ArticleSave hook aborted the edit but didn't
-        *     set the fatal flag of $status
-        *   edit-gone-missing: In update mode, but the article didn't exist.
-        *   edit-conflict: In update mode, the article changed unexpectedly.
-        *   edit-no-change: Warning that the text was the same as before.
-        *   edit-already-exists: In creation mode, but the article already exists.
-        *
-        * Extensions may define additional errors.
-        *
-        * $return->value will contain an associative array with members as follows:
-        *     new: Boolean indicating if the function attempted to create a new article.
-        *     revision: The revision object for the inserted revision, or null.
-        *
-        * Compatibility note: this function previously returned a boolean value
-        * indicating success/failure
-        *
-        * @deprecated since 1.21: use doEditContent() instead.
-        */
-       public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-
-               $content = ContentHandler::makeContent( $text, $this->getTitle() );
-
-               return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
-       }
-
-       /**
-        * Change an existing article or create a new article. Updates RC and all necessary caches,
-        * optionally via the deferred update array.
-        *
-        * @param Content $content New content
-        * @param string $summary Edit summary
-        * @param int $flags Bitfield:
-        *      EDIT_NEW
-        *          Article is known or assumed to be non-existent, create a new one
-        *      EDIT_UPDATE
-        *          Article is known or assumed to be pre-existing, update it
-        *      EDIT_MINOR
-        *          Mark this edit minor, if the user is allowed to do so
-        *      EDIT_SUPPRESS_RC
-        *          Do not log the change in recentchanges
-        *      EDIT_FORCE_BOT
-        *          Mark the edit a "bot" edit regardless of user rights
-        *      EDIT_DEFER_UPDATES
-        *          Defer some of the updates until the end of index.php
-        *      EDIT_AUTOSUMMARY
-        *          Fill in blank summaries with generated text where possible
-        *
-        * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the
-        * article will be detected. If EDIT_UPDATE is specified and the article
-        * doesn't exist, the function will return an edit-gone-missing error. If
-        * EDIT_NEW is specified and the article does exist, an edit-already-exists
-        * error will be returned. These two conditions are also possible with
-        * auto-detection due to MediaWiki's performance-optimised locking strategy.
-        *
-        * @param bool|int $baseRevId The revision ID this edit was based off, if any
-        * @param User $user The user doing the edit
-        * @param string $serialisation_format Format for storing the content in the
-        *   database.
-        *
-        * @throws MWException
-        * @return Status object. Possible errors:
-        *     edit-hook-aborted: The ArticleSave hook aborted the edit but didn't
-        *       set the fatal flag of $status.
-        *     edit-gone-missing: In update mode, but the article didn't exist.
-        *     edit-conflict: In update mode, the article changed unexpectedly.
-        *     edit-no-change: Warning that the text was the same as before.
-        *     edit-already-exists: In creation mode, but the article already exists.
-        *
-        *  Extensions may define additional errors.
-        *
-        *  $return->value will contain an associative array with members as follows:
-        *     new: Boolean indicating if the function attempted to create a new article.
-        *     revision: The revision object for the inserted revision, or null.
-        *
-        * @since 1.21
-        */
-       public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
-               User $user = null, $serialisation_format = null
-       ) {
-               global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
-
-               // Low-level sanity check
-               if ( $this->mTitle->getText() === '' ) {
-                       throw new MWException( 'Something is trying to edit an article with an empty title' );
-               }
-
-               wfProfileIn( __METHOD__ );
-
-               if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
-                       wfProfileOut( __METHOD__ );
-                       return Status::newFatal( 'content-not-allowed-here',
-                               ContentHandler::getLocalizedName( $content->getModel() ),
-                               $this->getTitle()->getPrefixedText() );
-               }
-
-               $user = is_null( $user ) ? $wgUser : $user;
-               $status = Status::newGood( array() );
-
-               // Load the data from the master database if needed.
-               // The caller may already loaded it from the master or even loaded it using
-               // SELECT FOR UPDATE, so do not override that using clear().
-               $this->loadPageData( 'fromdbmaster' );
-
-               $flags = $this->checkFlags( $flags );
-
-               // handle hook
-               $hook_args = array( &$this, &$user, &$content, &$summary,
-                                                       $flags & EDIT_MINOR, null, null, &$flags, &$status );
-
-               if ( !wfRunHooks( 'PageContentSave', $hook_args )
-                       || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
-
-                       wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
-
-                       if ( $status->isOK() ) {
-                               $status->fatal( 'edit-hook-aborted' );
-                       }
-
-                       wfProfileOut( __METHOD__ );
-                       return $status;
-               }
-
-               // Silently ignore EDIT_MINOR if not allowed
-               $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
-               $bot = $flags & EDIT_FORCE_BOT;
-
-               $old_content = $this->getContent( Revision::RAW ); // current revision's content
-
-               $oldsize = $old_content ? $old_content->getSize() : 0;
-               $oldid = $this->getLatest();
-               $oldIsRedirect = $this->isRedirect();
-               $oldcountable = $this->isCountable();
-
-               $handler = $content->getContentHandler();
-
-               // Provide autosummaries if one is not provided and autosummaries are enabled.
-               if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
-                       if ( !$old_content ) {
-                               $old_content = null;
-                       }
-                       $summary = $handler->getAutosummary( $old_content, $content, $flags );
-               }
-
-               $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
-               $serialized = $editInfo->pst;
-
-               /**
-                * @var Content $content
-                */
-               $content = $editInfo->pstContent;
-               $newsize = $content->getSize();
-
-               $dbw = wfGetDB( DB_MASTER );
-               $now = wfTimestampNow();
-               $this->mTimestamp = $now;
-
-               if ( $flags & EDIT_UPDATE ) {
-                       // Update article, but only if changed.
-                       $status->value['new'] = false;
-
-                       if ( !$oldid ) {
-                               // Article gone missing
-                               wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
-                               $status->fatal( 'edit-gone-missing' );
-
-                               wfProfileOut( __METHOD__ );
-                               return $status;
-                       } elseif ( !$old_content ) {
-                               // Sanity check for bug 37225
-                               wfProfileOut( __METHOD__ );
-                               throw new MWException( "Could not find text for current revision {$oldid}." );
-                       }
-
-                       $revision = new Revision( array(
-                               'page'       => $this->getId(),
-                               'title'      => $this->getTitle(), // for determining the default content model
-                               'comment'    => $summary,
-                               'minor_edit' => $isminor,
-                               'text'       => $serialized,
-                               'len'        => $newsize,
-                               'parent_id'  => $oldid,
-                               'user'       => $user->getId(),
-                               'user_text'  => $user->getName(),
-                               'timestamp'  => $now,
-                               'content_model' => $content->getModel(),
-                               'content_format' => $serialisation_format,
-                       ) ); // XXX: pass content object?!
-
-                       $changed = !$content->equals( $old_content );
-
-                       if ( $changed ) {
-                               if ( !$content->isValid() ) {
-                                       wfProfileOut( __METHOD__ );
-                                       throw new MWException( "New content failed validity check!" );
-                               }
-
-                               $dbw->begin( __METHOD__ );
-                               try {
-
-                                       $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
-                                       $status->merge( $prepStatus );
-
-                                       if ( !$status->isOK() ) {
-                                               $dbw->rollback( __METHOD__ );
-
-                                               wfProfileOut( __METHOD__ );
-                                               return $status;
-                                       }
-                                       $revisionId = $revision->insertOn( $dbw );
-
-                                       // Update page
-                                       //
-                                       // We check for conflicts by comparing $oldid with the current latest revision ID.
-                                       $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
-
-                                       if ( !$ok ) {
-                                               // Belated edit conflict! Run away!!
-                                               $status->fatal( 'edit-conflict' );
-
-                                               $dbw->rollback( __METHOD__ );
-
-                                               wfProfileOut( __METHOD__ );
-                                               return $status;
-                                       }
-
-                                       wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
-                                       // Update recentchanges
-                                       if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
-                                               // Mark as patrolled if the user can do so
-                                               $patrolled = $wgUseRCPatrol && !count(
-                                               $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
-                                               // Add RC row to the DB
-                                               $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
-                                                       $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
-                                                       $revisionId, $patrolled
-                                               );
-
-                                               // Log auto-patrolled edits
-                                               if ( $patrolled ) {
-                                                       PatrolLog::record( $rc, true, $user );
-                                               }
-                                       }
-                                       $user->incEditCount();
-                               } catch ( MWException $e ) {
-                                       $dbw->rollback( __METHOD__ );
-                                       // Question: Would it perhaps be better if this method turned all
-                                       // exceptions into $status's?
-                                       throw $e;
-                               }
-                               $dbw->commit( __METHOD__ );
-                       } else {
-                               // Bug 32948: revision ID must be set to page {{REVISIONID}} and
-                               // related variables correctly
-                               $revision->setId( $this->getLatest() );
-                       }
-
-                       // Update links tables, site stats, etc.
-                       $this->doEditUpdates(
-                               $revision,
-                               $user,
-                               array(
-                                       'changed' => $changed,
-                                       'oldcountable' => $oldcountable
-                               )
-                       );
-
-                       if ( !$changed ) {
-                               $status->warning( 'edit-no-change' );
-                               $revision = null;
-                               // Update page_touched, this is usually implicit in the page update
-                               // Other cache updates are done in onArticleEdit()
-                               $this->mTitle->invalidateCache();
-                       }
-               } else {
-                       // Create new article
-                       $status->value['new'] = true;
-
-                       $dbw->begin( __METHOD__ );
-                       try {
-
-                               $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
-                               $status->merge( $prepStatus );
-
-                               if ( !$status->isOK() ) {
-                                       $dbw->rollback( __METHOD__ );
-
-                                       wfProfileOut( __METHOD__ );
-                                       return $status;
-                               }
-
-                               $status->merge( $prepStatus );
-
-                               // Add the page record; stake our claim on this title!
-                               // This will return false if the article already exists
-                               $newid = $this->insertOn( $dbw );
-
-                               if ( $newid === false ) {
-                                       $dbw->rollback( __METHOD__ );
-                                       $status->fatal( 'edit-already-exists' );
-
-                                       wfProfileOut( __METHOD__ );
-                                       return $status;
-                               }
-
-                               // Save the revision text...
-                               $revision = new Revision( array(
-                                       'page'       => $newid,
-                                       'title'      => $this->getTitle(), // for determining the default content model
-                                       'comment'    => $summary,
-                                       'minor_edit' => $isminor,
-                                       'text'       => $serialized,
-                                       'len'        => $newsize,
-                                       'user'       => $user->getId(),
-                                       'user_text'  => $user->getName(),
-                                       'timestamp'  => $now,
-                                       'content_model' => $content->getModel(),
-                                       'content_format' => $serialisation_format,
-                               ) );
-                               $revisionId = $revision->insertOn( $dbw );
-
-                               // Bug 37225: use accessor to get the text as Revision may trim it
-                               $content = $revision->getContent(); // sanity; get normalized version
-
-                               if ( $content ) {
-                                       $newsize = $content->getSize();
-                               }
-
-                               // Update the page record with revision data
-                               $this->updateRevisionOn( $dbw, $revision, 0 );
-
-                               wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
-
-                               // Update recentchanges
-                               if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
-                                       // Mark as patrolled if the user can do so
-                                       $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
-                                               $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
-                                       // Add RC row to the DB
-                                       $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
-                                               '', $newsize, $revisionId, $patrolled );
-
-                                       // Log auto-patrolled edits
-                                       if ( $patrolled ) {
-                                               PatrolLog::record( $rc, true, $user );
-                                       }
-                               }
-                               $user->incEditCount();
-
-                       } catch ( MWException $e ) {
-                               $dbw->rollback( __METHOD__ );
-                               throw $e;
-                       }
-                       $dbw->commit( __METHOD__ );
-
-                       // Update links, etc.
-                       $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
-
-                       $hook_args = array( &$this, &$user, $content, $summary,
-                                                               $flags & EDIT_MINOR, null, null, &$flags, $revision );
-
-                       ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
-                       wfRunHooks( 'PageContentInsertComplete', $hook_args );
-               }
-
-               // Do updates right now unless deferral was requested
-               if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
-                       DeferredUpdates::doUpdates();
-               }
-
-               // Return the new revision (or null) to the caller
-               $status->value['revision'] = $revision;
-
-               $hook_args = array( &$this, &$user, $content, $summary,
-                                                       $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
-
-               ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
-               wfRunHooks( 'PageContentSaveComplete', $hook_args );
-
-               // Promote user to any groups they meet the criteria for
-               $user->addAutopromoteOnceGroups( 'onEdit' );
-
-               wfProfileOut( __METHOD__ );
-               return $status;
-       }
-
-       /**
-        * Get parser options suitable for rendering the primary article wikitext
-        *
-        * @see ContentHandler::makeParserOptions
-        *
-        * @param IContextSource|User|string $context One of the following:
-        *        - IContextSource: Use the User and the Language of the provided
-        *          context
-        *        - User: Use the provided User object and $wgLang for the language,
-        *          so use an IContextSource object if possible.
-        *        - 'canonical': Canonical options (anonymous user with default
-        *          preferences and content language).
-        * @return ParserOptions
-        */
-       public function makeParserOptions( $context ) {
-               $options = $this->getContentHandler()->makeParserOptions( $context );
-
-               if ( $this->getTitle()->isConversionTable() ) {
-                       // @todo ConversionTable should become a separate content model, so
-                       // we don't need special cases like this one.
-                       $options->disableContentConversion();
-               }
-
-               return $options;
-       }
-
-       /**
-        * Prepare text which is about to be saved.
-        * Returns a stdclass with source, pst and output members
-        *
-        * @deprecated since 1.21: use prepareContentForEdit instead.
-        * @return object
-        */
-       public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-               $content = ContentHandler::makeContent( $text, $this->getTitle() );
-               return $this->prepareContentForEdit( $content, $revid, $user );
-       }
-
-       /**
-        * Prepare content which is about to be saved.
-        * Returns a stdclass with source, pst and output members
-        *
-        * @param Content $content
-        * @param int|null $revid
-        * @param User|null $user
-        * @param string|null $serialization_format
-        *
-        * @return bool|object
-        *
-        * @since 1.21
-        */
-       public function prepareContentForEdit( Content $content, $revid = null, User $user = null,
-               $serialization_format = null
-       ) {
-               global $wgContLang, $wgUser;
-               $user = is_null( $user ) ? $wgUser : $user;
-               //XXX: check $user->getId() here???
-
-               // Use a sane default for $serialization_format, see bug 57026
-               if ( $serialization_format === null ) {
-                       $serialization_format = $content->getContentHandler()->getDefaultFormat();
-               }
-
-               if ( $this->mPreparedEdit
-                       && $this->mPreparedEdit->newContent
-                       && $this->mPreparedEdit->newContent->equals( $content )
-                       && $this->mPreparedEdit->revid == $revid
-                       && $this->mPreparedEdit->format == $serialization_format
-                       // XXX: also check $user here?
-               ) {
-                       // Already prepared
-                       return $this->mPreparedEdit;
-               }
-
-               $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
-               wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
-
-               $edit = (object)array();
-               $edit->revid = $revid;
-               $edit->timestamp = wfTimestampNow();
-
-               $edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
-
-               $edit->format = $serialization_format;
-               $edit->popts = $this->makeParserOptions( 'canonical' );
-               $edit->output = $edit->pstContent
-                       ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
-                       : null;
-
-               $edit->newContent = $content;
-               $edit->oldContent = $this->getContent( Revision::RAW );
-
-               // NOTE: B/C for hooks! don't use these fields!
-               $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
-               $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
-               $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : '';
-
-               $this->mPreparedEdit = $edit;
-               return $edit;
-       }
-
-       /**
-        * Do standard deferred updates after page edit.
-        * Update links tables, site stats, search index and message cache.
-        * Purges pages that include this page if the text was changed here.
-        * Every 100th edit, prune the recent changes table.
-        *
-        * @param Revision $revision
-        * @param User $user User object that did the revision
-        * @param array $options Array of options, following indexes are used:
-        * - changed: boolean, whether the revision changed the content (default true)
-        * - created: boolean, whether the revision created the page (default false)
-        * - oldcountable: boolean or null (default null):
-        *   - boolean: whether the page was counted as an article before that
-        *     revision, only used in changed is true and created is false
-        *   - null: don't change the article count
-        */
-       public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
-               global $wgEnableParserCache;
-
-               wfProfileIn( __METHOD__ );
-
-               $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
-               $content = $revision->getContent();
-
-               // Parse the text
-               // Be careful not to do pre-save transform twice: $text is usually
-               // already pre-save transformed once.
-               if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
-                       wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
-                       $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
-               } else {
-                       wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
-                       $editInfo = $this->mPreparedEdit;
-               }
-
-               // Save it to the parser cache
-               if ( $wgEnableParserCache ) {
-                       $parserCache = ParserCache::singleton();
-                       $parserCache->save(
-                               $editInfo->output, $this, $editInfo->popts, $editInfo->timestamp, $editInfo->revid
-                       );
-               }
-
-               // Update the links tables and other secondary data
-               if ( $content ) {
-                       $recursive = $options['changed']; // bug 50785
-                       $updates = $content->getSecondaryDataUpdates(
-                               $this->getTitle(), null, $recursive, $editInfo->output );
-                       DataUpdate::runUpdates( $updates );
-               }
-
-               wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
-
-               if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
-                       if ( 0 == mt_rand( 0, 99 ) ) {
-                               // Flush old entries from the `recentchanges` table; we do this on
-                               // random requests so as to avoid an increase in writes for no good reason
-                               RecentChange::purgeExpiredChanges();
-                       }
-               }
-
-               if ( !$this->exists() ) {
-                       wfProfileOut( __METHOD__ );
-                       return;
-               }
-
-               $id = $this->getId();
-               $title = $this->mTitle->getPrefixedDBkey();
-               $shortTitle = $this->mTitle->getDBkey();
-
-               if ( !$options['changed'] ) {
-                       $good = 0;
-               } elseif ( $options['created'] ) {
-                       $good = (int)$this->isCountable( $editInfo );
-               } elseif ( $options['oldcountable'] !== null ) {
-                       $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
-               } else {
-                       $good = 0;
-               }
-               $edits = $options['changed'] ? 1 : 0;
-               $total = $options['created'] ? 1 : 0;
-
-               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, $edits, $good, $total ) );
-               DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );
-
-               // If this is another user's talk page, update newtalk.
-               // Don't do this if $options['changed'] = false (null-edits) nor if
-               // it's a minor edit and the user doesn't want notifications for those.
-               if ( $options['changed']
-                       && $this->mTitle->getNamespace() == NS_USER_TALK
-                       && $shortTitle != $user->getTitleKey()
-                       && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
-               ) {
-                       $recipient = User::newFromName( $shortTitle, false );
-                       if ( !$recipient ) {
-                               wfDebug( __METHOD__ . ": invalid username\n" );
-                       } else {
-                               // Allow extensions to prevent user notification when a new message is added to their talk page
-                               if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
-                                       if ( User::isIP( $shortTitle ) ) {
-                                               // An anonymous user
-                                               $recipient->setNewtalk( true, $revision );
-                                       } elseif ( $recipient->isLoggedIn() ) {
-                                               $recipient->setNewtalk( true, $revision );
-                                       } else {
-                                               wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
-                                       }
-                               }
-                       }
-               }
-
-               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-                       // XXX: could skip pseudo-messages like js/css here, based on content model.
-                       $msgtext = $content ? $content->getWikitextForTransclusion() : null;
-                       if ( $msgtext === false || $msgtext === null ) {
-                               $msgtext = '';
-                       }
-
-                       MessageCache::singleton()->replace( $shortTitle, $msgtext );
-               }
-
-               if ( $options['created'] ) {
-                       self::onArticleCreate( $this->mTitle );
-               } elseif ( $options['changed'] ) { // bug 50785
-                       self::onArticleEdit( $this->mTitle );
-               }
-
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Edit an article without doing all that other stuff
-        * The article must already exist; link tables etc
-        * are not updated, caches are not flushed.
-        *
-        * @param string $text Text submitted
-        * @param User $user The relevant user
-        * @param string $comment Comment submitted
-        * @param bool $minor Whereas it's a minor modification
-        *
-        * @deprecated since 1.21, use doEditContent() instead.
-        */
-       public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
-               ContentHandler::deprecated( __METHOD__, "1.21" );
-
-               $content = ContentHandler::makeContent( $text, $this->getTitle() );
-               $this->doQuickEditContent( $content, $user, $comment, $minor );
-       }
-
-       /**
-        * Edit an article without doing all that other stuff
-        * The article must already exist; link tables etc
-        * are not updated, caches are not flushed.
-        *
-        * @param Content $content Content submitted
-        * @param User $user The relevant user
-        * @param string $comment comment submitted
-        * @param string $serialisation_format Format for storing the content in the database
-        * @param bool $minor Whereas it's a minor modification
-        */
-       public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
-               $serialisation_format = null
-       ) {
-               wfProfileIn( __METHOD__ );
-
-               $serialized = $content->serialize( $serialisation_format );
-
-               $dbw = wfGetDB( DB_MASTER );
-               $revision = new Revision( array(
-                       'title'      => $this->getTitle(), // for determining the default content model
-                       'page'       => $this->getId(),
-                       'user_text'  => $user->getName(),
-                       'user'       => $user->getId(),
-                       'text'       => $serialized,
-                       'length'     => $content->getSize(),
-                       'comment'    => $comment,
-                       'minor_edit' => $minor ? 1 : 0,
-               ) ); // XXX: set the content object?
-               $revision->insertOn( $dbw );
-               $this->updateRevisionOn( $dbw, $revision );
-
-               wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
-
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Update the article's restriction field, and leave a log entry.
-        * This works for protection both existing and non-existing pages.
-        *
-        * @param array $limit Set of restriction keys
-        * @param array $expiry Per restriction type expiration
-        * @param int &$cascade Set to false if cascading protection isn't allowed.
-        * @param string $reason
-        * @param User $user The user updating the restrictions
-        * @return Status
-        */
-       public function doUpdateRestrictions( array $limit, array $expiry,
-               &$cascade, $reason, User $user
-       ) {
-               global $wgCascadingRestrictionLevels, $wgContLang;
-
-               if ( wfReadOnly() ) {
-                       return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
-               }
-
-               $this->loadPageData( 'fromdbmaster' );
-               $restrictionTypes = $this->mTitle->getRestrictionTypes();
-               $id = $this->getId();
-
-               if ( !$cascade ) {
-                       $cascade = false;
-               }
-
-               // Take this opportunity to purge out expired restrictions
-               Title::purgeExpiredRestrictions();
-
-               // @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
-               // we expect a single selection, but the schema allows otherwise.
-               $isProtected = false;
-               $protect = false;
-               $changed = false;
-
-               $dbw = wfGetDB( DB_MASTER );
-
-               foreach ( $restrictionTypes as $action ) {
-                       if ( !isset( $expiry[$action] ) ) {
-                               $expiry[$action] = $dbw->getInfinity();
-                       }
-                       if ( !isset( $limit[$action] ) ) {
-                               $limit[$action] = '';
-                       } elseif ( $limit[$action] != '' ) {
-                               $protect = true;
-                       }
-
-                       // Get current restrictions on $action
-                       $current = implode( '', $this->mTitle->getRestrictions( $action ) );
-                       if ( $current != '' ) {
-                               $isProtected = true;
-                       }
-
-                       if ( $limit[$action] != $current ) {
-                               $changed = true;
-                       } elseif ( $limit[$action] != '' ) {
-                               // Only check expiry change if the action is actually being
-                               // protected, since expiry does nothing on an not-protected
-                               // action.
-                               if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
-                                       $changed = true;
-                               }
-                       }
-               }
-
-               if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
-                       $changed = true;
-               }
-
-               // If nothing has changed, do nothing
-               if ( !$changed ) {
-                       return Status::newGood();
-               }
-
-               if ( !$protect ) { // No protection at all means unprotection
-                       $revCommentMsg = 'unprotectedarticle';
-                       $logAction = 'unprotect';
-               } elseif ( $isProtected ) {
-                       $revCommentMsg = 'modifiedarticleprotection';
-                       $logAction = 'modify';
-               } else {
-                       $revCommentMsg = 'protectedarticle';
-                       $logAction = 'protect';
-               }
-
-               // Truncate for whole multibyte characters
-               $reason = $wgContLang->truncate( $reason, 255 );
-
-               $logRelationsValues = array();
-               $logRelationsField = null;
-
-               if ( $id ) { // Protection of existing page
-                       if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
-                               return Status::newGood();
-                       }
-
-                       // Only certain restrictions can cascade...
-                       $editrestriction = isset( $limit['edit'] )
-                               ? array( $limit['edit'] )
-                               : $this->mTitle->getRestrictions( 'edit' );
-                       foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
-                               $editrestriction[$key] = 'editprotected'; // backwards compatibility
-                       }
-                       foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
-                               $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
-                       }
-
-                       $cascadingRestrictionLevels = $wgCascadingRestrictionLevels;
-                       foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
-                               $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
-                       }
-                       foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
-                               $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
-                       }
-
-                       // The schema allows multiple restrictions
-                       if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
-                               $cascade = false;
-                       }
-
-                       // insert null revision to identify the page protection change as edit summary
-                       $latest = $this->getLatest();
-                       $nullRevision = $this->insertProtectNullRevision(
-                               $revCommentMsg,
-                               $limit,
-                               $expiry,
-                               $cascade,
-                               $reason,
-                               $user
-                       );
-
-                       if ( $nullRevision === null ) {
-                               return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
-                       }
-
-                       $logRelationsField = 'pr_id';
-
-                       // Update restrictions table
-                       foreach ( $limit as $action => $restrictions ) {
-                               $dbw->delete(
-                                       'page_restrictions',
-                                       array(
-                                               'pr_page' => $id,
-                                               'pr_type' => $action
-                                       ),
-                                       __METHOD__
-                               );
-                               if ( $restrictions != '' ) {
-                                       $dbw->insert(
-                                               'page_restrictions',
-                                               array(
-                                                       'pr_id' => $dbw->nextSequenceValue( 'page_restrictions_pr_id_seq' ),
-                                                       'pr_page' => $id,
-                                                       'pr_type' => $action,
-                                                       'pr_level' => $restrictions,
-                                                       'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
-                                                       'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
-                                               ),
-                                               __METHOD__
-                                       );
-                                       $logRelationsValues[] = $dbw->insertId();
-                               }
-                       }
-
-                       // Clear out legacy restriction fields
-                       $dbw->update(
-                               'page',
-                               array( 'page_restrictions' => '' ),
-                               array( 'page_id' => $id ),
-                               __METHOD__
-                       );
-
-                       wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
-                       wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
-               } else { // Protection of non-existing page (also known as "title protection")
-                       // Cascade protection is meaningless in this case
-                       $cascade = false;
-
-                       if ( $limit['create'] != '' ) {
-                               $dbw->replace( 'protected_titles',
-                                       array( array( 'pt_namespace', 'pt_title' ) ),
-                                       array(
-                                               'pt_namespace' => $this->mTitle->getNamespace(),
-                                               'pt_title' => $this->mTitle->getDBkey(),
-                                               'pt_create_perm' => $limit['create'],
-                                               'pt_timestamp' => $dbw->timestamp(),
-                                               'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
-                                               'pt_user' => $user->getId(),
-                                               'pt_reason' => $reason,
-                                       ), __METHOD__
-                               );
-                       } else {
-                               $dbw->delete( 'protected_titles',
-                                       array(
-                                               'pt_namespace' => $this->mTitle->getNamespace(),
-                                               'pt_title' => $this->mTitle->getDBkey()
-                                       ), __METHOD__
-                               );
-                       }
-               }
-
-               $this->mTitle->flushRestrictions();
-               InfoAction::invalidateCache( $this->mTitle );
-
-               if ( $logAction == 'unprotect' ) {
-                       $params = array();
-               } else {
-                       $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
-                       $params = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
-               }
-
-               // Update the protection log
-               $log = new LogPage( 'protect' );
-               $logId = $log->addEntry( $logAction, $this->mTitle, $reason, $params, $user );
-               if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
-                       $log->addRelations( $logRelationsField, $logRelationsValues, $logId );
-               }
-
-               return Status::newGood();
-       }
-
-       /**
-        * Insert a new null revision for this page.
-        *
-        * @param string $revCommentMsg Comment message key for the revision
-        * @param array $limit Set of restriction keys
-        * @param array $expiry Per restriction type expiration
-        * @param int $cascade Set to false if cascading protection isn't allowed.
-        * @param string $reason
-        * @param User|null $user
-        * @return Revision|null Null on error
-        */
-       public function insertProtectNullRevision( $revCommentMsg, array $limit,
-               array $expiry, $cascade, $reason, $user = null
-       ) {
-               global $wgContLang;
-               $dbw = wfGetDB( DB_MASTER );
-
-               // Prepare a null revision to be added to the history
-               $editComment = $wgContLang->ucfirst(
-                       wfMessage(
-                               $revCommentMsg,
-                               $this->mTitle->getPrefixedText()
-                       )->inContentLanguage()->text()
-               );
-               if ( $reason ) {
-                       $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
-               }
-               $protectDescription = $this->protectDescription( $limit, $expiry );
-               if ( $protectDescription ) {
-                       $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
-                       $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
-                               ->inContentLanguage()->text();
-               }
-               if ( $cascade ) {
-                       $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
-                       $editComment .= wfMessage( 'brackets' )->params(
-                               wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
-                       )->inContentLanguage()->text();
-               }
-
-               $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true, $user );
-               if ( $nullRev ) {
-                       $nullRev->insertOn( $dbw );
-
-                       // Update page record and touch page
-                       $oldLatest = $nullRev->getParentId();
-                       $this->updateRevisionOn( $dbw, $nullRev, $oldLatest );
-               }
-
-               return $nullRev;
-       }
-
-       /**
-        * @param string $expiry 14-char timestamp or "infinity", or false if the input was invalid
-        * @return string
-        */
-       protected function formatExpiry( $expiry ) {
-               global $wgContLang;
-               $dbr = wfGetDB( DB_SLAVE );
-
-               $encodedExpiry = $dbr->encodeExpiry( $expiry );
-               if ( $encodedExpiry != 'infinity' ) {
-                       return wfMessage(
-                               'protect-expiring',
-                               $wgContLang->timeanddate( $expiry, false, false ),
-                               $wgContLang->date( $expiry, false, false ),
-                               $wgContLang->time( $expiry, false, false )
-                       )->inContentLanguage()->text();
-               } else {
-                       return wfMessage( 'protect-expiry-indefinite' )
-                               ->inContentLanguage()->text();
-               }
-       }
-
-       /**
-        * Builds the description to serve as comment for the edit.
-        *
-        * @param array $limit Set of restriction keys
-        * @param array $expiry Per restriction type expiration
-        * @return string
-        */
-       public function protectDescription( array $limit, array $expiry ) {
-               $protectDescription = '';
-
-               foreach ( array_filter( $limit ) as $action => $restrictions ) {
-                       # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
-                       # All possible message keys are listed here for easier grepping:
-                       # * restriction-create
-                       # * restriction-edit
-                       # * restriction-move
-                       # * restriction-upload
-                       $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
-                       # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
-                       # with '' filtered out. All possible message keys are listed below:
-                       # * protect-level-autoconfirmed
-                       # * protect-level-sysop
-                       $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
-
-                       $expiryText = $this->formatExpiry( $expiry[$action] );
-
-                       if ( $protectDescription !== '' ) {
-                               $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
-                       }
-                       $protectDescription .= wfMessage( 'protect-summary-desc' )
-                               ->params( $actionText, $restrictionsText, $expiryText )
-                               ->inContentLanguage()->text();
-               }
-
-               return $protectDescription;
-       }
-
-       /**
-        * Builds the description to serve as comment for the log entry.
-        *
-        * Some bots may parse IRC lines, which are generated from log entries which contain plain
-        * protect description text. Keep them in old format to avoid breaking compatibility.
-        * TODO: Fix protection log to store structured description and format it on-the-fly.
-        *
-        * @param array $limit Set of restriction keys
-        * @param array $expiry Per restriction type expiration
-        * @return string
-        */
-       public function protectDescriptionLog( array $limit, array $expiry ) {
-               global $wgContLang;
-
-               $protectDescriptionLog = '';
-
-               foreach ( array_filter( $limit ) as $action => $restrictions ) {
-                       $expiryText = $this->formatExpiry( $expiry[$action] );
-                       $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
-               }
-
-               return trim( $protectDescriptionLog );
-       }
-
-       /**
-        * Take an array of page restrictions and flatten it to a string
-        * suitable for insertion into the page_restrictions field.
-        *
-        * @param string[] $limit
-        *
-        * @throws MWException
-        * @return string
-        */
-       protected static function flattenRestrictions( $limit ) {
-               if ( !is_array( $limit ) ) {
-                       throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
-               }
-
-               $bits = array();
-               ksort( $limit );
-
-               foreach ( array_filter( $limit ) as $action => $restrictions ) {
-                       $bits[] = "$action=$restrictions";
-               }
-
-               return implode( ':', $bits );
-       }
-
-       /**
-        * Same as doDeleteArticleReal(), but returns a simple boolean. This is kept around for
-        * backwards compatibility, if you care about error reporting you should use
-        * doDeleteArticleReal() instead.
-        *
-        * Deletes the article with database consistency, writes logs, purges caches
-        *
-        * @param string $reason Delete reason for deletion log
-        * @param bool $suppress Suppress all revisions and log the deletion in
-        *        the suppression log instead of the deletion log
-        * @param int $id Article ID
-        * @param bool $commit Defaults to true, triggers transaction end
-        * @param array &$error Array of errors to append to
-        * @param User $user The deleting user
-        * @return bool true if successful
-        */
-       public function doDeleteArticle(
-               $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
-       ) {
-               $status = $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user );
-               return $status->isGood();
-       }
-
-       /**
-        * Back-end article deletion
-        * Deletes the article with database consistency, writes logs, purges caches
-        *
-        * @since 1.19
-        *
-        * @param string $reason Delete reason for deletion log
-        * @param bool $suppress Suppress all revisions and log the deletion in
-        *   the suppression log instead of the deletion log
-        * @param int $id Article ID
-        * @param bool $commit Defaults to true, triggers transaction end
-        * @param array &$error Array of errors to append to
-        * @param User $user The deleting user
-        * @return Status Status object; if successful, $status->value is the log_id of the
-        *   deletion log entry. If the page couldn't be deleted because it wasn't
-        *   found, $status is a non-fatal 'cannotdelete' error
-        */
-       public function doDeleteArticleReal(
-               $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
-       ) {
-               global $wgUser, $wgContentHandlerUseDB;
-
-               wfDebug( __METHOD__ . "\n" );
-
-               $status = Status::newGood();
-
-               if ( $this->mTitle->getDBkey() === '' ) {
-                       $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
-                       return $status;
-               }
-
-               $user = is_null( $user ) ? $wgUser : $user;
-               if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
-                       if ( $status->isOK() ) {
-                               // Hook aborted but didn't set a fatal status
-                               $status->fatal( 'delete-hook-aborted' );
-                       }
-                       return $status;
-               }
-
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->begin( __METHOD__ );
-
-               if ( $id == 0 ) {
-                       $this->loadPageData( 'forupdate' );
-                       $id = $this->getID();
-                       if ( $id == 0 ) {
-                               $dbw->rollback( __METHOD__ );
-                               $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
-                               return $status;
-                       }
-               }
-
-               // we need to remember the old content so we can use it to generate all deletion updates.
-               $content = $this->getContent( Revision::RAW );
-
-               // Bitfields to further suppress the content
-               if ( $suppress ) {
-                       $bitfield = 0;
-                       // This should be 15...
-                       $bitfield |= Revision::DELETED_TEXT;
-                       $bitfield |= Revision::DELETED_COMMENT;
-                       $bitfield |= Revision::DELETED_USER;
-                       $bitfield |= Revision::DELETED_RESTRICTED;
-               } else {
-                       $bitfield = 'rev_deleted';
-               }
-
-               // For now, shunt the revision data into the archive table.
-               // Text is *not* removed from the text table; bulk storage
-               // is left intact to avoid breaking block-compression or
-               // immutable storage schemes.
-               //
-               // For backwards compatibility, note that some older archive
-               // table entries will have ar_text and ar_flags fields still.
-               //
-               // In the future, we may keep revisions and mark them with
-               // the rev_deleted field, which is reserved for this purpose.
-
-               $row = array(
-                       'ar_namespace'  => 'page_namespace',
-                       'ar_title'      => 'page_title',
-                       'ar_comment'    => 'rev_comment',
-                       'ar_user'       => 'rev_user',
-                       'ar_user_text'  => 'rev_user_text',
-                       'ar_timestamp'  => 'rev_timestamp',
-                       'ar_minor_edit' => 'rev_minor_edit',
-                       'ar_rev_id'     => 'rev_id',
-                       'ar_parent_id'  => 'rev_parent_id',
-                       'ar_text_id'    => 'rev_text_id',
-                       'ar_text'       => '\'\'', // Be explicit to appease
-                       'ar_flags'      => '\'\'', // MySQL's "strict mode"...
-                       'ar_len'        => 'rev_len',
-                       'ar_page_id'    => 'page_id',
-                       'ar_deleted'    => $bitfield,
-                       'ar_sha1'       => 'rev_sha1',
-               );
-
-               if ( $wgContentHandlerUseDB ) {
-                       $row['ar_content_model'] = 'rev_content_model';
-                       $row['ar_content_format'] = 'rev_content_format';
-               }
-
-               $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
-                       $row,
-                       array(
-                               'page_id' => $id,
-                               'page_id = rev_page'
-                       ), __METHOD__
-               );
-
-               // Now that it's safely backed up, delete it
-               $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
-               $ok = ( $dbw->affectedRows() > 0 ); // $id could be laggy
-
-               if ( !$ok ) {
-                       $dbw->rollback( __METHOD__ );
-                       $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
-                       return $status;
-               }
-
-               if ( !$dbw->cascadingDeletes() ) {
-                       $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
-               }
-
-               // Clone the title, so we have the information we need when we log
-               $logTitle = clone $this->mTitle;
-
-               $this->doDeleteUpdates( $id, $content );
-
-               // Log the deletion, if the page was suppressed, log it at Oversight instead
-               $logtype = $suppress ? 'suppress' : 'delete';
-
-               $logEntry = new ManualLogEntry( $logtype, 'delete' );
-               $logEntry->setPerformer( $user );
-               $logEntry->setTarget( $logTitle );
-               $logEntry->setComment( $reason );
-               $logid = $logEntry->insert();
-
-               $dbw->onTransactionPreCommitOrIdle( function() use ( $dbw, $logEntry, $logid ) {
-                       // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
-                       $logEntry->publish( $logid );
-               } );
-
-               if ( $commit ) {
-                       $dbw->commit( __METHOD__ );
-               }
-
-               wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
-               $status->value = $logid;
-               return $status;
-       }
-
-       /**
-        * Do some database updates after deletion
-        *
-        * @param int $id page_id value of the page being deleted
-        * @param Content $content Optional page content to be used when determining
-        *   the required updates. This may be needed because $this->getContent()
-        *   may already return null when the page proper was deleted.
-        */
-       public function doDeleteUpdates( $id, Content $content = null ) {
-               // update site status
-               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
-
-               // remove secondary indexes, etc
-               $updates = $this->getDeletionUpdates( $content );
-               DataUpdate::runUpdates( $updates );
-
-               // Reparse any pages transcluding this page
-               LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
-
-               // Reparse any pages including this image
-               if ( $this->mTitle->getNamespace() == NS_FILE ) {
-                       LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' );
-               }
-
-               // Clear caches
-               WikiPage::onArticleDelete( $this->mTitle );
-
-               // Reset this object and the Title object
-               $this->loadFromRow( false, self::READ_LATEST );
-
-               // Search engine
-               DeferredUpdates::addUpdate( new SearchUpdate( $id, $this->mTitle ) );
-       }
-
-       /**
-        * Roll back the most recent consecutive set of edits to a page
-        * from the same user; fails if there are no eligible edits to
-        * roll back to, e.g. user is the sole contributor. This function
-        * performs permissions checks on $user, then calls commitRollback()
-        * to do the dirty work
-        *
-        * @todo Separate the business/permission stuff out from backend code
-        *
-        * @param string $fromP Name of the user whose edits to rollback.
-        * @param string $summary Custom summary. Set to default summary if empty.
-        * @param string $token Rollback token.
-        * @param bool $bot If true, mark all reverted edits as bot.
-        *
-        * @param array $resultDetails contains result-specific array of additional values
-        *    'alreadyrolled' : 'current' (rev)
-        *    success        : 'summary' (str), 'current' (rev), 'target' (rev)
-        *
-        * @param User $user The user performing the rollback
-        * @return array Array of errors, each error formatted as
-        *   array(messagekey, param1, param2, ...).
-        * On success, the array is empty.  This array can also be passed to
-        * OutputPage::showPermissionsErrorPage().
-        */
-       public function doRollback(
-               $fromP, $summary, $token, $bot, &$resultDetails, User $user
-       ) {
-               $resultDetails = null;
-
-               // Check permissions
-               $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user );
-               $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $user );
-               $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
-
-               if ( !$user->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) ) {
-                       $errors[] = array( 'sessionfailure' );
-               }
-
-               if ( $user->pingLimiter( 'rollback' ) || $user->pingLimiter() ) {
-                       $errors[] = array( 'actionthrottledtext' );
-               }
-
-               // If there were errors, bail out now
-               if ( !empty( $errors ) ) {
-                       return $errors;
-               }
-
-               return $this->commitRollback( $fromP, $summary, $bot, $resultDetails, $user );
-       }
-
-       /**
-        * Backend implementation of doRollback(), please refer there for parameter
-        * and return value documentation
-        *
-        * NOTE: This function does NOT check ANY permissions, it just commits the
-        * rollback to the DB. Therefore, you should only call this function direct-
-        * ly if you want to use custom permissions checks. If you don't, use
-        * doRollback() instead.
-        * @param string $fromP Name of the user whose edits to rollback.
-        * @param string $summary Custom summary. Set to default summary if empty.
-        * @param bool $bot If true, mark all reverted edits as bot.
-        *
-        * @param array $resultDetails Contains result-specific array of additional values
-        * @param User $guser The user performing the rollback
-        * @return array
-        */
-       public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
-               global $wgUseRCPatrol, $wgContLang;
-
-               $dbw = wfGetDB( DB_MASTER );
-
-               if ( wfReadOnly() ) {
-                       return array( array( 'readonlytext' ) );
-               }
-
-               // Get the last editor
-               $current = $this->getRevision();
-               if ( is_null( $current ) ) {
-                       // Something wrong... no page?
-                       return array( array( 'notanarticle' ) );
-               }
-
-               $from = str_replace( '_', ' ', $fromP );
-               // User name given should match up with the top revision.
-               // If the user was deleted then $from should be empty.
-               if ( $from != $current->getUserText() ) {
-                       $resultDetails = array( 'current' => $current );
-                       return array( array( 'alreadyrolled',
-                               htmlspecialchars( $this->mTitle->getPrefixedText() ),
-                               htmlspecialchars( $fromP ),
-                               htmlspecialchars( $current->getUserText() )
-                       ) );
-               }
-
-               // Get the last edit not by this guy...
-               // Note: these may not be public values
-               $user = intval( $current->getRawUser() );
-               $user_text = $dbw->addQuotes( $current->getRawUserText() );
-               $s = $dbw->selectRow( 'revision',
-                       array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
-                       array( 'rev_page' => $current->getPage(),
-                               "rev_user != {$user} OR rev_user_text != {$user_text}"
-                       ), __METHOD__,
-                       array( 'USE INDEX' => 'page_timestamp',
-                               'ORDER BY' => 'rev_timestamp DESC' )
-                       );
-               if ( $s === false ) {
-                       // No one else ever edited this page
-                       return array( array( 'cantrollback' ) );
-               } elseif ( $s->rev_deleted & Revision::DELETED_TEXT
-                       || $s->rev_deleted & Revision::DELETED_USER
-               ) {
-                       // Only admins can see this text
-                       return array( array( 'notvisiblerev' ) );
-               }
-
-               // Set patrolling and bot flag on the edits, which gets rollbacked.
-               // This is done before the rollback edit to have patrolling also on failure (bug 62157).
-               $set = array();
-               if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
-                       // Mark all reverted edits as bot
-                       $set['rc_bot'] = 1;
-               }
-
-               if ( $wgUseRCPatrol ) {
-                       // Mark all reverted edits as patrolled
-                       $set['rc_patrolled'] = 1;
-               }
-
-               if ( count( $set ) ) {
-                       $dbw->update( 'recentchanges', $set,
-                               array( /* WHERE */
-                                       'rc_cur_id' => $current->getPage(),
-                                       'rc_user_text' => $current->getUserText(),
-                                       'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ),
-                               ), __METHOD__
-                       );
-               }
-
-               // Generate the edit summary if necessary
-               $target = Revision::newFromId( $s->rev_id );
-               if ( empty( $summary ) ) {
-                       if ( $from == '' ) { // no public user name
-                               $summary = wfMessage( 'revertpage-nouser' );
-                       } else {
-                               $summary = wfMessage( 'revertpage' );
-                       }
-               }
-
-               // Allow the custom summary to use the same args as the default message
-               $args = array(
-                       $target->getUserText(), $from, $s->rev_id,
-                       $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
-                       $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
-               );
-               if ( $summary instanceof Message ) {
-                       $summary = $summary->params( $args )->inContentLanguage()->text();
-               } else {
-                       $summary = wfMsgReplaceArgs( $summary, $args );
-               }
-
-               // Trim spaces on user supplied text
-               $summary = trim( $summary );
-
-               // Truncate for whole multibyte characters.
-               $summary = $wgContLang->truncate( $summary, 255 );
-
-               // Save
-               $flags = EDIT_UPDATE;
-
-               if ( $guser->isAllowed( 'minoredit' ) ) {
-                       $flags |= EDIT_MINOR;
-               }
-
-               if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
-                       $flags |= EDIT_FORCE_BOT;
-               }
-
-               // Actually store the edit
-               $status = $this->doEditContent(
-                       $target->getContent(),
-                       $summary,
-                       $flags,
-                       $target->getId(),
-                       $guser
-               );
-
-               if ( !$status->isOK() ) {
-                       return $status->getErrorsArray();
-               }
-
-               // raise error, when the edit is an edit without a new version
-               if ( empty( $status->value['revision'] ) ) {
-                       $resultDetails = array( 'current' => $current );
-                       return array( array( 'alreadyrolled',
-                                       htmlspecialchars( $this->mTitle->getPrefixedText() ),
-                                       htmlspecialchars( $fromP ),
-                                       htmlspecialchars( $current->getUserText() )
-                       ) );
-               }
-
-               $revId = $status->value['revision']->getId();
-
-               wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
-
-               $resultDetails = array(
-                       'summary' => $summary,
-                       'current' => $current,
-                       'target' => $target,
-                       'newid' => $revId
-               );
-
-               return array();
-       }
-
-       /**
-        * The onArticle*() functions are supposed to be a kind of hooks
-        * which should be called whenever any of the specified actions
-        * are done.
-        *
-        * This is a good place to put code to clear caches, for instance.
-        *
-        * This is called on page move and undelete, as well as edit
-        *
-        * @param Title $title
-        */
-       public static function onArticleCreate( $title ) {
-               // Update existence markers on article/talk tabs...
-               if ( $title->isTalkPage() ) {
-                       $other = $title->getSubjectPage();
-               } else {
-                       $other = $title->getTalkPage();
-               }
-
-               $other->invalidateCache();
-               $other->purgeSquid();
-
-               $title->touchLinks();
-               $title->purgeSquid();
-               $title->deleteTitleProtection();
-       }
-
-       /**
-        * Clears caches when article is deleted
-        *
-        * @param Title $title
-        */
-       public static function onArticleDelete( $title ) {
-               // Update existence markers on article/talk tabs...
-               if ( $title->isTalkPage() ) {
-                       $other = $title->getSubjectPage();
-               } else {
-                       $other = $title->getTalkPage();
-               }
-
-               $other->invalidateCache();
-               $other->purgeSquid();
-
-               $title->touchLinks();
-               $title->purgeSquid();
-
-               // File cache
-               HTMLFileCache::clearFileCache( $title );
-               InfoAction::invalidateCache( $title );
-
-               // Messages
-               if ( $title->getNamespace() == NS_MEDIAWIKI ) {
-                       MessageCache::singleton()->replace( $title->getDBkey(), false );
-               }
-
-               // Images
-               if ( $title->getNamespace() == NS_FILE ) {
-                       $update = new HTMLCacheUpdate( $title, 'imagelinks' );
-                       $update->doUpdate();
-               }
-
-               // User talk pages
-               if ( $title->getNamespace() == NS_USER_TALK ) {
-                       $user = User::newFromName( $title->getText(), false );
-                       if ( $user ) {
-                               $user->setNewtalk( false );
-                       }
-               }
-
-               // Image redirects
-               RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
-       }
-
-       /**
-        * Purge caches on page update etc
-        *
-        * @param Title $title
-        * @todo Verify that $title is always a Title object (and never false or
-        *   null), add Title hint to parameter $title.
-        */
-       public static function onArticleEdit( $title ) {
-               // Invalidate caches of articles which include this page
-               DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
-
-               // Invalidate the caches of all pages which redirect here
-               DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' );
-
-               // Purge squid for this page only
-               $title->purgeSquid();
-
-               // Clear file cache for this page only
-               HTMLFileCache::clearFileCache( $title );
-               InfoAction::invalidateCache( $title );
-       }
-
-       /**#@-*/
-
-       /**
-        * Returns a list of categories this page is a member of.
-        * Results will include hidden categories
-        *
-        * @return TitleArray
-        */
-       public function getCategories() {
-               $id = $this->getId();
-               if ( $id == 0 ) {
-                       return TitleArray::newFromResult( new FakeResultWrapper( array() ) );
-               }
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( 'categorylinks',
-                       array( 'cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace' ),
-                       // Have to do that since DatabaseBase::fieldNamesWithAlias treats numeric indexes
-                       // as not being aliases, and NS_CATEGORY is numeric
-                       array( 'cl_from' => $id ),
-                       __METHOD__ );
-
-               return TitleArray::newFromResult( $res );
-       }
-
-       /**
-        * Returns a list of hidden categories this page is a member of.
-        * Uses the page_props and categorylinks tables.
-        *
-        * @return array Array of Title objects
-        */
-       public function getHiddenCategories() {
-               $result = array();
-               $id = $this->getId();
-
-               if ( $id == 0 ) {
-                       return array();
-               }
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
-                       array( 'cl_to' ),
-                       array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
-                               'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ),
-                       __METHOD__ );
-
-               if ( $res !== false ) {
-                       foreach ( $res as $row ) {
-                               $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
-                       }
-               }
-
-               return $result;
-       }
-
-       /**
-        * Return an applicable autosummary if one exists for the given edit.
-        * @param string|null $oldtext The previous text of the page.
-        * @param string|null $newtext The submitted text of the page.
-        * @param int $flags Bitmask: a bitmask of flags submitted for the edit.
-        * @return string An appropriate autosummary, or an empty string.
-        *
-        * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
-        */
-       public static function getAutosummary( $oldtext, $newtext, $flags ) {
-               // NOTE: stub for backwards-compatibility. assumes the given text is
-               // wikitext. will break horribly if it isn't.
-
-               ContentHandler::deprecated( __METHOD__, '1.21' );
-
-               $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
-               $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
-               $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
-
-               return $handler->getAutosummary( $oldContent, $newContent, $flags );
-       }
-
-       /**
-        * Auto-generates a deletion reason
-        *
-        * @param bool &$hasHistory Whether the page has a history
-        * @return string|bool String containing deletion reason or empty string, or boolean false
-        *    if no revision occurred
-        */
-       public function getAutoDeleteReason( &$hasHistory ) {
-               return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
-       }
-
-       /**
-        * Update all the appropriate counts in the category table, given that
-        * we've added the categories $added and deleted the categories $deleted.
-        *
-        * @param array $added The names of categories that were added
-        * @param array $deleted The names of categories that were deleted
-        */
-       public function updateCategoryCounts( array $added, array $deleted ) {
-               $that = $this;
-               $method = __METHOD__;
-               $dbw = wfGetDB( DB_MASTER );
-
-               // Do this at the end of the commit to reduce lock wait timeouts
-               $dbw->onTransactionPreCommitOrIdle(
-                       function() use ( $dbw, $that, $method, $added, $deleted ) {
-                               $ns = $that->getTitle()->getNamespace();
-
-                               $addFields = array( 'cat_pages = cat_pages + 1' );
-                               $removeFields = array( 'cat_pages = cat_pages - 1' );
-                               if ( $ns == NS_CATEGORY ) {
-                                       $addFields[] = 'cat_subcats = cat_subcats + 1';
-                                       $removeFields[] = 'cat_subcats = cat_subcats - 1';
-                               } elseif ( $ns == NS_FILE ) {
-                                       $addFields[] = 'cat_files = cat_files + 1';
-                                       $removeFields[] = 'cat_files = cat_files - 1';
-                               }
-
-                               if ( count( $added ) ) {
-                                       $insertRows = array();
-                                       foreach ( $added as $cat ) {
-                                               $insertRows[] = array(
-                                                       'cat_title'   => $cat,
-                                                       'cat_pages'   => 1,
-                                                       'cat_subcats' => ( $ns == NS_CATEGORY ) ? 1 : 0,
-                                                       'cat_files'   => ( $ns == NS_FILE ) ? 1 : 0,
-                                               );
-                                       }
-                                       $dbw->upsert(
-                                               'category',
-                                               $insertRows,
-                                               array( 'cat_title' ),
-                                               $addFields,
-                                               $method
-                                       );
-                               }
-
-                               if ( count( $deleted ) ) {
-                                       $dbw->update(
-                                               'category',
-                                               $removeFields,
-                                               array( 'cat_title' => $deleted ),
-                                               $method
-                                       );
-                               }
-
-                               foreach ( $added as $catName ) {
-                                       $cat = Category::newFromName( $catName );
-                                       wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $that ) );
-                               }
-
-                               foreach ( $deleted as $catName ) {
-                                       $cat = Category::newFromName( $catName );
-                                       wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $that ) );
-                               }
-                       }
-               );
-       }
-
-       /**
-        * Updates cascading protections
-        *
-        * @param ParserOutput $parserOutput ParserOutput object for the current version
-        */
-       public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
-               if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
-                       return;
-               }
-
-               // templatelinks or imagelinks tables 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 and images from imagelinks
-               $id = $this->getId();
-
-               $dbLinks = array();
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( array( 'templatelinks' ),
-                       array( 'tl_namespace', 'tl_title' ),
-                       array( 'tl_from' => $id ),
-                       __METHOD__
-               );
-
-               foreach ( $res as $row ) {
-                       $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true;
-               }
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( array( 'imagelinks' ),
-                       array( 'il_to' ),
-                       array( 'il_from' => $id ),
-                       __METHOD__
-               );
-
-               foreach ( $res as $row ) {
-                       $dbLinks[NS_FILE . ":{$row->il_to}"] = true;
-               }
-
-               // Get templates and images from parser output.
-               $poLinks = array();
-               foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
-                       foreach ( $templates as $dbk => $id ) {
-                               $poLinks["$ns:$dbk"] = true;
-                       }
-               }
-               foreach ( $parserOutput->getImages() as $dbk => $id ) {
-                       $poLinks[NS_FILE . ":$dbk"] = true;
-               }
-
-               // Get the diff
-               $links_diff = array_diff_key( $poLinks, $dbLinks );
-
-               if ( count( $links_diff ) > 0 ) {
-                       // Whee, link updates time.
-                       // Note: we are only interested in links here. We don't need to get
-                       // other DataUpdate items from the parser output.
-                       $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
-                       $u->doUpdate();
-               }
-       }
-
-       /**
-        * Return a list of templates used by this article.
-        * Uses the templatelinks table
-        *
-        * @deprecated since 1.19; use Title::getTemplateLinksFrom()
-        * @return array Array of Title objects
-        */
-       public function getUsedTemplates() {
-               return $this->mTitle->getTemplateLinksFrom();
-       }
-
-       /**
-        * This function is called right before saving the wikitext,
-        * so we can do things like signatures and links-in-context.
-        *
-        * @deprecated since 1.19; use Parser::preSaveTransform() instead
-        * @param string $text Article contents
-        * @param User $user User doing the edit
-        * @param ParserOptions $popts Parser options, default options for
-        *   the user loaded if null given
-        * @return string Article contents with altered wikitext markup (signatures
-        *      converted, {{subst:}}, templates, etc.)
-        */
-       public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
-               global $wgParser, $wgUser;
-
-               wfDeprecated( __METHOD__, '1.19' );
-
-               $user = is_null( $user ) ? $wgUser : $user;
-
-               if ( $popts === null ) {
-                       $popts = ParserOptions::newFromUser( $user );
-               }
-
-               return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
-       }
-
-       /**
-        * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
-        *
-        * @deprecated since 1.19; use Title::isBigDeletion() instead.
-        * @return bool
-        */
-       public function isBigDeletion() {
-               wfDeprecated( __METHOD__, '1.19' );
-               return $this->mTitle->isBigDeletion();
-       }
-
-       /**
-        * Get the  approximate revision count of this page.
-        *
-        * @deprecated since 1.19; use Title::estimateRevisionCount() instead.
-        * @return int
-        */
-       public function estimateRevisionCount() {
-               wfDeprecated( __METHOD__, '1.19' );
-               return $this->mTitle->estimateRevisionCount();
-       }
-
-       /**
-        * Update the article's restriction field, and leave a log entry.
-        *
-        * @deprecated since 1.19
-        * @param array $limit Set of restriction keys
-        * @param string $reason
-        * @param int &$cascade Set to false if cascading protection isn't allowed.
-        * @param array $expiry Per restriction type expiration
-        * @param User $user The user updating the restrictions
-        * @return bool true on success
-        */
-       public function updateRestrictions(
-               $limit = array(), $reason = '', &$cascade = 0, $expiry = array(), User $user = null
-       ) {
-               global $wgUser;
-
-               $user = is_null( $user ) ? $wgUser : $user;
-
-               return $this->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user )->isOK();
-       }
-
-       /**
-        * Returns a list of updates to be performed when this page is deleted. The
-        * updates should remove any information about this page from secondary data
-        * stores such as links tables.
-        *
-        * @param Content|null $content Optional Content object for determining the
-        *   necessary updates.
-        * @return array An array of DataUpdates objects
-        */
-       public function getDeletionUpdates( Content $content = null ) {
-               if ( !$content ) {
-                       // load content object, which may be used to determine the necessary updates
-                       // XXX: the content may not be needed to determine the updates, then this would be overhead.
-                       $content = $this->getContent( Revision::RAW );
-               }
-
-               if ( !$content ) {
-                       $updates = array();
-               } else {
-                       $updates = $content->getDeletionUpdates( $this );
-               }
-
-               wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
-               return $updates;
-       }
-
-}
-
-class PoolWorkArticleView extends PoolCounterWork {
-       /** @var Page */
-       private $page;
-
-       /** @var string */
-       private $cacheKey;
-
-       /** @var int */
-       private $revid;
-
-       /** @var ParserOptions */
-       private $parserOptions;
-
-       /** @var Content|null */
-       private $content = null;
-
-       /** @var ParserOutput|bool */
-       private $parserOutput = false;
-
-       /** @var bool */
-       private $isDirty = false;
-
-       /** @var Status|bool */
-       private $error = false;
-
-       /**
-        * @param Page $page
-        * @param int $revid ID of the revision being parsed.
-        * @param bool $useParserCache Whether to use the parser cache.
-        * @param ParserOptions $parserOptions ParserOptions to use for the parse
-        *   operation.
-        * @param Content|string $content Content to parse or null to load it; may
-        *   also be given as a wikitext string, for BC.
-        */
-       public function __construct( Page $page, ParserOptions $parserOptions,
-               $revid, $useParserCache, $content = null
-       ) {
-               if ( is_string( $content ) ) { // BC: old style call
-                       $modelId = $page->getRevision()->getContentModel();
-                       $format = $page->getRevision()->getContentFormat();
-                       $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
-               }
-
-               $this->page = $page;
-               $this->revid = $revid;
-               $this->cacheable = $useParserCache;
-               $this->parserOptions = $parserOptions;
-               $this->content = $content;
-               $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
-               parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
-       }
-
-       /**
-        * Get the ParserOutput from this object, or false in case of failure
-        *
-        * @return ParserOutput
-        */
-       public function getParserOutput() {
-               return $this->parserOutput;
-       }
-
-       /**
-        * Get whether the ParserOutput is a dirty one (i.e. expired)
-        *
-        * @return bool
-        */
-       public function getIsDirty() {
-               return $this->isDirty;
-       }
-
-       /**
-        * Get a Status object in case of error or false otherwise
-        *
-        * @return Status|bool
-        */
-       public function getError() {
-               return $this->error;
-       }
-
-       /**
-        * @return bool
-        */
-       public function doWork() {
-               global $wgUseFileCache;
-
-               // @todo several of the methods called on $this->page are not declared in Page, but present
-               //        in WikiPage and delegated by Article.
-
-               $isCurrent = $this->revid === $this->page->getLatest();
-
-               if ( $this->content !== null ) {
-                       $content = $this->content;
-               } elseif ( $isCurrent ) {
-                       // XXX: why use RAW audience here, and PUBLIC (default) below?
-                       $content = $this->page->getContent( Revision::RAW );
-               } else {
-                       $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
-
-                       if ( $rev === null ) {
-                               $content = null;
-                       } else {
-                               // XXX: why use PUBLIC audience here (default), and RAW above?
-                               $content = $rev->getContent();
-                       }
-               }
-
-               if ( $content === null ) {
-                       return false;
-               }
-
-               // Reduce effects of race conditions for slow parses (bug 46014)
-               $cacheTime = wfTimestampNow();
-
-               $time = - microtime( true );
-               $this->parserOutput = $content->getParserOutput(
-                       $this->page->getTitle(),
-                       $this->revid,
-                       $this->parserOptions
-               );
-               $time += microtime( true );
-
-               // Timing hack
-               if ( $time > 3 ) {
-                       wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
-                               $this->page->getTitle()->getPrefixedDBkey() ) );
-               }
-
-               if ( $this->cacheable && $this->parserOutput->isCacheable() && $isCurrent ) {
-                       ParserCache::singleton()->save(
-                               $this->parserOutput, $this->page, $this->parserOptions, $cacheTime, $this->revid );
-               }
-
-               // 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->parserOutput->isCacheable() || $this->parserOutput->containsOldMagic() ) {
-                       $wgUseFileCache = false;
-               }
-
-               if ( $isCurrent ) {
-                       $this->page->doCascadeProtectionUpdates( $this->parserOutput );
-               }
-
-               return true;
-       }
-
-       /**
-        * @return bool
-        */
-       public function getCachedWork() {
-               $this->parserOutput = ParserCache::singleton()->get( $this->page, $this->parserOptions );
-
-               if ( $this->parserOutput === false ) {
-                       wfDebug( __METHOD__ . ": parser cache miss\n" );
-                       return false;
-               } else {
-                       wfDebug( __METHOD__ . ": parser cache hit\n" );
-                       return true;
-               }
-       }
-
-       /**
-        * @return bool
-        */
-       public function fallback() {
-               $this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions );
-
-               if ( $this->parserOutput === false ) {
-                       wfDebugLog( 'dirty', 'dirty missing' );
-                       wfDebug( __METHOD__ . ": no dirty cache\n" );
-                       return false;
-               } else {
-                       wfDebug( __METHOD__ . ": sending dirty output\n" );
-                       wfDebugLog( 'dirty', "dirty output {$this->cacheKey}" );
-                       $this->isDirty = true;
-                       return true;
-               }
-       }
-
-       /**
-        * @param Status $status
-        * @return bool
-        */
-       public function error( $status ) {
-               $this->error = $status;
-               return false;
-       }
-}
diff --git a/includes/page/Article.php b/includes/page/Article.php
new file mode 100644 (file)
index 0000000..c68c675
--- /dev/null
@@ -0,0 +1,2100 @@
+<?php
+/**
+ * User interface for page actions.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Class for viewing MediaWiki article and history.
+ *
+ * This maintains WikiPage functions for backwards compatibility.
+ *
+ * @todo Move and rewrite code to an Action class
+ *
+ * See design.txt for an overview.
+ * Note: edit user interface and cache support functions have been
+ * moved to separate EditPage and HTMLFileCache classes.
+ *
+ * @internal documentation reviewed 15 Mar 2010
+ */
+class Article implements Page {
+       /** @var IContextSource The context this Article is executed in */
+       protected $mContext;
+
+       /** @var WikiPage The WikiPage object of this instance */
+       protected $mPage;
+
+       /** @var ParserOptions ParserOptions object for $wgUser articles */
+       public $mParserOptions;
+
+       /**
+        * @var string Text of the revision we are working on
+        * @todo BC cruft
+        */
+       public $mContent;
+
+       /**
+        * @var Content Content of the revision we are working on
+        * @since 1.21
+        */
+       protected $mContentObject;
+
+       /** @var bool Is the content ($mContent) already loaded? */
+       protected $mContentLoaded = false;
+
+       /** @var int|null The oldid of the article that is to be shown, 0 for the current revision */
+       protected $mOldId;
+
+       /** @var Title Title from which we were redirected here */
+       protected $mRedirectedFrom = null;
+
+       /** @var string|bool URL to redirect to or false if none */
+       protected $mRedirectUrl = false;
+
+       /** @var int Revision ID of revision we are working on */
+       protected $mRevIdFetched = 0;
+
+       /** @var Revision Revision we are working on */
+       protected $mRevision = null;
+
+       /** @var ParserOutput */
+       public $mParserOutput;
+
+       /**
+        * Constructor and clear the article
+        * @param Title $title Reference to a Title object.
+        * @param int $oldId Revision ID, null to fetch from request, zero for current
+        */
+       public function __construct( Title $title, $oldId = null ) {
+               $this->mOldId = $oldId;
+               $this->mPage = $this->newPage( $title );
+       }
+
+       /**
+        * @param Title $title
+        * @return WikiPage
+        */
+       protected function newPage( Title $title ) {
+               return new WikiPage( $title );
+       }
+
+       /**
+        * Constructor from a page id
+        * @param int $id Article ID to load
+        * @return Article|null
+        */
+       public static function newFromID( $id ) {
+               $t = Title::newFromID( $id );
+               # @todo FIXME: Doesn't inherit right
+               return $t == null ? null : new self( $t );
+               # return $t == null ? null : new static( $t ); // PHP 5.3
+       }
+
+       /**
+        * Create an Article object of the appropriate class for the given page.
+        *
+        * @param Title $title
+        * @param IContextSource $context
+        * @return Article
+        */
+       public static function newFromTitle( $title, IContextSource $context ) {
+               if ( NS_MEDIA == $title->getNamespace() ) {
+                       // FIXME: where should this go?
+                       $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
+               }
+
+               $page = null;
+               wfRunHooks( 'ArticleFromTitle', array( &$title, &$page, $context ) );
+               if ( !$page ) {
+                       switch ( $title->getNamespace() ) {
+                               case NS_FILE:
+                                       $page = new ImagePage( $title );
+                                       break;
+                               case NS_CATEGORY:
+                                       $page = new CategoryPage( $title );
+                                       break;
+                               default:
+                                       $page = new Article( $title );
+                       }
+               }
+               $page->setContext( $context );
+
+               return $page;
+       }
+
+       /**
+        * Create an Article object of the appropriate class for the given page.
+        *
+        * @param WikiPage $page
+        * @param IContextSource $context
+        * @return Article
+        */
+       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.
+        * @param Title $from
+        */
+       public function setRedirectedFrom( Title $from ) {
+               $this->mRedirectedFrom = $from;
+       }
+
+       /**
+        * Get the title object of the article
+        *
+        * @return Title Title object of this page
+        */
+       public function getTitle() {
+               return $this->mPage->getTitle();
+       }
+
+       /**
+        * Get the WikiPage object of this instance
+        *
+        * @since 1.19
+        * @return WikiPage
+        */
+       public function getPage() {
+               return $this->mPage;
+       }
+
+       /**
+        * Clear the object
+        */
+       public function clear() {
+               $this->mContentLoaded = false;
+
+               $this->mRedirectedFrom = null; # Title object if set
+               $this->mRevIdFetched = 0;
+               $this->mRedirectUrl = false;
+
+               $this->mPage->clear();
+       }
+
+       /**
+        * 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.
+        *
+        * @deprecated since 1.21; use WikiPage::getContent() instead
+        *
+        * @return string Return the text of this revision
+        */
+       public function getContent() {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+               $content = $this->getContentObject();
+               return ContentHandler::getContentText( $content );
+       }
+
+       /**
+        * Returns a Content object representing the pages effective display content,
+        * not necessarily the revision's 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 Content Return the content of this revision
+        *
+        * @since 1.21
+        */
+       protected function getContentObject() {
+               wfProfileIn( __METHOD__ );
+
+               if ( $this->mPage->getID() === 0 ) {
+                       # If this is a MediaWiki:x message, then load the messages
+                       # and return the message value for x.
+                       if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
+                               $text = $this->getTitle()->getDefaultMessageText();
+                               if ( $text === false ) {
+                                       $text = '';
+                               }
+
+                               $content = ContentHandler::makeContent( $text, $this->getTitle() );
+                       } else {
+                               $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
+                               $content = new MessageContent( $message, null, 'parsemag' );
+                       }
+               } else {
+                       $this->fetchContentObject();
+                       $content = $this->mContentObject;
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $content;
+       }
+
+       /**
+        * @return int The oldid of the article that is to be shown, 0 for the current revision
+        */
+       public function getOldID() {
+               if ( is_null( $this->mOldId ) ) {
+                       $this->mOldId = $this->getOldIDFromRequest();
+               }
+
+               return $this->mOldId;
+       }
+
+       /**
+        * Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect
+        *
+        * @return int The old id for the request
+        */
+       public function getOldIDFromRequest() {
+               $this->mRedirectUrl = false;
+
+               $request = $this->getContext()->getRequest();
+               $oldid = $request->getIntOrNull( 'oldid' );
+
+               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.
+                       if ( $oldid === $this->mPage->getLatest() ) {
+                               $this->mRevision = $this->mPage->getRevision();
+                       } else {
+                               $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 ( $request->getVal( 'direction' ) == 'next' ) {
+                       $nextid = $this->getTitle()->getNextRevisionID( $oldid );
+                       if ( $nextid ) {
+                               $oldid = $nextid;
+                               $this->mRevision = null;
+                       } else {
+                               $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
+                       }
+               } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
+                       $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
+                       if ( $previd ) {
+                               $oldid = $previd;
+                               $this->mRevision = null;
+                       }
+               }
+
+               return $oldid;
+       }
+
+       /**
+        * Load the revision (including text) into this object
+        *
+        * @deprecated since 1.19; use fetchContent()
+        */
+       function loadContent() {
+               wfDeprecated( __METHOD__, '1.19' );
+               $this->fetchContent();
+       }
+
+       /**
+        * Get text of an article from database
+        * Does *NOT* follow redirects.
+        *
+        * @protected
+        * @note This is really internal functionality that should really NOT be
+        * used by other functions. For accessing article content, use the WikiPage
+        * class, especially WikiBase::getContent(). However, a lot of legacy code
+        * uses this method to retrieve page text from the database, so the function
+        * has to remain public for now.
+        *
+        * @return string|bool String containing article contents, or false if null
+        * @deprecated since 1.21, use WikiPage::getContent() instead
+        */
+       function fetchContent() { #BC cruft!
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+
+               if ( $this->mContentLoaded && $this->mContent ) {
+                       return $this->mContent;
+               }
+
+               wfProfileIn( __METHOD__ );
+
+               $content = $this->fetchContentObject();
+
+               if ( !$content ) {
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               // @todo Get rid of mContent everywhere!
+               $this->mContent = ContentHandler::getContentText( $content );
+               ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+
+               wfProfileOut( __METHOD__ );
+
+               return $this->mContent;
+       }
+
+       /**
+        * Get text content object
+        * Does *NOT* follow redirects.
+        * @todo When is this null?
+        *
+        * @note Code that wants to retrieve page content from the database should
+        * use WikiPage::getContent().
+        *
+        * @return Content|null|bool
+        *
+        * @since 1.21
+        */
+       protected function fetchContentObject() {
+               if ( $this->mContentLoaded ) {
+                       return $this->mContentObject;
+               }
+
+               wfProfileIn( __METHOD__ );
+
+               $this->mContentLoaded = true;
+               $this->mContent = null;
+
+               $oldid = $this->getOldID();
+
+               # Pre-fill content with error message so that if something
+               # fails we'll have something telling us what we intended.
+               //XXX: this isn't page content but a UI message. horrible.
+               $this->mContentObject = new MessageContent( 'missing-revision', array( $oldid ), 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...
+               // Loads if user is allowed
+               $this->mContentObject = $this->mRevision->getContent(
+                       Revision::FOR_THIS_USER,
+                       $this->getContext()->getUser()
+               );
+               $this->mRevIdFetched = $this->mRevision->getId();
+
+               wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
+
+               wfProfileOut( __METHOD__ );
+
+               return $this->mContentObject;
+       }
+
+       /**
+        * Returns true if the currently-referenced revision is the current edit
+        * to this page (and it exists).
+        * @return bool
+        */
+       public function isCurrent() {
+               # If no oldid, this is the current version.
+               if ( $this->getOldID() == 0 ) {
+                       return true;
+               }
+
+               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
+        *
+        * @return int Revision ID of last article revision
+        */
+       public function getRevIdFetched() {
+               if ( $this->mRevIdFetched ) {
+                       return $this->mRevIdFetched;
+               } else {
+                       return $this->mPage->getLatest();
+               }
+       }
+
+       /**
+        * This is the default action of the index.php entry point: just view the
+        * page of the given title.
+        */
+       public function view() {
+               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();
+
+               $user = $this->getContext()->getUser();
+               # Another whitelist check in case getOldID() is altering the title
+               $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
+               if ( count( $permErrors ) ) {
+                       wfDebug( __METHOD__ . ": denied on secondary read check\n" );
+                       wfProfileOut( __METHOD__ );
+                       throw new PermissionsError( 'read', $permErrors );
+               }
+
+               $outputPage = $this->getContext()->getOutput();
+               # getOldID() may as well want us to redirect somewhere else
+               if ( $this->mRedirectUrl ) {
+                       $outputPage->redirect( $this->mRedirectUrl );
+                       wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
+                       wfProfileOut( __METHOD__ );
+
+                       return;
+               }
+
+               # If we got diff in the query, we want to see a diff page instead of the article.
+               if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
+                       wfDebug( __METHOD__ . ": showing diff page\n" );
+                       $this->showDiffPage();
+                       wfProfileOut( __METHOD__ );
+
+                       return;
+               }
+
+               # Set page title (may be overridden by DISPLAYTITLE)
+               $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
+
+               $outputPage->setArticleFlag( true );
+               # Allow frames by default
+               $outputPage->allowClickjacking();
+
+               $parserCache = ParserCache::singleton();
+
+               $parserOptions = $this->getParserOptions();
+               # Render printable version, use printable version cache
+               if ( $outputPage->isPrintable() ) {
+                       $parserOptions->setIsPrintable( true );
+                       $parserOptions->setEditSection( false );
+               } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
+                       $parserOptions->setEditSection( false );
+               }
+
+               # Try client and file cache
+               if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
+                       if ( $wgUseETag ) {
+                               $outputPage->setETag( $parserCache->getETag( $this, $parserOptions ) );
+                       }
+
+                       # Is it client cached?
+                       if ( $outputPage->checkLastModified( $this->mPage->getTouched() ) ) {
+                               wfDebug( __METHOD__ . ": done 304\n" );
+                               wfProfileOut( __METHOD__ );
+
+                               return;
+                       # Try file cache
+                       } elseif ( $wgUseFileCache && $this->tryFileCache() ) {
+                               wfDebug( __METHOD__ . ": done file cache\n" );
+                               # tell wgOut that output is taken care of
+                               $outputPage->disable();
+                               $this->mPage->doViewUpdates( $user, $oldid );
+                               wfProfileOut( __METHOD__ );
+
+                               return;
+                       }
+               }
+
+               # Should the parser cache be used?
+               $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
+               wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
+               if ( $user->getStubThreshold() ) {
+                       wfIncrStats( 'pcache_miss_stub' );
+               }
+
+               $this->showRedirectedFromHeader();
+               $this->showNamespaceHeader();
+
+               # Iterate through the possible ways of constructing the output text.
+               # Keep going until $outputDone is set, or we run out of things to do.
+               $pass = 0;
+               $outputDone = false;
+               $this->mParserOutput = false;
+
+               while ( !$outputDone && ++$pass ) {
+                       switch ( $pass ) {
+                               case 1:
+                                       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();
+                                               $this->mPage->doViewUpdates( $user );
+                                               wfProfileOut( __METHOD__ );
+                                               return;
+                                       }
+
+                                       # Try the parser cache
+                                       if ( $useParserCache ) {
+                                               $this->mParserOutput = $parserCache->get( $this, $parserOptions );
+
+                                               if ( $this->mParserOutput !== false ) {
+                                                       if ( $oldid ) {
+                                                               wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
+                                                               $this->setOldSubtitle( $oldid );
+                                                       } else {
+                                                               wfDebug( __METHOD__ . ": showing parser cache contents\n" );
+                                                       }
+                                                       $outputPage->addParserOutput( $this->mParserOutput );
+                                                       # Ensure that UI elements requiring revision ID have
+                                                       # the correct version information.
+                                                       $outputPage->setRevisionId( $this->mPage->getLatest() );
+                                                       # Preload timestamp to avoid a DB hit
+                                                       $cachedTimestamp = $this->mParserOutput->getTimestamp();
+                                                       if ( $cachedTimestamp !== null ) {
+                                                               $outputPage->setRevisionTimestamp( $cachedTimestamp );
+                                                               $this->mPage->setTimestamp( $cachedTimestamp );
+                                                       }
+                                                       $outputDone = true;
+                                               }
+                                       }
+                                       break;
+                               case 3:
+                                       # This will set $this->mRevision if needed
+                                       $this->fetchContentObject();
+
+                                       # Are we looking at an old revision
+                                       if ( $oldid && $this->mRevision ) {
+                                               $this->setOldSubtitle( $oldid );
+
+                                               if ( !$this->showDeletedRevisionHeader() ) {
+                                                       wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
+                                                       wfProfileOut( __METHOD__ );
+                                                       return;
+                                               }
+                                       }
+
+                                       # Ensure that UI elements requiring revision ID have
+                                       # the correct version information.
+                                       $outputPage->setRevisionId( $this->getRevIdFetched() );
+                                       # Preload timestamp to avoid a DB hit
+                                       $outputPage->setRevisionTimestamp( $this->getTimestamp() );
+
+                                       # Pages containing custom CSS or JavaScript get special treatment
+                                       if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
+                                               wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
+                                               $this->showCssOrJsPage();
+                                               $outputDone = true;
+                                       } elseif ( !wfRunHooks( 'ArticleContentViewCustom',
+                                                       array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+
+                                               # Allow extensions do their own custom view for certain pages
+                                               $outputDone = true;
+                                       } elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
+                                                       array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+
+                                               # Allow extensions do their own custom view for certain pages
+                                               $outputDone = true;
+                                       }
+                                       break;
+                               case 4:
+                                       # Run the parse, protected by a pool counter
+                                       wfDebug( __METHOD__ . ": doing uncached parse\n" );
+
+                                       $content = $this->getContentObject();
+                                       $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
+                                               $this->getRevIdFetched(), $useParserCache, $content );
+
+                                       if ( !$poolArticleView->execute() ) {
+                                               $error = $poolArticleView->getError();
+                                               if ( $error ) {
+                                                       $outputPage->clearHTML(); // for release() errors
+                                                       $outputPage->enableClientCache( false );
+                                                       $outputPage->setRobotPolicy( 'noindex,nofollow' );
+
+                                                       $errortext = $error->getWikiText( false, 'view-pool-error' );
+                                                       $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
+                                               }
+                                               # Connection or timeout error
+                                               wfProfileOut( __METHOD__ );
+                                               return;
+                                       }
+
+                                       $this->mParserOutput = $poolArticleView->getParserOutput();
+                                       $outputPage->addParserOutput( $this->mParserOutput );
+                                       if ( $content->getRedirectTarget() ) {
+                                               $outputPage->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
+                                       }
+
+                                       # Don't cache a dirty ParserOutput object
+                                       if ( $poolArticleView->getIsDirty() ) {
+                                               $outputPage->setSquidMaxage( 0 );
+                                               $outputPage->addHTML( "<!-- parser cache is expired, " .
+                                                       "sending anyway due to pool overload-->\n" );
+                                       }
+
+                                       $outputDone = true;
+                                       break;
+                               # Should be unreachable, but just in case...
+                               default:
+                                       break 2;
+                       }
+               }
+
+               # 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;
+
+               # 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()->isMainPage() ) {
+                       $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
+                       if ( !$msg->isDisabled() ) {
+                               $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
+                       }
+               }
+
+               # Check for any __NOINDEX__ tags on the page using $pOutput
+               $policy = $this->getRobotPolicy( 'view', $pOutput );
+               $outputPage->setIndexPolicy( $policy['index'] );
+               $outputPage->setFollowPolicy( $policy['follow'] );
+
+               $this->showViewFooter();
+               $this->mPage->doViewUpdates( $user, $oldid );
+
+               $outputPage->addModules( 'mediawiki.action.view.postEdit' );
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Adjust title for pages with displaytitle, -{T|}- or language conversion
+        * @param ParserOutput $pOutput
+        */
+       public function adjustDisplayTitle( ParserOutput $pOutput ) {
+               # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
+               $titleText = $pOutput->getTitleText();
+               if ( strval( $titleText ) !== '' ) {
+                       $this->getContext()->getOutput()->setPageTitle( $titleText );
+               }
+       }
+
+       /**
+        * Show a diff page according to current request variables. For use within
+        * Article::view() only, other callers should use the DifferenceEngine class.
+        *
+        * @todo Make protected
+        */
+       public function showDiffPage() {
+               $request = $this->getContext()->getRequest();
+               $user = $this->getContext()->getUser();
+               $diff = $request->getVal( 'diff' );
+               $rcid = $request->getVal( 'rcid' );
+               $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
+               $purge = $request->getVal( 'action' ) == 'purge';
+               $unhide = $request->getInt( 'unhide' ) == 1;
+               $oldid = $this->getOldID();
+
+               $rev = $this->getRevisionFetched();
+
+               if ( !$rev ) {
+                       $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
+                       $this->getContext()->getOutput()->addWikiMsg( 'difference-missing-revision', $oldid, 1 );
+                       return;
+               }
+
+               $contentHandler = $rev->getContentHandler();
+               $de = $contentHandler->createDifferenceEngine(
+                       $this->getContext(),
+                       $oldid,
+                       $diff,
+                       $rcid,
+                       $purge,
+                       $unhide
+               );
+
+               // DifferenceEngine directly fetched the revision:
+               $this->mRevIdFetched = $de->mNewid;
+               $de->showDiffPage( $diffOnly );
+
+               // Run view updates for the newer revision being diffed (and shown
+               // below the diff if not $diffOnly).
+               list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
+               // New can be false, convert it to 0 - this conveniently means the latest revision
+               $this->mPage->doViewUpdates( $user, (int)$new );
+       }
+
+       /**
+        * Show a page view for a page formatted as CSS or JavaScript. To be called by
+        * Article::view() only.
+        *
+        * This exists mostly to serve the deprecated ShowRawCssJs hook (used to customize these views).
+        * It has been replaced by the ContentGetParserOutput hook, which lets you do the same but with
+        * more flexibility.
+        *
+        * @param bool $showCacheHint Whether to show a message telling the user
+        *   to clear the browser cache (default: true).
+        */
+       protected function showCssOrJsPage( $showCacheHint = true ) {
+               $outputPage = $this->getContext()->getOutput();
+
+               if ( $showCacheHint ) {
+                       $dir = $this->getContext()->getLanguage()->getDir();
+                       $lang = $this->getContext()->getLanguage()->getCode();
+
+                       $outputPage->wrapWikiMsg(
+                               "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
+                               'clearyourcache'
+                       );
+               }
+
+               $this->fetchContentObject();
+
+               if ( $this->mContentObject ) {
+                       // Give hooks a chance to customise the output
+                       if ( ContentHandler::runLegacyHooks(
+                               'ShowRawCssJs',
+                               array( $this->mContentObject, $this->getTitle(), $outputPage ) )
+                       ) {
+                               // If no legacy hooks ran, display the content of the parser output, including RL modules,
+                               // but excluding metadata like categories and language links
+                               $po = $this->mContentObject->getParserOutput( $this->getTitle() );
+                               $outputPage->addParserOutputContent( $po );
+                       }
+               }
+       }
+
+       /**
+        * Get the robot policy to be used for the current view
+        * @param string $action The action= GET parameter
+        * @param ParserOutput|null $pOutput
+        * @return array The policy that should be set
+        * @todo: actions other than 'view'
+        */
+       public function getRobotPolicy( $action, $pOutput = null ) {
+               global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
+
+               $ns = $this->getTitle()->getNamespace();
+
+               # Don't index user and user talk pages for blocked users (bug 11443)
+               if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
+                       $specificTarget = null;
+                       $vagueTarget = null;
+                       $titleText = $this->getTitle()->getText();
+                       if ( IP::isValid( $titleText ) ) {
+                               $vagueTarget = $titleText;
+                       } else {
+                               $specificTarget = $titleText;
+                       }
+                       if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
+                               return array(
+                                       'index' => 'noindex',
+                                       'follow' => 'nofollow'
+                               );
+                       }
+               }
+
+               if ( $this->mPage->getID() === 0 || $this->getOldID() ) {
+                       # Non-articles (special pages etc), and old revisions
+                       return array(
+                               'index' => 'noindex',
+                               'follow' => 'nofollow'
+                       );
+               } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
+                       # Discourage indexing of printable versions, but encourage following
+                       return array(
+                               'index' => 'noindex',
+                               'follow' => 'follow'
+                       );
+               } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
+                       # For ?curid=x urls, disallow indexing
+                       return array(
+                               'index' => 'noindex',
+                               'follow' => 'follow'
+                       );
+               }
+
+               # Otherwise, construct the policy based on the various config variables.
+               $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
+
+               if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
+                       # Honour customised robot policies for this namespace
+                       $policy = array_merge(
+                               $policy,
+                               self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
+                       );
+               }
+               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' => $pOutput->getIndexPolicy() )
+                       );
+               }
+
+               if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
+                       # (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
+                       $policy = array_merge(
+                               $policy,
+                               self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
+                       );
+               }
+
+               return $policy;
+       }
+
+       /**
+        * Converts a String robot policy into an associative array, to allow
+        * merging of several policies using array_merge().
+        * @param array|string $policy Returns empty array on null/false/'', transparent
+        *   to already-converted arrays, converts string.
+        * @return array 'index' => \<indexpolicy\>, 'follow' => \<followpolicy\>
+        */
+       public static function formatRobotPolicy( $policy ) {
+               if ( is_array( $policy ) ) {
+                       return $policy;
+               } elseif ( !$policy ) {
+                       return array();
+               }
+
+               $policy = explode( ',', $policy );
+               $policy = array_map( 'trim', $policy );
+
+               $arr = array();
+               foreach ( $policy as $var ) {
+                       if ( in_array( $var, array( 'index', 'noindex' ) ) ) {
+                               $arr['index'] = $var;
+                       } elseif ( in_array( $var, array( 'follow', 'nofollow' ) ) ) {
+                               $arr['follow'] = $var;
+                       }
+               }
+
+               return $arr;
+       }
+
+       /**
+        * If this request is a redirect view, send "redirected from" subtitle to
+        * the output. Returns true if the header was needed, false if this is not
+        * a redirect view. Handles both local and remote redirects.
+        *
+        * @return bool
+        */
+       public function showRedirectedFromHeader() {
+               global $wgRedirectSources;
+               $outputPage = $this->getContext()->getOutput();
+
+               $rdfrom = $this->getContext()->getRequest()->getVal( 'rdfrom' );
+
+               if ( isset( $this->mRedirectedFrom ) ) {
+                       // 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::linkKnown(
+                                       $this->mRedirectedFrom,
+                                       null,
+                                       array(),
+                                       array( 'redirect' => 'no' )
+                               );
+
+                               $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
+
+                               // Set the fragment if one was specified in the redirect
+                               if ( $this->getTitle()->hasFragment() ) {
+                                       $outputPage->addJsConfigVars( 'wgRedirectToFragment', $this->getTitle()->getFragmentForURL() );
+                                       $outputPage->addModules( 'mediawiki.action.view.redirectToFragment' );
+                               }
+
+                               // Add a <link rel="canonical"> tag
+                               $outputPage->setCanonicalUrl( $this->getTitle()->getLocalURL() );
+
+                               // Tell the output object that the user arrived at this article through a redirect
+                               $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
+
+                               return true;
+                       }
+               } elseif ( $rdfrom ) {
+                       // This is an externally redirected view, from some other wiki.
+                       // If it was reported from a trusted site, supply a backlink.
+                       if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
+                               $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
+                               $outputPage->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
+
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Show a header specific to the namespace currently being viewed, like
+        * [[MediaWiki:Talkpagetext]]. For Article::view().
+        */
+       public function showNamespaceHeader() {
+               if ( $this->getTitle()->isTalkPage() ) {
+                       if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
+                               $this->getContext()->getOutput()->wrapWikiMsg(
+                                       "<div class=\"mw-talkpageheader\">\n$1\n</div>",
+                                       array( 'talkpageheader' )
+                               );
+                       }
+               }
+       }
+
+       /**
+        * Show the footer section of an ordinary page view
+        */
+       public function showViewFooter() {
+               # 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() )
+               ) {
+                       $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
+               }
+
+               // Show a footer allowing the user to patrol the shown revision or page if possible
+               $patrolFooterShown = $this->showPatrolFooter();
+
+               wfRunHooks( 'ArticleViewFooter', array( $this, $patrolFooterShown ) );
+       }
+
+       /**
+        * If patrol is possible, output a patrol UI box. This is called from the
+        * footer section of ordinary page views. If patrol is not possible or not
+        * desired, does nothing.
+        * Side effect: When the patrol link is build, this method will call
+        * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
+        *
+        * @return bool
+        */
+       public function showPatrolFooter() {
+               global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI;
+
+               $outputPage = $this->getContext()->getOutput();
+               $user = $this->getContext()->getUser();
+               $cache = wfGetMainCache();
+               $rc = false;
+
+               if ( !$this->getTitle()->quickUserCan( 'patrol', $user )
+                       || !( $wgUseRCPatrol || $wgUseNPPatrol )
+               ) {
+                       // Patrolling is disabled or the user isn't allowed to
+                       return false;
+               }
+
+               wfProfileIn( __METHOD__ );
+
+               // New page patrol: Get the timestamp of the oldest revison which
+               // the revision table holds for the given page. Then we look
+               // whether it's within the RC lifespan and if it is, we try
+               // to get the recentchanges row belonging to that entry
+               // (with rc_new = 1).
+
+               // Check for cached results
+               if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) {
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               if ( $this->mRevision
+                       && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
+               ) {
+                       // The current revision is already older than what could be in the RC table
+                       // 6h tolerance because the RC might not be cleaned out regularly
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $oldestRevisionTimestamp = $dbr->selectField(
+                       'revision',
+                       'MIN( rev_timestamp )',
+                       array( 'rev_page' => $this->getTitle()->getArticleID() ),
+                       __METHOD__
+               );
+
+               if ( $oldestRevisionTimestamp
+                       && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
+               ) {
+                       // 6h tolerance because the RC might not be cleaned out regularly
+                       $rc = RecentChange::newFromConds(
+                               array(
+                                       'rc_new' => 1,
+                                       'rc_timestamp' => $oldestRevisionTimestamp,
+                                       'rc_namespace' => $this->getTitle()->getNamespace(),
+                                       'rc_cur_id' => $this->getTitle()->getArticleID(),
+                                       'rc_patrolled' => 0
+                               ),
+                               __METHOD__,
+                               array( 'USE INDEX' => 'new_name_timestamp' )
+                       );
+               }
+
+               if ( !$rc ) {
+                       // No RC entry around
+
+                       // Cache the information we gathered above in case we can't patrol
+                       // Don't cache in case we can patrol as this could change
+                       $cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' );
+
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               if ( $rc->getPerformer()->getName() == $user->getName() ) {
+                       // Don't show a patrol link for own creations. If the user could
+                       // patrol them, they already would be patrolled
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+
+               $rcid = $rc->getAttribute( 'rc_id' );
+
+               $token = $user->getEditToken( $rcid );
+
+               $outputPage->preventClickjacking();
+               if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
+                       $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
+               }
+
+               $link = Linker::linkKnown(
+                       $this->getTitle(),
+                       wfMessage( 'markaspatrolledtext' )->escaped(),
+                       array(),
+                       array(
+                               'action' => 'markpatrolled',
+                               'rcid' => $rcid,
+                               'token' => $token,
+                       )
+               );
+
+               $outputPage->addHTML(
+                       "<div class='patrollink'>" .
+                               wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
+                       '</div>'
+               );
+
+               wfProfileOut( __METHOD__ );
+               return true;
+       }
+
+       /**
+        * Show the error text for a missing article. For articles in the MediaWiki
+        * namespace, show the default message text. To be called from Article::view().
+        */
+       public function showMissingArticle() {
+               global $wgSend404Code;
+               $outputPage = $this->getContext()->getOutput();
+               // Whether the page is a root user page of an existing user (but not a subpage)
+               $validUserPage = false;
+
+               # 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
+               ) {
+                       $parts = explode( '/', $this->getTitle()->getText() );
+                       $rootPart = $parts[0];
+                       $user = User::newFromName( $rootPart, false /* allow IP users*/ );
+                       $ip = User::isIP( $rootPart );
+
+                       if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
+                               $outputPage->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
+                               LogEventsList::showLogExtract(
+                                       $outputPage,
+                                       'block',
+                                       $user->getUserPage(),
+                                       '',
+                                       array(
+                                               'lim' => 1,
+                                               'showIfEmpty' => false,
+                                               'msgKey' => array(
+                                                       'blocked-notice-logextract',
+                                                       $user->getName() # Support GENDER in notice
+                                               )
+                                       )
+                               );
+                               $validUserPage = !$this->getTitle()->isSubpage();
+                       } else {
+                               $validUserPage = !$this->getTitle()->isSubpage();
+                       }
+               }
+
+               wfRunHooks( 'ShowMissingArticle', array( $this ) );
+
+               // Give extensions a chance to hide their (unrelated) log entries
+               $logTypes = array( 'delete', 'move' );
+               $conds = array( "log_action != 'revision'" );
+               wfRunHooks( 'Article::MissingArticleConditions', array( &$conds, $logTypes ) );
+
+               # Show delete and move logs
+               LogEventsList::showLogExtract( $outputPage, $logTypes, $this->getTitle(), '',
+                       array( 'lim' => 10,
+                               'conds' => $conds,
+                               'showIfEmpty' => false,
+                               'msgKey' => array( 'moveddeleted-notice' ) )
+               );
+
+               if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
+                       // If there's no backing content, send a 404 Not Found
+                       // for better machine handling of broken links.
+                       $this->getContext()->getRequest()->response()->header( "HTTP/1.1 404 Not Found" );
+               }
+
+               if ( $validUserPage ) {
+                       // Also apply the robot policy for nonexisting user pages (as those aren't served as 404)
+                       $policy = $this->getRobotPolicy( 'view' );
+                       $outputPage->setIndexPolicy( $policy['index'] );
+                       $outputPage->setFollowPolicy( $policy['follow'] );
+               }
+
+               $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
+
+               if ( ! $hookResult ) {
+                       return;
+               }
+
+               # Show error message
+               $oldid = $this->getOldID();
+               if ( $oldid ) {
+                       $text = wfMessage( 'missing-revision', $oldid )->plain();
+               } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) {
+                       // Use the default message text
+                       $text = $this->getTitle()->getDefaultMessageText();
+               } elseif ( $this->getTitle()->quickUserCan( 'create', $this->getContext()->getUser() )
+                       && $this->getTitle()->quickUserCan( 'edit', $this->getContext()->getUser() )
+               ) {
+                       $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
+                       $text = wfMessage( $message )->plain();
+               } else {
+                       $text = wfMessage( 'noarticletext-nopermission' )->plain();
+               }
+               $text = "<div class='noarticletext'>\n$text\n</div>";
+
+               $outputPage->addWikiText( $text );
+       }
+
+       /**
+        * If the revision requested for view is deleted, check permissions.
+        * Send either an error message or a warning header to the output.
+        *
+        * @return bool true if the view is allowed, false if not.
+        */
+       public function showDeletedRevisionHeader() {
+               if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
+                       // Not deleted
+                       return true;
+               }
+
+               $outputPage = $this->getContext()->getOutput();
+               $user = $this->getContext()->getUser();
+               // If the user is not allowed to see it...
+               if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
+                       $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+                               'rev-deleted-text-permission' );
+
+                       return false;
+               // If the user needs to confirm that they want to see it...
+               } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
+                       # Give explanation and add a link to view the revision...
+                       $oldid = intval( $this->getOldID() );
+                       $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
+                       $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
+                               'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
+                       $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+                               array( $msg, $link ) );
+
+                       return false;
+               // We are allowed to see...
+               } else {
+                       $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
+                               'rev-suppressed-text-view' : 'rev-deleted-text-view';
+                       $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
+
+                       return true;
+               }
+       }
+
+       /**
+        * 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 int $oldid Revision ID of this article revision
+        */
+       public function setOldSubtitle( $oldid = 0 ) {
+               if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
+                       return;
+               }
+
+               $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1;
+
+               # Cascade unhide param in links for easy deletion browsing
+               $extraParams = array();
+               if ( $unhide ) {
+                       $extraParams['unhide'] = 1;
+               }
+
+               if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
+                       $revision = $this->mRevision;
+               } else {
+                       $revision = Revision::newFromId( $oldid );
+               }
+
+               $timestamp = $revision->getTimestamp();
+
+               $current = ( $oldid == $this->mPage->getLatest() );
+               $language = $this->getContext()->getLanguage();
+               $user = $this->getContext()->getUser();
+
+               $td = $language->userTimeAndDate( $timestamp, $user );
+               $tddate = $language->userDate( $timestamp, $user );
+               $tdtime = $language->userTime( $timestamp, $user );
+
+               # 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';
+
+               $outputPage = $this->getContext()->getOutput();
+               $outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
+                       $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
+                       $tdtime, $revision->getUser() )->rawParams( Linker::revComment( $revision, true, true ) )->parse() . "</div>" );
+
+               $lnk = $current
+                       ? wfMessage( 'currentrevisionlink' )->escaped()
+                       : Linker::linkKnown(
+                               $this->getTitle(),
+                               wfMessage( 'currentrevisionlink' )->escaped(),
+                               array(),
+                               $extraParams
+                       );
+               $curdiff = $current
+                       ? wfMessage( 'diff' )->escaped()
+                       : Linker::linkKnown(
+                               $this->getTitle(),
+                               wfMessage( 'diff' )->escaped(),
+                               array(),
+                               array(
+                                       'diff' => 'cur',
+                                       'oldid' => $oldid
+                               ) + $extraParams
+                       );
+               $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
+               $prevlink = $prev
+                       ? Linker::linkKnown(
+                               $this->getTitle(),
+                               wfMessage( 'previousrevision' )->escaped(),
+                               array(),
+                               array(
+                                       'direction' => 'prev',
+                                       'oldid' => $oldid
+                               ) + $extraParams
+                       )
+                       : wfMessage( 'previousrevision' )->escaped();
+               $prevdiff = $prev
+                       ? Linker::linkKnown(
+                               $this->getTitle(),
+                               wfMessage( 'diff' )->escaped(),
+                               array(),
+                               array(
+                                       'diff' => 'prev',
+                                       'oldid' => $oldid
+                               ) + $extraParams
+                       )
+                       : wfMessage( 'diff' )->escaped();
+               $nextlink = $current
+                       ? wfMessage( 'nextrevision' )->escaped()
+                       : Linker::linkKnown(
+                               $this->getTitle(),
+                               wfMessage( 'nextrevision' )->escaped(),
+                               array(),
+                               array(
+                                       'direction' => 'next',
+                                       'oldid' => $oldid
+                               ) + $extraParams
+                       );
+               $nextdiff = $current
+                       ? wfMessage( 'diff' )->escaped()
+                       : Linker::linkKnown(
+                               $this->getTitle(),
+                               wfMessage( 'diff' )->escaped(),
+                               array(),
+                               array(
+                                       'diff' => 'next',
+                                       'oldid' => $oldid
+                               ) + $extraParams
+                       );
+
+               $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
+               if ( $cdel !== '' ) {
+                       $cdel .= ' ';
+               }
+
+               $outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
+                       wfMessage( 'revision-nav' )->rawParams(
+                               $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
+                       )->escaped() . "</div>" );
+       }
+
+       /**
+        * Return the HTML for the top of a redirect page
+        *
+        * Chances are you should just be using the ParserOutput from
+        * WikitextContent::getParserOutput instead of calling this for redirects.
+        *
+        * @param Title|array $target Destination(s) to redirect
+        * @param bool $appendSubtitle [optional]
+        * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
+        * @return string Containing HMTL with redirect link
+        */
+       public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
+               $lang = $this->getTitle()->getPageLanguage();
+               if ( $appendSubtitle ) {
+                       $out = $this->getContext()->getOutput();
+                       $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
+               }
+               return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
+       }
+
+       /**
+        * Return the HTML for the top of a redirect page
+        *
+        * Chances are you should just be using the ParserOutput from
+        * WikitextContent::getParserOutput instead of calling this for redirects.
+        *
+        * @since 1.23
+        * @param Language $lang
+        * @param Title|array $target Destination(s) to redirect
+        * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
+        * @return string Containing HMTL with redirect link
+        */
+       public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
+               global $wgStylePath;
+
+               if ( !is_array( $target ) ) {
+                       $target = array( $target );
+               }
+
+               $imageDir = $lang->getDir();
+
+               // 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 ) {
+                       $link = Linker::linkKnown( $title, htmlspecialchars( $title->getFullText() ) );
+               } else {
+                       $link = Linker::link( $title, htmlspecialchars( $title->getFullText() ) );
+               }
+
+               $nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
+               $alt = $lang->isRTL() ? '←' : '→';
+
+               // Automatically append redirect=no to each link, since most of them are
+               // redirect pages themselves.
+               /** @var Title $rt */
+               foreach ( $target as $rt ) {
+                       $link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) );
+                       if ( $forceKnown ) {
+                               $link .= Linker::linkKnown(
+                                       $rt,
+                                       htmlspecialchars( $rt->getFullText(),
+                                       array(),
+                                       array( 'redirect' => 'no' )
+                               )
+                               );
+                       } else {
+                               $link .= Linker::link(
+                                       $rt,
+                                       htmlspecialchars( $rt->getFullText() ),
+                                       array(),
+                                       array( 'redirect' => 'no' )
+                               );
+                       }
+               }
+
+               $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
+               return '<div class="redirectMsg">' .
+                       Html::element( 'img', array( 'src' => $imageUrl, 'alt' => '#REDIRECT' ) ) .
+                       '<span class="redirectText">' . $link . '</span></div>';
+       }
+
+       /**
+        * Handle action=render
+        */
+       public function render() {
+               $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
+               $this->getContext()->getOutput()->setArticleBodyOnly( true );
+               $this->getContext()->getOutput()->enableSectionEditLinks( false );
+               $this->view();
+       }
+
+       /**
+        * action=protect handler
+        */
+       public function protect() {
+               $form = new ProtectionForm( $this );
+               $form->execute();
+       }
+
+       /**
+        * action=unprotect handler (alias)
+        */
+       public function unprotect() {
+               $this->protect();
+       }
+
+       /**
+        * UI entry point for page deletion
+        */
+       public function delete() {
+               # This code desperately needs to be totally rewritten
+
+               $title = $this->getTitle();
+               $user = $this->getContext()->getUser();
+
+               # Check permissions
+               $permission_errors = $title->getUserPermissionsErrors( 'delete', $user );
+               if ( count( $permission_errors ) ) {
+                       throw new PermissionsError( 'delete', $permission_errors );
+               }
+
+               # Read-only check...
+               if ( wfReadOnly() ) {
+                       throw new ReadOnlyError;
+               }
+
+               # Better double-check that it hasn't been deleted yet!
+               $this->mPage->loadPageData( 'fromdbmaster' );
+               if ( !$this->mPage->exists() ) {
+                       $deleteLogPage = new LogPage( 'delete' );
+                       $outputPage = $this->getContext()->getOutput();
+                       $outputPage->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) );
+                       $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
+                                       array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) )
+                               );
+                       $outputPage->addHTML(
+                               Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
+                       );
+                       LogEventsList::showLogExtract(
+                               $outputPage,
+                               'delete',
+                               $title
+                       );
+
+                       return;
+               }
+
+               $request = $this->getContext()->getRequest();
+               $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
+               $deleteReason = $request->getText( 'wpReason' );
+
+               if ( $deleteReasonList == 'other' ) {
+                       $reason = $deleteReason;
+               } elseif ( $deleteReason != '' ) {
+                       // Entry from drop down menu + additional comment
+                       $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
+                       $reason = $deleteReasonList . $colonseparator . $deleteReason;
+               } else {
+                       $reason = $deleteReasonList;
+               }
+
+               if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
+                       array( 'delete', $this->getTitle()->getPrefixedText() ) )
+               ) {
+                       # Flag to hide all contents of the archived revisions
+                       $suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
+
+                       $this->doDelete( $reason, $suppress );
+
+                       WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
+
+                       return;
+               }
+
+               // Generate deletion reason
+               $hasHistory = false;
+               if ( !$reason ) {
+                       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 ) {
+                       $revisions = $this->mTitle->estimateRevisionCount();
+                       // @todo FIXME: i18n issue/patchwork message
+                       $this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' .
+                               wfMessage( 'historywarning' )->numParams( $revisions )->parse() .
+                               wfMessage( 'word-separator' )->plain() . Linker::linkKnown( $title,
+                                       wfMessage( 'history' )->escaped(),
+                                       array( 'rel' => 'archives' ),
+                                       array( 'action' => 'history' ) ) .
+                               '</strong>'
+                       );
+
+                       if ( $this->mTitle->isBigDeletion() ) {
+                               global $wgDeleteRevisionsLimit;
+                               $this->getContext()->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
+                                       array(
+                                               'delete-warning-toobig',
+                                               $this->getContext()->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
+                                       )
+                               );
+                       }
+               }
+
+               $this->confirmDelete( $reason );
+       }
+
+       /**
+        * Output deletion confirmation dialog
+        * @todo FIXME: Move to another file?
+        * @param string $reason Prefilled reason
+        */
+       public function confirmDelete( $reason ) {
+               wfDebug( "Article::confirmDelete\n" );
+
+               $outputPage = $this->getContext()->getOutput();
+               $outputPage->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
+               $outputPage->addBacklinkSubtitle( $this->getTitle() );
+               $outputPage->setRobotPolicy( 'noindex,nofollow' );
+               $backlinkCache = $this->getTitle()->getBacklinkCache();
+               if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
+                       $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
+                               'deleting-backlinks-warning' );
+               }
+               $outputPage->addWikiMsg( 'confirmdeletetext' );
+
+               wfRunHooks( 'ArticleConfirmDelete', array( $this, $outputPage, &$reason ) );
+
+               $user = $this->getContext()->getUser();
+
+               if ( $user->isAllowed( 'suppressrevision' ) ) {
+                       $suppress = "<tr id=\"wpDeleteSuppressRow\">
+                                       <td></td>
+                                       <td class='mw-input'><strong>" .
+                                               Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
+                                                       'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
+                                       "</strong></td>
+                               </tr>";
+               } else {
+                       $suppress = '';
+               }
+               $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $this->getTitle() );
+
+               $form = Xml::openElement( 'form', array( 'method' => 'post',
+                       'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
+                       Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
+                       Xml::tags( 'legend', null, wfMessage( 'delete-legend' )->escaped() ) .
+                       Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
+                       "<tr id=\"wpDeleteReasonListRow\">
+                               <td class='mw-label'>" .
+                                       Xml::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
+                               "</td>
+                               <td class='mw-input'>" .
+                                       Xml::listDropDown(
+                                               'wpDeleteReasonList',
+                                               wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
+                                               wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
+                                               '',
+                                               'wpReasonDropDown',
+                                               1
+                                       ) .
+                               "</td>
+                       </tr>
+                       <tr id=\"wpDeleteReasonRow\">
+                               <td class='mw-label'>" .
+                                       Xml::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
+                               "</td>
+                               <td class='mw-input'>" .
+                               Html::input( 'wpReason', $reason, 'text', array(
+                                       'size' => '60',
+                                       'maxlength' => '255',
+                                       'tabindex' => '2',
+                                       'id' => 'wpReason',
+                                       'autofocus'
+                               ) ) .
+                               "</td>
+                       </tr>";
+
+               # Disallow watching if user is not logged in
+               if ( $user->isLoggedIn() ) {
+                       $form .= "
+                       <tr>
+                               <td></td>
+                               <td class='mw-input'>" .
+                                       Xml::checkLabel( wfMessage( 'watchthis' )->text(),
+                                               'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
+                               "</td>
+                       </tr>";
+               }
+
+               $form .= "
+                       $suppress
+                       <tr>
+                               <td></td>
+                               <td class='mw-submit'>" .
+                                       Xml::submitButton( wfMessage( 'deletepage' )->text(),
+                                               array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
+                               "</td>
+                       </tr>" .
+                       Xml::closeElement( 'table' ) .
+                       Xml::closeElement( 'fieldset' ) .
+                       Html::hidden(
+                               'wpEditToken',
+                               $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) )
+                       ) .
+                       Xml::closeElement( 'form' );
+
+                       if ( $user->isAllowed( 'editinterface' ) ) {
+                               $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
+                               $link = Linker::link(
+                                       $title,
+                                       wfMessage( 'delete-edit-reasonlist' )->escaped(),
+                                       array(),
+                                       array( 'action' => 'edit' )
+                               );
+                               $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
+                       }
+
+               $outputPage->addHTML( $form );
+
+               $deleteLogPage = new LogPage( 'delete' );
+               $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
+               LogEventsList::showLogExtract( $outputPage, 'delete',
+                       $this->getTitle()
+               );
+       }
+
+       /**
+        * Perform a deletion and output success or failure messages
+        * @param string $reason
+        * @param bool $suppress
+        */
+       public function doDelete( $reason, $suppress = false ) {
+               $error = '';
+               $outputPage = $this->getContext()->getOutput();
+               $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error );
+
+               if ( $status->isGood() ) {
+                       $deleted = $this->getTitle()->getPrefixedText();
+
+                       $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
+                       $outputPage->setRobotPolicy( 'noindex,nofollow' );
+
+                       $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
+
+                       $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
+                       $outputPage->returnToMain( false );
+               } else {
+                       $outputPage->setPageTitle(
+                               wfMessage( 'cannotdelete-title',
+                                       $this->getTitle()->getPrefixedText() )
+                       );
+
+                       if ( $error == '' ) {
+                               $outputPage->addWikiText(
+                                       "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
+                               );
+                               $deleteLogPage = new LogPage( 'delete' );
+                               $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
+
+                               LogEventsList::showLogExtract(
+                                       $outputPage,
+                                       'delete',
+                                       $this->getTitle()
+                               );
+                       } else {
+                               $outputPage->addHTML( $error );
+                       }
+               }
+       }
+
+       /* Caching functions */
+
+       /**
+        * 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 bool true if cached version send, false otherwise
+        */
+       protected function tryFileCache() {
+               static $called = false;
+
+               if ( $called ) {
+                       wfDebug( "Article::tryFileCache(): called twice!?\n" );
+                       return false;
+               }
+
+               $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' ) );
+                       }
+               } else {
+                       wfDebug( "Article::tryFileCache(): not cacheable\n" );
+               }
+
+               return false;
+       }
+
+       /**
+        * 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 ) );
+                       }
+               }
+
+               return $cacheable;
+       }
+
+       /**#@-*/
+
+       /**
+        * 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 int|null $oldid Revision ID or null
+        * @param User $user The relevant user
+        * @return ParserOutput|bool ParserOutput or false if the given revision ID is not found
+        */
+       public function getParserOutput( $oldid = null, User $user = null ) {
+               //XXX: bypasses mParserOptions and thus setParserOptions()
+
+               if ( $user === null ) {
+                       $parserOptions = $this->getParserOptions();
+               } else {
+                       $parserOptions = $this->mPage->makeParserOptions( $user );
+               }
+
+               return $this->mPage->getParserOutput( $parserOptions, $oldid );
+       }
+
+       /**
+        * Override the ParserOptions used to render the primary article wikitext.
+        *
+        * @param ParserOptions $options
+        * @throws MWException if the parser options where already initialized.
+        */
+       public function setParserOptions( ParserOptions $options ) {
+               if ( $this->mParserOptions ) {
+                       throw new MWException( "can't change parser options after they have already been set" );
+               }
+
+               // clone, so if $options is modified later, it doesn't confuse the parser cache.
+               $this->mParserOptions = clone $options;
+       }
+
+       /**
+        * Get parser options suitable for rendering the primary article wikitext
+        * @return ParserOptions
+        */
+       public function getParserOptions() {
+               if ( !$this->mParserOptions ) {
+                       $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
+               }
+               // Clone to allow modifications of the return value without affecting cache
+               return clone $this->mParserOptions;
+       }
+
+       /**
+        * Sets the context this Article is executed in
+        *
+        * @param IContextSource $context
+        * @since 1.18
+        */
+       public function setContext( $context ) {
+               $this->mContext = $context;
+       }
+
+       /**
+        * Gets the context this Article is executed in
+        *
+        * @return IContextSource
+        * @since 1.18
+        */
+       public function getContext() {
+               if ( $this->mContext instanceof IContextSource ) {
+                       return $this->mContext;
+               } else {
+                       wfDebug( __METHOD__ . " called and \$mContext is null. " .
+                               "Return RequestContext::getMain(); for sanity\n" );
+                       return RequestContext::getMain();
+               }
+       }
+
+       /**
+        * Use PHP's magic __get handler to handle accessing of
+        * raw WikiPage fields for backwards compatibility.
+        *
+        * @param string $fname Field name
+        */
+       public function __get( $fname ) {
+               if ( property_exists( $this->mPage, $fname ) ) {
+                       #wfWarn( "Access to raw $fname field " . __CLASS__ );
+                       return $this->mPage->$fname;
+               }
+               trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
+       }
+
+       /**
+        * Use PHP's magic __set handler to handle setting of
+        * raw WikiPage fields for backwards compatibility.
+        *
+        * @param string $fname Field name
+        * @param mixed $fvalue New value
+        */
+       public function __set( $fname, $fvalue ) {
+               if ( property_exists( $this->mPage, $fname ) ) {
+                       #wfWarn( "Access to raw $fname field of " . __CLASS__ );
+                       $this->mPage->$fname = $fvalue;
+               // Note: extensions may want to toss on new fields
+               } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
+                       $this->mPage->$fname = $fvalue;
+               } else {
+                       trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
+               }
+       }
+
+       /**
+        * Use PHP's magic __call handler to transform instance calls to
+        * WikiPage functions for backwards compatibility.
+        *
+        * @param string $fname Name of called method
+        * @param array $args Arguments to the method
+        * @return mixed
+        */
+       public function __call( $fname, $args ) {
+               if ( is_callable( array( $this->mPage, $fname ) ) ) {
+                       #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 );
+       }
+
+       // ****** B/C functions to work-around PHP silliness with __call and references ****** //
+
+       /**
+        * @param array $limit
+        * @param array $expiry
+        * @param bool $cascade
+        * @param string $reason
+        * @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 array $limit
+        * @param string $reason
+        * @param int $cascade
+        * @param array $expiry
+        * @return bool
+        */
+       public function updateRestrictions( $limit = array(), $reason = '',
+               &$cascade = 0, $expiry = array()
+       ) {
+               return $this->mPage->doUpdateRestrictions(
+                       $limit,
+                       $expiry,
+                       $cascade,
+                       $reason,
+                       $this->getContext()->getUser()
+               );
+       }
+
+       /**
+        * @param string $reason
+        * @param bool $suppress
+        * @param int $id
+        * @param bool $commit
+        * @param string $error
+        * @return bool
+        */
+       public function doDeleteArticle( $reason, $suppress = false, $id = 0,
+               $commit = true, &$error = ''
+       ) {
+               return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
+       }
+
+       /**
+        * @param string $fromP
+        * @param string $summary
+        * @param string $token
+        * @param bool $bot
+        * @param array $resultDetails
+        * @param User|null $user
+        * @return array
+        */
+       public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
+               $user = is_null( $user ) ? $this->getContext()->getUser() : $user;
+               return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
+       }
+
+       /**
+        * @param string $fromP
+        * @param string $summary
+        * @param bool $bot
+        * @param array $resultDetails
+        * @param User|null $guser
+        * @return array
+        */
+       public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
+               $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser;
+               return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
+       }
+
+       /**
+        * @param bool $hasHistory
+        * @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 ) {
+               WikiPage::onArticleCreate( $title );
+       }
+
+       /**
+        * @param Title $title
+        */
+       public static function onArticleDelete( $title ) {
+               WikiPage::onArticleDelete( $title );
+       }
+
+       /**
+        * @param Title $title
+        */
+       public static function onArticleEdit( $title ) {
+               WikiPage::onArticleEdit( $title );
+       }
+
+       /**
+        * @param string $oldtext
+        * @param string $newtext
+        * @param int $flags
+        * @return string
+        * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
+        */
+       public static function getAutosummary( $oldtext, $newtext, $flags ) {
+               return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
+       }
+       // ******
+}
diff --git a/includes/page/CategoryPage.php b/includes/page/CategoryPage.php
new file mode 100644 (file)
index 0000000..9abc6a8
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Special handling for category description pages.
+ * Modelled after ImagePage.php.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Special handling for category description pages, showing pages,
+ * subcategories and file that belong to the category
+ */
+class CategoryPage extends Article {
+       # Subclasses can change this to override the viewer class.
+       protected $mCategoryViewerClass = 'CategoryViewer';
+
+       /**
+        * @param Title $title
+        * @return WikiCategoryPage
+        */
+       protected function newPage( Title $title ) {
+               // Overload mPage with a category-specific page
+               return new WikiCategoryPage( $title );
+       }
+
+       /**
+        * Constructor from a page id
+        * @param int $id Article ID to load
+        * @return CategoryPage|null
+        */
+       public static function newFromID( $id ) {
+               $t = Title::newFromID( $id );
+               # @todo FIXME: Doesn't inherit right
+               return $t == null ? null : new self( $t );
+               # return $t == null ? null : new static( $t ); // PHP 5.3
+       }
+
+       function view() {
+               $request = $this->getContext()->getRequest();
+               $diff = $request->getVal( 'diff' );
+               $diffOnly = $request->getBool( 'diffonly',
+                       $this->getContext()->getUser()->getOption( 'diffonly' ) );
+
+               if ( $diff !== null && $diffOnly ) {
+                       parent::view();
+                       return;
+               }
+
+               if ( !wfRunHooks( 'CategoryPageView', array( &$this ) ) ) {
+                       return;
+               }
+
+               $title = $this->getTitle();
+               if ( NS_CATEGORY == $title->getNamespace() ) {
+                       $this->openShowCategory();
+               }
+
+               parent::view();
+
+               if ( NS_CATEGORY == $title->getNamespace() ) {
+                       $this->closeShowCategory();
+               }
+       }
+
+       function openShowCategory() {
+               # For overloading
+       }
+
+       function closeShowCategory() {
+               // Use these as defaults for back compat --catrope
+               $request = $this->getContext()->getRequest();
+               $oldFrom = $request->getVal( 'from' );
+               $oldUntil = $request->getVal( 'until' );
+
+               $reqArray = $request->getValues();
+
+               $from = $until = array();
+               foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
+                       $from[$type] = $request->getVal( "{$type}from", $oldFrom );
+                       $until[$type] = $request->getVal( "{$type}until", $oldUntil );
+
+                       // Do not want old-style from/until propagating in nav links.
+                       if ( !isset( $reqArray["{$type}from"] ) && isset( $reqArray["from"] ) ) {
+                               $reqArray["{$type}from"] = $reqArray["from"];
+                       }
+                       if ( !isset( $reqArray["{$type}to"] ) && isset( $reqArray["to"] ) ) {
+                               $reqArray["{$type}to"] = $reqArray["to"];
+                       }
+               }
+
+               unset( $reqArray["from"] );
+               unset( $reqArray["to"] );
+
+               $viewer = new $this->mCategoryViewerClass(
+                       $this->getContext()->getTitle(),
+                       $this->getContext(),
+                       $from,
+                       $until,
+                       $reqArray
+               );
+               $this->getContext()->getOutput()->addHTML( $viewer->getHTML() );
+       }
+}
diff --git a/includes/page/ImagePage.php b/includes/page/ImagePage.php
new file mode 100644 (file)
index 0000000..60db202
--- /dev/null
@@ -0,0 +1,1567 @@
+<?php
+/**
+ * Special handling for file description pages.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Class for viewing MediaWiki file description pages
+ *
+ * @ingroup Media
+ */
+class ImagePage extends Article {
+       /** @var File */
+       private $displayImg;
+
+       /** @var FileRepo */
+       private $repo;
+
+       /** @var bool */
+       private $fileLoaded;
+
+       /** @var bool */
+       protected $mExtraDescription = false;
+
+       /**
+        * @param Title $title
+        * @return WikiFilePage
+        */
+       protected function newPage( Title $title ) {
+               // Overload mPage with a file-specific page
+               return new WikiFilePage( $title );
+       }
+
+       /**
+        * Constructor from a page id
+        * @param int $id Article ID to load
+        * @return ImagePage|null
+        */
+       public static function newFromID( $id ) {
+               $t = Title::newFromID( $id );
+               # @todo FIXME: Doesn't inherit right
+               return $t == null ? null : new self( $t );
+               # return $t == null ? null : new static( $t ); // PHP 5.3
+       }
+
+       /**
+        * @param File $file
+        * @return void
+        */
+       public function setFile( $file ) {
+               $this->mPage->setFile( $file );
+               $this->displayImg = $file;
+               $this->fileLoaded = true;
+       }
+
+       protected function loadFile() {
+               if ( $this->fileLoaded ) {
+                       return;
+               }
+               $this->fileLoaded = true;
+
+               $this->displayImg = $img = false;
+               wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) );
+               if ( !$img ) { // not set by hook?
+                       $img = wfFindFile( $this->getTitle() );
+                       if ( !$img ) {
+                               $img = wfLocalFile( $this->getTitle() );
+                       }
+               }
+               $this->mPage->setFile( $img );
+               if ( !$this->displayImg ) { // not set by hook?
+                       $this->displayImg = $img;
+               }
+               $this->repo = $img->getRepo();
+       }
+
+       /**
+        * Handler for action=render
+        * Include body text only; none of the image extras
+        */
+       public function render() {
+               $this->getContext()->getOutput()->setArticleBodyOnly( true );
+               parent::view();
+       }
+
+       public function view() {
+               global $wgShowEXIF;
+
+               $out = $this->getContext()->getOutput();
+               $request = $this->getContext()->getRequest();
+               $diff = $request->getVal( 'diff' );
+               $diffOnly = $request->getBool(
+                       'diffonly',
+                       $this->getContext()->getUser()->getOption( 'diffonly' )
+               );
+
+               if ( $this->getTitle()->getNamespace() != NS_FILE || ( $diff !== null && $diffOnly ) ) {
+                       parent::view();
+                       return;
+               }
+
+               $this->loadFile();
+
+               if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) {
+                       if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || $diff !== null ) {
+                               // mTitle is the same as the redirect target so ask Article
+                               // to perform the redirect for us.
+                               $request->setVal( 'diffonly', 'true' );
+                               parent::view();
+                               return;
+                       } else {
+                               // mTitle is not the same as the redirect target so it is
+                               // probably the redirect page itself. Fake the redirect symbol
+                               $out->setPageTitle( $this->getTitle()->getPrefixedText() );
+                               $out->addHTML( $this->viewRedirect(
+                                       Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
+                                       /* $appendSubtitle */ true,
+                                       /* $forceKnown */ true )
+                               );
+                               $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
+                               return;
+                       }
+               }
+
+               if ( $wgShowEXIF && $this->displayImg->exists() ) {
+                       // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
+                       $formattedMetadata = $this->displayImg->formatMetadata();
+                       $showmeta = $formattedMetadata !== false;
+               } else {
+                       $showmeta = false;
+               }
+
+               if ( !$diff && $this->displayImg->exists() ) {
+                       $out->addHTML( $this->showTOC( $showmeta ) );
+               }
+
+               if ( !$diff ) {
+                       $this->openShowImage();
+               }
+
+               # No need to display noarticletext, we use our own message, output in openShowImage()
+               if ( $this->mPage->getID() ) {
+                       # NS_FILE is in the user language, but this section (the actual wikitext)
+                       # should be in page content language
+                       $pageLang = $this->getTitle()->getPageViewLanguage();
+                       $out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
+                               'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
+                               'class' => 'mw-content-' . $pageLang->getDir() ) ) );
+
+                       parent::view();
+
+                       $out->addHTML( Xml::closeElement( 'div' ) );
+               } else {
+                       # Just need to set the right headers
+                       $out->setArticleFlag( true );
+                       $out->setPageTitle( $this->getTitle()->getPrefixedText() );
+                       $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
+               }
+
+               # Show shared description, if needed
+               if ( $this->mExtraDescription ) {
+                       $fol = wfMessage( 'shareddescriptionfollows' );
+                       if ( !$fol->isDisabled() ) {
+                               $out->addWikiText( $fol->plain() );
+                       }
+                       $out->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
+               }
+
+               $this->closeShowImage();
+               $this->imageHistory();
+               // TODO: Cleanup the following
+
+               $out->addHTML( Xml::element( 'h2',
+                       array( 'id' => 'filelinks' ),
+                       wfMessage( 'imagelinks' )->text() ) . "\n" );
+               $this->imageDupes();
+               # @todo FIXME: For some freaky reason, we can't redirect to foreign images.
+               # Yet we return metadata about the target. Definitely an issue in the FileRepo
+               $this->imageLinks();
+
+               # Allow extensions to add something after the image links
+               $html = '';
+               wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) );
+               if ( $html ) {
+                       $out->addHTML( $html );
+               }
+
+               if ( $showmeta ) {
+                       $out->addHTML( Xml::element(
+                               'h2',
+                               array( 'id' => 'metadata' ),
+                               wfMessage( 'metadata' )->text() ) . "\n" );
+                       $out->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
+                       $out->addModules( array( 'mediawiki.action.view.metadata' ) );
+               }
+
+               // Add remote Filepage.css
+               if ( !$this->repo->isLocal() ) {
+                       $css = $this->repo->getDescriptionStylesheetUrl();
+                       if ( $css ) {
+                               $out->addStyle( $css );
+                       }
+               }
+               // always show the local local Filepage.css, bug 29277
+               $out->addModuleStyles( 'filepage' );
+       }
+
+       /**
+        * @return File
+        */
+       public function getDisplayedFile() {
+               $this->loadFile();
+               return $this->displayImg;
+       }
+
+       /**
+        * Create the TOC
+        *
+        * @param bool $metadata Whether or not to show the metadata link
+        * @return string
+        */
+       protected function showTOC( $metadata ) {
+               $r = array(
+                       '<li><a href="#file">' . wfMessage( 'file-anchor-link' )->escaped() . '</a></li>',
+                       '<li><a href="#filehistory">' . wfMessage( 'filehist' )->escaped() . '</a></li>',
+                       '<li><a href="#filelinks">' . wfMessage( 'imagelinks' )->escaped() . '</a></li>',
+               );
+               if ( $metadata ) {
+                       $r[] = '<li><a href="#metadata">' . wfMessage( 'metadata' )->escaped() . '</a></li>';
+               }
+
+               wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) );
+
+               return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
+       }
+
+       /**
+        * Make a table with metadata to be shown in the output page.
+        *
+        * @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
+        *
+        * @param array $metadata The array containing the Exif data
+        * @return string The metadata table. This is treated as Wikitext (!)
+        */
+       protected function makeMetadataTable( $metadata ) {
+               $r = "<div class=\"mw-imagepage-section-metadata\">";
+               $r .= wfMessage( 'metadata-help' )->plain();
+               $r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n";
+               foreach ( $metadata as $type => $stuff ) {
+                       foreach ( $stuff as $v ) {
+                               # @todo FIXME: Why is this using escapeId for a class?!
+                               $class = Sanitizer::escapeId( $v['id'] );
+                               if ( $type == 'collapsed' ) {
+                                       // Handled by mediawiki.action.view.metadata module
+                                       // and skins/common/shared.css.
+                                       $class .= ' collapsable';
+                               }
+                               $r .= "<tr class=\"$class\">\n";
+                               $r .= "<th>{$v['name']}</th>\n";
+                               $r .= "<td>{$v['value']}</td>\n</tr>";
+                       }
+               }
+               $r .= "</table>\n</div>\n";
+               return $r;
+       }
+
+       /**
+        * Overloading Article's getContentObject method.
+        *
+        * Omit noarticletext if sharedupload; text will be fetched from the
+        * shared upload server if possible.
+        * @return string
+        */
+       public function getContentObject() {
+               $this->loadFile();
+               if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
+                       return null;
+               }
+               return parent::getContentObject();
+       }
+
+       protected function openShowImage() {
+               global $wgImageLimits, $wgEnableUploads, $wgSend404Code;
+
+               $this->loadFile();
+               $out = $this->getContext()->getOutput();
+               $user = $this->getContext()->getUser();
+               $lang = $this->getContext()->getLanguage();
+               $dirmark = $lang->getDirMarkEntity();
+               $request = $this->getContext()->getRequest();
+
+               $max = $this->getImageLimitsFromOption( $user, 'imagesize' );
+               $maxWidth = $max[0];
+               $maxHeight = $max[1];
+
+               if ( $this->displayImg->exists() ) {
+                       # image
+                       $page = $request->getIntOrNull( 'page' );
+                       if ( is_null( $page ) ) {
+                               $params = array();
+                               $page = 1;
+                       } else {
+                               $params = array( 'page' => $page );
+                       }
+
+                       $renderLang = $request->getVal( 'lang' );
+                       if ( !is_null( $renderLang ) ) {
+                               $handler = $this->displayImg->getHandler();
+                               if ( $handler && $handler->validateParam( 'lang', $renderLang ) ) {
+                                       $params['lang'] = $renderLang;
+                               } else {
+                                       $renderLang = null;
+                               }
+                       }
+
+                       $width_orig = $this->displayImg->getWidth( $page );
+                       $width = $width_orig;
+                       $height_orig = $this->displayImg->getHeight( $page );
+                       $height = $height_orig;
+
+                       $filename = wfEscapeWikiText( $this->displayImg->getName() );
+                       $linktext = $filename;
+
+                       wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) );
+
+                       if ( $this->displayImg->allowInlineDisplay() ) {
+                               # image
+
+                               # "Download high res version" link below the image
+                               # $msgsize = wfMessage( 'file-info-size', $width_orig, $height_orig,
+                               #   Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
+                               # We'll show a thumbnail of this image
+                               if ( $width > $maxWidth || $height > $maxHeight ) {
+                                       # Calculate the thumbnail size.
+                                       # First case, the limiting factor is the width, not the height.
+                                       /** @todo // FIXME: Possible division by 0. bug 36911 */
+                                       if ( $width / $height >= $maxWidth / $maxHeight ) {
+                                               /** @todo // FIXME: Possible division by 0. bug 36911 */
+                                               $height = round( $height * $maxWidth / $width );
+                                               $width = $maxWidth;
+                                               # Note that $height <= $maxHeight now.
+                                       } else {
+                                               /** @todo // FIXME: Possible division by 0. bug 36911 */
+                                               $newwidth = floor( $width * $maxHeight / $height );
+                                               /** @todo // FIXME: Possible division by 0. bug 36911 */
+                                               $height = round( $height * $newwidth / $width );
+                                               $width = $newwidth;
+                                               # Note that $height <= $maxHeight now, but might not be identical
+                                               # because of rounding.
+                                       }
+                                       $linktext = wfMessage( 'show-big-image' )->escaped();
+                                       if ( $this->displayImg->getRepo()->canTransformVia404() ) {
+                                               $thumbSizes = $wgImageLimits;
+                                               // Also include the full sized resolution in the list, so
+                                               // that users know they can get it. This will link to the
+                                               // original file asset if mustRender() === false. In the case
+                                               // that we mustRender, some users have indicated that they would
+                                               // find it useful to have the full size image in the rendered
+                                               // image format.
+                                               $thumbSizes[] = array( $width_orig, $height_orig );
+                                       } else {
+                                               # Creating thumb links triggers thumbnail generation.
+                                               # Just generate the thumb for the current users prefs.
+                                               $thumbSizes = array( $this->getImageLimitsFromOption( $user, 'thumbsize' ) );
+                                               if ( !$this->displayImg->mustRender() ) {
+                                                       // We can safely include a link to the "full-size" preview,
+                                                       // without actually rendering.
+                                                       $thumbSizes[] = array( $width_orig, $height_orig );
+                                               }
+                                       }
+                                       # Generate thumbnails or thumbnail links as needed...
+                                       $otherSizes = array();
+                                       foreach ( $thumbSizes as $size ) {
+                                               // We include a thumbnail size in the list, if it is
+                                               // less than or equal to the original size of the image
+                                               // asset ($width_orig/$height_orig). We also exclude
+                                               // the current thumbnail's size ($width/$height)
+                                               // since that is added to the message separately, so
+                                               // it can be denoted as the current size being shown.
+                                               if ( $size[0] <= $width_orig && $size[1] <= $height_orig
+                                                       && $size[0] != $width && $size[1] != $height
+                                               ) {
+                                                       $sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
+                                                       if ( $sizeLink ) {
+                                                               $otherSizes[] = $sizeLink;
+                                                       }
+                                               }
+                                       }
+                                       $otherSizes = array_unique( $otherSizes );
+                                       $msgsmall = '';
+                                       $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
+                                       if ( $sizeLinkBigImagePreview ) {
+                                               $msgsmall .= wfMessage( 'show-big-image-preview' )->
+                                                       rawParams( $sizeLinkBigImagePreview )->
+                                                       parse();
+                                       }
+                                       if ( count( $otherSizes ) ) {
+                                               $msgsmall .= ' ' .
+                                               Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ),
+                                                       wfMessage( 'show-big-image-other' )->rawParams( $lang->pipeList( $otherSizes ) )->
+                                                       params( count( $otherSizes ) )->parse()
+                                               );
+                                       }
+                               } elseif ( $width == 0 && $height == 0 ) {
+                                       # Some sort of audio file that doesn't have dimensions
+                                       # Don't output a no hi res message for such a file
+                                       $msgsmall = '';
+                               } elseif ( $this->displayImg->isVectorized() ) {
+                                       # For vectorized images, full size is just the frame size
+                                       $msgsmall = '';
+                               } else {
+                                       # Image is small enough to show full size on image page
+                                       $msgsmall = wfMessage( 'file-nohires' )->parse();
+                               }
+
+                               $params['width'] = $width;
+                               $params['height'] = $height;
+                               $thumbnail = $this->displayImg->transform( $params );
+                               Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params );
+
+                               $anchorclose = Html::rawElement(
+                                       'div',
+                                       array( 'class' => 'mw-filepage-resolutioninfo' ),
+                                       $msgsmall
+                               );
+
+                               $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
+                               if ( $isMulti ) {
+                                       $out->addModules( 'mediawiki.page.image.pagination' );
+                                       $out->addHTML( '<table class="multipageimage"><tr><td>' );
+                               }
+
+                               if ( $thumbnail ) {
+                                       $options = array(
+                                               'alt' => $this->displayImg->getTitle()->getPrefixedText(),
+                                               'file-link' => true,
+                                       );
+                                       $out->addHTML( '<div class="fullImageLink" id="file">' .
+                                               $thumbnail->toHtml( $options ) .
+                                               $anchorclose . "</div>\n" );
+                               }
+
+                               if ( $isMulti ) {
+                                       $count = $this->displayImg->pageCount();
+
+                                       if ( $page > 1 ) {
+                                               $label = $out->parse( wfMessage( 'imgmultipageprev' )->text(), false );
+                                               $link = Linker::linkKnown(
+                                                       $this->getTitle(),
+                                                       $label,
+                                                       array(),
+                                                       array( 'page' => $page - 1 )
+                                               );
+                                               $thumb1 = Linker::makeThumbLinkObj(
+                                                       $this->getTitle(),
+                                                       $this->displayImg,
+                                                       $link,
+                                                       $label,
+                                                       'none',
+                                                       array( 'page' => $page - 1 )
+                                               );
+                                       } else {
+                                               $thumb1 = '';
+                                       }
+
+                                       if ( $page < $count ) {
+                                               $label = wfMessage( 'imgmultipagenext' )->text();
+                                               $link = Linker::linkKnown(
+                                                       $this->getTitle(),
+                                                       $label,
+                                                       array(),
+                                                       array( 'page' => $page + 1 )
+                                               );
+                                               $thumb2 = Linker::makeThumbLinkObj(
+                                                       $this->getTitle(),
+                                                       $this->displayImg,
+                                                       $link,
+                                                       $label,
+                                                       'none',
+                                                       array( 'page' => $page + 1 )
+                                               );
+                                       } else {
+                                               $thumb2 = '';
+                                       }
+
+                                       global $wgScript;
+
+                                       $formParams = array(
+                                               'name' => 'pageselector',
+                                               'action' => $wgScript,
+                                       );
+                                       $options = array();
+                                       for ( $i = 1; $i <= $count; $i++ ) {
+                                               $options[] = Xml::option( $lang->formatNum( $i ), $i, $i == $page );
+                                       }
+                                       $select = Xml::tags( 'select',
+                                               array( 'id' => 'pageselector', 'name' => 'page' ),
+                                               implode( "\n", $options ) );
+
+                                       $out->addHTML(
+                                               '</td><td><div class="multipageimagenavbox">' .
+                                               Xml::openElement( 'form', $formParams ) .
+                                               Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
+                                                       wfMessage( 'imgmultigoto' )->rawParams( $select )->parse() .
+                                               Xml::submitButton( wfMessage( 'imgmultigo' )->text() ) .
+                                               Xml::closeElement( 'form' ) .
+                                               "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
+                                       );
+                               }
+                       } elseif ( $this->displayImg->isSafeFile() ) {
+                               # if direct link is allowed but it's not a renderable image, show an icon.
+                               $icon = $this->displayImg->iconThumb();
+
+                               $out->addHTML( '<div class="fullImageLink" id="file">' .
+                                       $icon->toHtml( array( 'file-link' => true ) ) .
+                                       "</div>\n" );
+                       }
+
+                       $longDesc = wfMessage( 'parentheses', $this->displayImg->getLongDesc() )->text();
+
+                       $medialink = "[[Media:$filename|$linktext]]";
+
+                       if ( !$this->displayImg->isSafeFile() ) {
+                               $warning = wfMessage( 'mediawarning' )->plain();
+                               // dirmark is needed here to separate the file name, which
+                               // most likely ends in Latin characters, from the description,
+                               // which may begin with the file type. In RTL environment
+                               // this will get messy.
+                               // The dirmark, however, must not be immediately adjacent
+                               // to the filename, because it can get copied with it.
+                               // See bug 25277.
+                               // @codingStandardsIgnoreStart Ignore long line
+                               $out->addWikiText( <<<EOT
+<div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
+<div class="mediaWarning">$warning</div>
+EOT
+                               );
+                               // @codingStandardsIgnoreEnd
+                       } else {
+                               $out->addWikiText( <<<EOT
+<div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
+</div>
+EOT
+                               );
+                       }
+
+                       $renderLangOptions = $this->displayImg->getAvailableLanguages();
+                       if ( count( $renderLangOptions ) >= 1 ) {
+                               $currentLanguage = $renderLang;
+                               $defaultLang = $this->displayImg->getDefaultRenderLanguage();
+                               if ( is_null( $currentLanguage ) ) {
+                                       $currentLanguage = $defaultLang;
+                               }
+                               $out->addHtml( $this->doRenderLangOpt( $renderLangOptions, $currentLanguage, $defaultLang ) );
+                       }
+
+                       // Add cannot animate thumbnail warning
+                       if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) {
+                               // Include the extension so wiki admins can
+                               // customize it on a per file-type basis
+                               // (aka say things like use format X instead).
+                               // additionally have a specific message for
+                               // file-no-thumb-animation-gif
+                               $ext = $this->displayImg->getExtension();
+                               $noAnimMesg = wfMessageFallback(
+                                       'file-no-thumb-animation-' . $ext,
+                                       'file-no-thumb-animation'
+                               )->plain();
+
+                               $out->addWikiText( <<<EOT
+<div class="mw-noanimatethumb">{$noAnimMesg}</div>
+EOT
+                               );
+                       }
+
+                       if ( !$this->displayImg->isLocal() ) {
+                               $this->printSharedImageText();
+                       }
+               } else {
+                       # Image does not exist
+                       if ( !$this->getID() ) {
+                               # No article exists either
+                               # Show deletion log to be consistent with normal articles
+                               LogEventsList::showLogExtract(
+                                       $out,
+                                       array( 'delete', 'move' ),
+                                       $this->getTitle()->getPrefixedText(),
+                                       '',
+                                       array( 'lim' => 10,
+                                               'conds' => array( "log_action != 'revision'" ),
+                                               'showIfEmpty' => false,
+                                               'msgKey' => array( 'moveddeleted-notice' )
+                                       )
+                               );
+                       }
+
+                       if ( $wgEnableUploads && $user->isAllowed( 'upload' ) ) {
+                               // Only show an upload link if the user can upload
+                               $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
+                               $nofile = array(
+                                       'filepage-nofile-link',
+                                       $uploadTitle->getFullURL( array( 'wpDestFile' => $this->mPage->getFile()->getName() ) )
+                               );
+                       } else {
+                               $nofile = 'filepage-nofile';
+                       }
+                       // Note, if there is an image description page, but
+                       // no image, then this setRobotPolicy is overridden
+                       // by Article::View().
+                       $out->setRobotPolicy( 'noindex,nofollow' );
+                       $out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
+                       if ( !$this->getID() && $wgSend404Code ) {
+                               // If there is no image, no shared image, and no description page,
+                               // output a 404, to be consistent with articles.
+                               $request->response()->header( 'HTTP/1.1 404 Not Found' );
+                       }
+               }
+               $out->setFileVersion( $this->displayImg );
+       }
+
+       /**
+        * Creates an thumbnail of specified size and returns an HTML link to it
+        * @param array $params Scaler parameters
+        * @param int $width
+        * @param int $height
+        * @return string
+        */
+       private function makeSizeLink( $params, $width, $height ) {
+               $params['width'] = $width;
+               $params['height'] = $height;
+               $thumbnail = $this->displayImg->transform( $params );
+               if ( $thumbnail && !$thumbnail->isError() ) {
+                       return Html::rawElement( 'a', array(
+                               'href' => $thumbnail->getUrl(),
+                               'class' => 'mw-thumbnail-link'
+                               ), wfMessage( 'show-big-image-size' )->numParams(
+                                       $thumbnail->getWidth(), $thumbnail->getHeight()
+                               )->parse() );
+               } else {
+                       return '';
+               }
+       }
+
+       /**
+        * Show a notice that the file is from a shared repository
+        */
+       protected function printSharedImageText() {
+               $out = $this->getContext()->getOutput();
+               $this->loadFile();
+
+               $descUrl = $this->mPage->getFile()->getDescriptionUrl();
+               $descText = $this->mPage->getFile()->getDescriptionText( $this->getContext()->getLanguage() );
+
+               /* Add canonical to head if there is no local page for this shared file */
+               if ( $descUrl && $this->mPage->getID() == 0 ) {
+                       $out->setCanonicalUrl( $descUrl );
+               }
+
+               $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
+               $repo = $this->mPage->getFile()->getRepo()->getDisplayName();
+
+               if ( $descUrl && $descText && wfMessage( 'sharedupload-desc-here' )->plain() !== '-' ) {
+                       $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
+               } elseif ( $descUrl && wfMessage( 'sharedupload-desc-there' )->plain() !== '-' ) {
+                       $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) );
+               } else {
+                       $out->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), ''/*BACKCOMPAT*/ );
+               }
+
+               if ( $descText ) {
+                       $this->mExtraDescription = $descText;
+               }
+       }
+
+       public function getUploadUrl() {
+               $this->loadFile();
+               $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
+               return $uploadTitle->getFullURL( array(
+                       'wpDestFile' => $this->mPage->getFile()->getName(),
+                       'wpForReUpload' => 1
+               ) );
+       }
+
+       /**
+        * Print out the various links at the bottom of the image page, e.g. reupload,
+        * external editing (and instructions link) etc.
+        */
+       protected function uploadLinksBox() {
+               global $wgEnableUploads;
+
+               if ( !$wgEnableUploads ) {
+                       return;
+               }
+
+               $this->loadFile();
+               if ( !$this->mPage->getFile()->isLocal() ) {
+                       return;
+               }
+
+               $out = $this->getContext()->getOutput();
+               $out->addHTML( "<ul>\n" );
+
+               # "Upload a new version of this file" link
+               $canUpload = $this->getTitle()->userCan( 'upload', $this->getContext()->getUser() );
+               if ( $canUpload && UploadBase::userCanReUpload(
+                               $this->getContext()->getUser(),
+                               $this->mPage->getFile()->name )
+               ) {
+                       $ulink = Linker::makeExternalLink(
+                               $this->getUploadUrl(),
+                               wfMessage( 'uploadnewversion-linktext' )->text()
+                       );
+                       $out->addHTML( "<li id=\"mw-imagepage-reupload-link\">"
+                               . "<div class=\"plainlinks\">{$ulink}</div></li>\n" );
+               } else {
+                       $out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">"
+                               . $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" );
+               }
+
+               $out->addHTML( "</ul>\n" );
+       }
+
+       /**
+        * For overloading
+        */
+       protected function closeShowImage() {
+       }
+
+       /**
+        * If the page we've just displayed is in the "Image" namespace,
+        * we follow it with an upload history of the image and its usage.
+        */
+       protected function imageHistory() {
+               $this->loadFile();
+               $out = $this->getContext()->getOutput();
+               $pager = new ImageHistoryPseudoPager( $this );
+               $out->addHTML( $pager->getBody() );
+               $out->preventClickjacking( $pager->getPreventClickjacking() );
+
+               $this->mPage->getFile()->resetHistory(); // free db resources
+
+               # Exist check because we don't want to show this on pages where an image
+               # doesn't exist along with the noimage message, that would suck. -ævar
+               if ( $this->mPage->getFile()->exists() ) {
+                       $this->uploadLinksBox();
+               }
+       }
+
+       /**
+        * @param string $target
+        * @param int $limit
+        * @return ResultWrapper
+        */
+       protected function queryImageLinks( $target, $limit ) {
+               $dbr = wfGetDB( DB_SLAVE );
+
+               return $dbr->select(
+                       array( 'imagelinks', 'page' ),
+                       array( 'page_namespace', 'page_title', 'il_to' ),
+                       array( 'il_to' => $target, 'il_from = page_id' ),
+                       __METHOD__,
+                       array( 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', )
+               );
+       }
+
+       protected function imageLinks() {
+               $limit = 100;
+
+               $out = $this->getContext()->getOutput();
+
+               $rows = array();
+               $redirects = array();
+               foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) {
+                       $redirects[$redir->getDBkey()] = array();
+                       $rows[] = (object)array(
+                               'page_namespace' => NS_FILE,
+                               'page_title' => $redir->getDBkey(),
+                       );
+               }
+
+               $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
+               foreach ( $res as $row ) {
+                       $rows[] = $row;
+               }
+               $count = count( $rows );
+
+               $hasMore = $count > $limit;
+               if ( !$hasMore && count( $redirects ) ) {
+                       $res = $this->queryImageLinks( array_keys( $redirects ),
+                               $limit - count( $rows ) + 1 );
+                       foreach ( $res as $row ) {
+                               $redirects[$row->il_to][] = $row;
+                               $count++;
+                       }
+                       $hasMore = ( $res->numRows() + count( $rows ) ) > $limit;
+               }
+
+               if ( $count == 0 ) {
+                       $out->wrapWikiMsg(
+                               Html::rawElement( 'div',
+                                       array( 'id' => 'mw-imagepage-nolinkstoimage' ), "\n$1\n" ),
+                               'nolinkstoimage'
+                       );
+                       return;
+               }
+
+               $out->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
+               if ( !$hasMore ) {
+                       $out->addWikiMsg( 'linkstoimage', $count );
+               } else {
+                       // More links than the limit. Add a link to [[Special:Whatlinkshere]]
+                       $out->addWikiMsg( 'linkstoimage-more',
+                               $this->getContext()->getLanguage()->formatNum( $limit ),
+                               $this->getTitle()->getPrefixedDBkey()
+                       );
+               }
+
+               $out->addHTML(
+                       Html::openElement( 'ul',
+                               array( 'class' => 'mw-imagepage-linkstoimage' ) ) . "\n"
+               );
+               $count = 0;
+
+               // Sort the list by namespace:title
+               usort( $rows, array( $this, 'compare' ) );
+
+               // Create links for every element
+               $currentCount = 0;
+               foreach ( $rows as $element ) {
+                       $currentCount++;
+                       if ( $currentCount > $limit ) {
+                               break;
+                       }
+
+                       $query = array();
+                       # Add a redirect=no to make redirect pages reachable
+                       if ( isset( $redirects[$element->page_title] ) ) {
+                               $query['redirect'] = 'no';
+                       }
+                       $link = Linker::linkKnown(
+                               Title::makeTitle( $element->page_namespace, $element->page_title ),
+                               null, array(), $query
+                       );
+                       if ( !isset( $redirects[$element->page_title] ) ) {
+                               # No redirects
+                               $liContents = $link;
+                       } elseif ( count( $redirects[$element->page_title] ) === 0 ) {
+                               # Redirect without usages
+                               $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse();
+                       } else {
+                               # Redirect with usages
+                               $li = '';
+                               foreach ( $redirects[$element->page_title] as $row ) {
+                                       $currentCount++;
+                                       if ( $currentCount > $limit ) {
+                                               break;
+                                       }
+
+                                       $link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) );
+                                       $li .= Html::rawElement(
+                                               'li',
+                                               array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
+                                               $link2
+                                               ) . "\n";
+                               }
+
+                               $ul = Html::rawElement(
+                                       'ul',
+                                       array( 'class' => 'mw-imagepage-redirectstofile' ),
+                                       $li
+                                       ) . "\n";
+                               $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams(
+                                       $link, $ul )->parse();
+                       }
+                       $out->addHTML( Html::rawElement(
+                                       'li',
+                                       array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
+                                       $liContents
+                               ) . "\n"
+                       );
+
+               };
+               $out->addHTML( Html::closeElement( 'ul' ) . "\n" );
+               $res->free();
+
+               // Add a links to [[Special:Whatlinkshere]]
+               if ( $count > $limit ) {
+                       $out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
+               }
+               $out->addHTML( Html::closeElement( 'div' ) . "\n" );
+       }
+
+       protected function imageDupes() {
+               $this->loadFile();
+               $out = $this->getContext()->getOutput();
+
+               $dupes = $this->mPage->getDuplicates();
+               if ( count( $dupes ) == 0 ) {
+                       return;
+               }
+
+               $out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
+               $out->addWikiMsg( 'duplicatesoffile',
+                       $this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
+               );
+               $out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
+
+               /**
+                * @var $file File
+                */
+               foreach ( $dupes as $file ) {
+                       $fromSrc = '';
+                       if ( $file->isLocal() ) {
+                               $link = Linker::linkKnown( $file->getTitle() );
+                       } else {
+                               $link = Linker::makeExternalLink( $file->getDescriptionUrl(),
+                                       $file->getTitle()->getPrefixedText() );
+                               $fromSrc = wfMessage( 'shared-repo-from', $file->getRepo()->getDisplayName() )->text();
+                       }
+                       $out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
+               }
+               $out->addHTML( "</ul></div>\n" );
+       }
+
+       /**
+        * Delete the file, or an earlier version of it
+        */
+       public function delete() {
+               $file = $this->mPage->getFile();
+               if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
+                       // Standard article deletion
+                       parent::delete();
+                       return;
+               }
+
+               $deleter = new FileDeleteForm( $file );
+               $deleter->execute();
+       }
+
+       /**
+        * Display an error with a wikitext description
+        *
+        * @param string $description
+        */
+       function showError( $description ) {
+               $out = $this->getContext()->getOutput();
+               $out->setPageTitle( wfMessage( 'internalerror' ) );
+               $out->setRobotPolicy( 'noindex,nofollow' );
+               $out->setArticleRelated( false );
+               $out->enableClientCache( false );
+               $out->addWikiText( $description );
+       }
+
+       /**
+        * Callback for usort() to do link sorts by (namespace, title)
+        * Function copied from Title::compare()
+        *
+        * @param object $a Object page to compare with
+        * @param object $b Object page to compare with
+        * @return int Result of string comparison, or namespace comparison
+        */
+       protected function compare( $a, $b ) {
+               if ( $a->page_namespace == $b->page_namespace ) {
+                       return strcmp( $a->page_title, $b->page_title );
+               } else {
+                       return $a->page_namespace - $b->page_namespace;
+               }
+       }
+
+       /**
+        * Returns the corresponding $wgImageLimits entry for the selected user option
+        *
+        * @param User $user
+        * @param string $optionName Name of a option to check, typically imagesize or thumbsize
+        * @return array
+        * @since 1.21
+        */
+       public function getImageLimitsFromOption( $user, $optionName ) {
+               global $wgImageLimits;
+
+               $option = $user->getIntOption( $optionName );
+               if ( !isset( $wgImageLimits[$option] ) ) {
+                       $option = User::getDefaultOption( $optionName );
+               }
+
+               // The user offset might still be incorrect, specially if
+               // $wgImageLimits got changed (see bug #8858).
+               if ( !isset( $wgImageLimits[$option] ) ) {
+                       // Default to the first offset in $wgImageLimits
+                       $option = 0;
+               }
+
+               return isset( $wgImageLimits[$option] )
+                       ? $wgImageLimits[$option]
+                       : array( 800, 600 ); // if nothing is set, fallback to a hardcoded default
+       }
+
+       /**
+        * Output a drop-down box for language options for the file
+        *
+        * @param array $langChoices Array of string language codes
+        * @param string $curLang Language code file is being viewed in.
+        * @param string $defaultLang Language code that image is rendered in by default
+        * @return string HTML to insert underneath image.
+        */
+       protected function doRenderLangOpt( array $langChoices, $curLang, $defaultLang ) {
+               global $wgScript;
+               sort( $langChoices );
+               $curLang = wfBCP47( $curLang );
+               $defaultLang = wfBCP47( $defaultLang );
+               $opts = '';
+               $haveCurrentLang = false;
+               $haveDefaultLang = false;
+
+               // We make a list of all the language choices in the file.
+               // Additionally if the default language to render this file
+               // is not included as being in this file (for example, in svgs
+               // usually the fallback content is the english content) also
+               // include a choice for that. Last of all, if we're viewing
+               // the file in a language not on the list, add it as a choice.
+               foreach ( $langChoices as $lang ) {
+                       $code = wfBCP47( $lang );
+                       $name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
+                       if ( $name !== '' ) {
+                               $display = wfMessage( 'img-lang-opt', $code, $name )->text();
+                       } else {
+                               $display = $code;
+                       }
+                       $opts .= "\n" . Xml::option( $display, $code, $curLang === $code );
+                       if ( $curLang === $code ) {
+                               $haveCurrentLang = true;
+                       }
+                       if ( $defaultLang === $code ) {
+                               $haveDefaultLang = true;
+                       }
+               }
+               if ( !$haveDefaultLang ) {
+                       // Its hard to know if the content is really in the default language, or
+                       // if its just unmarked content that could be in any language.
+                       $opts = Xml::option(
+                               wfMessage( 'img-lang-default' )->text(),
+                               $defaultLang,
+                               $defaultLang === $curLang
+                       ) . $opts;
+               }
+               if ( !$haveCurrentLang && $defaultLang !== $curLang ) {
+                       $name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() );
+                       if ( $name !== '' ) {
+                               $display = wfMessage( 'img-lang-opt', $curLang, $name )->text();
+                       } else {
+                               $display = $curLang;
+                       }
+                       $opts = Xml::option( $display, $curLang, true ) . $opts;
+               }
+
+               $select = Html::rawElement(
+                       'select',
+                       array( 'id' => 'mw-imglangselector', 'name' => 'lang' ),
+                       $opts
+               );
+               $submit = Xml::submitButton( wfMessage( 'img-lang-go' )->text() );
+
+               $formContents = wfMessage( 'img-lang-info' )->rawParams( $select, $submit )->parse()
+                       . Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
+
+               $langSelectLine = Html::rawElement( 'div', array( 'id' => 'mw-imglangselector-line' ),
+                       Html::rawElement( 'form', array( 'action' => $wgScript ), $formContents )
+               );
+               return $langSelectLine;
+       }
+}
+
+/**
+ * Builds the image revision log shown on image pages
+ *
+ * @ingroup Media
+ */
+class ImageHistoryList extends ContextSource {
+
+       /**
+        * @var Title
+        */
+       protected $title;
+
+       /**
+        * @var File
+        */
+       protected $img;
+
+       /**
+        * @var ImagePage
+        */
+       protected $imagePage;
+
+       /**
+        * @var File
+        */
+       protected $current;
+
+       protected $repo, $showThumb;
+       protected $preventClickjacking = false;
+
+       /**
+        * @param ImagePage $imagePage
+        */
+       public function __construct( $imagePage ) {
+               global $wgShowArchiveThumbnails;
+               $this->current = $imagePage->getFile();
+               $this->img = $imagePage->getDisplayedFile();
+               $this->title = $imagePage->getTitle();
+               $this->imagePage = $imagePage;
+               $this->showThumb = $wgShowArchiveThumbnails && $this->img->canRender();
+               $this->setContext( $imagePage->getContext() );
+       }
+
+       /**
+        * @return ImagePage
+        */
+       public function getImagePage() {
+               return $this->imagePage;
+       }
+
+       /**
+        * @return File
+        */
+       public function getFile() {
+               return $this->img;
+       }
+
+       /**
+        * @param string $navLinks
+        * @return string
+        */
+       public function beginImageHistoryList( $navLinks = '' ) {
+               return Xml::element( 'h2', array( 'id' => 'filehistory' ), $this->msg( 'filehist' )->text() )
+                       . "\n"
+                       . "<div id=\"mw-imagepage-section-filehistory\">\n"
+                       . $this->msg( 'filehist-help' )->parseAsBlock()
+                       . $navLinks . "\n"
+                       . Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n"
+                       . '<tr><td></td>'
+                       . ( $this->current->isLocal()
+                               && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' )
+                       . '<th>' . $this->msg( 'filehist-datetime' )->escaped() . '</th>'
+                       . ( $this->showThumb ? '<th>' . $this->msg( 'filehist-thumb' )->escaped() . '</th>' : '' )
+                       . '<th>' . $this->msg( 'filehist-dimensions' )->escaped() . '</th>'
+                       . '<th>' . $this->msg( 'filehist-user' )->escaped() . '</th>'
+                       . '<th>' . $this->msg( 'filehist-comment' )->escaped() . '</th>'
+                       . "</tr>\n";
+       }
+
+       /**
+        * @param string $navLinks
+        * @return string
+        */
+       public function endImageHistoryList( $navLinks = '' ) {
+               return "</table>\n$navLinks\n</div>\n";
+       }
+
+       /**
+        * @param bool $iscur
+        * @param File $file
+        * @return string
+        */
+       public function imageHistoryLine( $iscur, $file ) {
+               global $wgContLang;
+
+               $user = $this->getUser();
+               $lang = $this->getLanguage();
+               $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
+               $img = $iscur ? $file->getName() : $file->getArchiveName();
+               $userId = $file->getUser( 'id' );
+               $userText = $file->getUser( 'text' );
+               $description = $file->getDescription( File::FOR_THIS_USER, $user );
+
+               $local = $this->current->isLocal();
+               $row = $selected = '';
+
+               // Deletion link
+               if ( $local && ( $user->isAllowedAny( 'delete', 'deletedhistory' ) ) ) {
+                       $row .= '<td>';
+                       # Link to remove from history
+                       if ( $user->isAllowed( 'delete' ) ) {
+                               $q = array( 'action' => 'delete' );
+                               if ( !$iscur ) {
+                                       $q['oldimage'] = $img;
+                               }
+                               $row .= Linker::linkKnown(
+                                       $this->title,
+                                       $this->msg( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' )->escaped(),
+                                       array(), $q
+                               );
+                       }
+                       # Link to hide content. Don't show useless link to people who cannot hide revisions.
+                       $canHide = $user->isAllowed( 'deleterevision' );
+                       if ( $canHide || ( $user->isAllowed( 'deletedhistory' ) && $file->getVisibility() ) ) {
+                               if ( $user->isAllowed( 'delete' ) ) {
+                                       $row .= '<br />';
+                               }
+                               // If file is top revision or locked from this user, don't link
+                               if ( $iscur || !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
+                                       $del = Linker::revDeleteLinkDisabled( $canHide );
+                               } else {
+                                       list( $ts, ) = explode( '!', $img, 2 );
+                                       $query = array(
+                                               'type' => 'oldimage',
+                                               'target' => $this->title->getPrefixedText(),
+                                               'ids' => $ts,
+                                       );
+                                       $del = Linker::revDeleteLink( $query,
+                                               $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
+                               }
+                               $row .= $del;
+                       }
+                       $row .= '</td>';
+               }
+
+               // Reversion link/current indicator
+               $row .= '<td>';
+               if ( $iscur ) {
+                       $row .= $this->msg( 'filehist-current' )->escaped();
+               } elseif ( $local && $this->title->quickUserCan( 'edit', $user )
+                       && $this->title->quickUserCan( 'upload', $user )
+               ) {
+                       if ( $file->isDeleted( File::DELETED_FILE ) ) {
+                               $row .= $this->msg( 'filehist-revert' )->escaped();
+                       } else {
+                               $row .= Linker::linkKnown(
+                                       $this->title,
+                                       $this->msg( 'filehist-revert' )->escaped(),
+                                       array(),
+                                       array(
+                                               'action' => 'revert',
+                                               'oldimage' => $img,
+                                               'wpEditToken' => $user->getEditToken( $img )
+                                       )
+                               );
+                       }
+               }
+               $row .= '</td>';
+
+               // Date/time and image link
+               if ( $file->getTimestamp() === $this->img->getTimestamp() ) {
+                       $selected = "class='filehistory-selected'";
+               }
+               $row .= "<td $selected style='white-space: nowrap;'>";
+               if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
+                       # Don't link to unviewable files
+                       $row .= '<span class="history-deleted">'
+                               . $lang->userTimeAndDate( $timestamp, $user ) . '</span>';
+               } elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
+                       if ( $local ) {
+                               $this->preventClickjacking();
+                               $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+                               # Make a link to review the image
+                               $url = Linker::linkKnown(
+                                       $revdel,
+                                       $lang->userTimeAndDate( $timestamp, $user ),
+                                       array(),
+                                       array(
+                                               'target' => $this->title->getPrefixedText(),
+                                               'file' => $img,
+                                               'token' => $user->getEditToken( $img )
+                                       )
+                               );
+                       } else {
+                               $url = $lang->userTimeAndDate( $timestamp, $user );
+                       }
+                       $row .= '<span class="history-deleted">' . $url . '</span>';
+               } else {
+                       $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
+                       $row .= Xml::element(
+                               'a',
+                               array( 'href' => $url ),
+                               $lang->userTimeAndDate( $timestamp, $user )
+                       );
+               }
+               $row .= "</td>";
+
+               // Thumbnail
+               if ( $this->showThumb ) {
+                       $row .= '<td>' . $this->getThumbForLine( $file ) . '</td>';
+               }
+
+               // Image dimensions + size
+               $row .= '<td>';
+               $row .= htmlspecialchars( $file->getDimensionsString() );
+               $row .= $this->msg( 'word-separator' )->escaped();
+               $row .= '<span style="white-space: nowrap;">';
+               $row .= $this->msg( 'parentheses' )->sizeParams( $file->getSize() )->escaped();
+               $row .= '</span>';
+               $row .= '</td>';
+
+               // Uploading user
+               $row .= '<td>';
+               // Hide deleted usernames
+               if ( $file->isDeleted( File::DELETED_USER ) ) {
+                       $row .= '<span class="history-deleted">'
+                               . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
+               } else {
+                       if ( $local ) {
+                               $row .= Linker::userLink( $userId, $userText );
+                               $row .= $this->msg( 'word-separator' )->escaped();
+                               $row .= '<span style="white-space: nowrap;">';
+                               $row .= Linker::userToolLinks( $userId, $userText );
+                               $row .= '</span>';
+                       } else {
+                               $row .= htmlspecialchars( $userText );
+                       }
+               }
+               $row .= '</td>';
+
+               // Don't show deleted descriptions
+               if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
+                       $row .= '<td><span class="history-deleted">' .
+                               $this->msg( 'rev-deleted-comment' )->escaped() . '</span></td>';
+               } else {
+                       $row .= '<td dir="' . $wgContLang->getDir() . '">' .
+                               Linker::formatComment( $description, $this->title ) . '</td>';
+               }
+
+               $rowClass = null;
+               wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
+               $classAttr = $rowClass ? " class='$rowClass'" : '';
+
+               return "<tr{$classAttr}>{$row}</tr>\n";
+       }
+
+       /**
+        * @param File $file
+        * @return string
+        */
+       protected function getThumbForLine( $file ) {
+               $lang = $this->getLanguage();
+               $user = $this->getUser();
+               if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE, $user )
+                       && !$file->isDeleted( File::DELETED_FILE )
+               ) {
+                       $params = array(
+                               'width' => '120',
+                               'height' => '120',
+                       );
+                       $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
+
+                       $thumbnail = $file->transform( $params );
+                       $options = array(
+                               'alt' => $this->msg( 'filehist-thumbtext',
+                                       $lang->userTimeAndDate( $timestamp, $user ),
+                                       $lang->userDate( $timestamp, $user ),
+                                       $lang->userTime( $timestamp, $user ) )->text(),
+                               'file-link' => true,
+                       );
+
+                       if ( !$thumbnail ) {
+                               return $this->msg( 'filehist-nothumb' )->escaped();
+                       }
+
+                       return $thumbnail->toHtml( $options );
+               } else {
+                       return $this->msg( 'filehist-nothumb' )->escaped();
+               }
+       }
+
+       /**
+        * @param bool $enable
+        */
+       protected function preventClickjacking( $enable = true ) {
+               $this->preventClickjacking = $enable;
+       }
+
+       /**
+        * @return bool
+        */
+       public function getPreventClickjacking() {
+               return $this->preventClickjacking;
+       }
+}
+
+class ImageHistoryPseudoPager extends ReverseChronologicalPager {
+       protected $preventClickjacking = false;
+
+       /**
+        * @var File
+        */
+       protected $mImg;
+
+       /**
+        * @var Title
+        */
+       protected $mTitle;
+
+       /**
+        * @param ImagePage $imagePage
+        */
+       function __construct( $imagePage ) {
+               parent::__construct( $imagePage->getContext() );
+               $this->mImagePage = $imagePage;
+               $this->mTitle = clone ( $imagePage->getTitle() );
+               $this->mTitle->setFragment( '#filehistory' );
+               $this->mImg = null;
+               $this->mHist = array();
+               $this->mRange = array( 0, 0 ); // display range
+       }
+
+       /**
+        * @return Title
+        */
+       function getTitle() {
+               return $this->mTitle;
+       }
+
+       function getQueryInfo() {
+               return false;
+       }
+
+       /**
+        * @return string
+        */
+       function getIndexField() {
+               return '';
+       }
+
+       /**
+        * @param object $row
+        * @return string
+        */
+       function formatRow( $row ) {
+               return '';
+       }
+
+       /**
+        * @return string
+        */
+       function getBody() {
+               $s = '';
+               $this->doQuery();
+               if ( count( $this->mHist ) ) {
+                       $list = new ImageHistoryList( $this->mImagePage );
+                       # Generate prev/next links
+                       $navLink = $this->getNavigationBar();
+                       $s = $list->beginImageHistoryList( $navLink );
+                       // Skip rows there just for paging links
+                       for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
+                               $file = $this->mHist[$i];
+                               $s .= $list->imageHistoryLine( !$file->isOld(), $file );
+                       }
+                       $s .= $list->endImageHistoryList( $navLink );
+
+                       if ( $list->getPreventClickjacking() ) {
+                               $this->preventClickjacking();
+                       }
+               }
+               return $s;
+       }
+
+       function doQuery() {
+               if ( $this->mQueryDone ) {
+                       return;
+               }
+               $this->mImg = $this->mImagePage->getFile(); // ensure loading
+               if ( !$this->mImg->exists() ) {
+                       return;
+               }
+               $queryLimit = $this->mLimit + 1; // limit plus extra row
+               if ( $this->mIsBackwards ) {
+                       // Fetch the file history
+                       $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false );
+                       // The current rev may not meet the offset/limit
+                       $numRows = count( $this->mHist );
+                       if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) {
+                               $this->mHist = array_merge( array( $this->mImg ), $this->mHist );
+                       }
+               } else {
+                       // The current rev may not meet the offset
+                       if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) {
+                               $this->mHist[] = $this->mImg;
+                       }
+                       // Old image versions (fetch extra row for nav links)
+                       $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1;
+                       // Fetch the file history
+                       $this->mHist = array_merge( $this->mHist,
+                               $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) );
+               }
+               $numRows = count( $this->mHist ); // Total number of query results
+               if ( $numRows ) {
+                       # Index value of top item in the list
+                       $firstIndex = $this->mIsBackwards ?
+                               $this->mHist[$numRows - 1]->getTimestamp() : $this->mHist[0]->getTimestamp();
+                       # Discard the extra result row if there is one
+                       if ( $numRows > $this->mLimit && $numRows > 1 ) {
+                               if ( $this->mIsBackwards ) {
+                                       # Index value of item past the index
+                                       $this->mPastTheEndIndex = $this->mHist[0]->getTimestamp();
+                                       # Index value of bottom item in the list
+                                       $lastIndex = $this->mHist[1]->getTimestamp();
+                                       # Display range
+                                       $this->mRange = array( 1, $numRows - 1 );
+                               } else {
+                                       # Index value of item past the index
+                                       $this->mPastTheEndIndex = $this->mHist[$numRows - 1]->getTimestamp();
+                                       # Index value of bottom item in the list
+                                       $lastIndex = $this->mHist[$numRows - 2]->getTimestamp();
+                                       # Display range
+                                       $this->mRange = array( 0, $numRows - 2 );
+                               }
+                       } else {
+                               # Setting indexes to an empty string means that they will be
+                               # omitted if they would otherwise appear in URLs. It just so
+                               # happens that this  is the right thing to do in the standard
+                               # UI, in all the relevant cases.
+                               $this->mPastTheEndIndex = '';
+                               # Index value of bottom item in the list
+                               $lastIndex = $this->mIsBackwards ?
+                                       $this->mHist[0]->getTimestamp() : $this->mHist[$numRows - 1]->getTimestamp();
+                               # Display range
+                               $this->mRange = array( 0, $numRows - 1 );
+                       }
+               } else {
+                       $firstIndex = '';
+                       $lastIndex = '';
+                       $this->mPastTheEndIndex = '';
+               }
+               if ( $this->mIsBackwards ) {
+                       $this->mIsFirst = ( $numRows < $queryLimit );
+                       $this->mIsLast = ( $this->mOffset == '' );
+                       $this->mLastShown = $firstIndex;
+                       $this->mFirstShown = $lastIndex;
+               } else {
+                       $this->mIsFirst = ( $this->mOffset == '' );
+                       $this->mIsLast = ( $numRows < $queryLimit );
+                       $this->mLastShown = $lastIndex;
+                       $this->mFirstShown = $firstIndex;
+               }
+               $this->mQueryDone = true;
+       }
+
+       /**
+        * @param bool $enable
+        */
+       protected function preventClickjacking( $enable = true ) {
+               $this->preventClickjacking = $enable;
+       }
+
+       /**
+        * @return bool
+        */
+       public function getPreventClickjacking() {
+               return $this->preventClickjacking;
+       }
+
+}
diff --git a/includes/page/WikiCategoryPage.php b/includes/page/WikiCategoryPage.php
new file mode 100644 (file)
index 0000000..d382001
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Special handling for category pages.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Special handling for category pages
+ */
+class WikiCategoryPage extends WikiPage {
+
+       /**
+        * Don't return a 404 for categories in use.
+        * In use defined as: either the actual page exists
+        * or the category currently has members.
+        *
+        * @return bool
+        */
+       public function hasViewableContent() {
+               if ( parent::hasViewableContent() ) {
+                       return true;
+               } else {
+                       $cat = Category::newFromTitle( $this->mTitle );
+                       // If any of these are not 0, then has members
+                       if ( $cat->getPageCount()
+                               || $cat->getSubcatCount()
+                               || $cat->getFileCount()
+                       ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+}
diff --git a/includes/page/WikiFilePage.php b/includes/page/WikiFilePage.php
new file mode 100644 (file)
index 0000000..34f15c3
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+/**
+ * Special handling for file pages.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Special handling for file pages
+ *
+ * @ingroup Media
+ */
+class WikiFilePage extends WikiPage {
+       /**
+        * @var File
+        */
+       protected $mFile = false;                               // !< File object
+       protected $mRepo = null;                            // !<
+       protected $mFileLoaded = false;             // !<
+       protected $mDupes = null;                               // !<
+
+       public function __construct( $title ) {
+               parent::__construct( $title );
+               $this->mDupes = null;
+               $this->mRepo = null;
+       }
+
+       public function getActionOverrides() {
+               $overrides = parent::getActionOverrides();
+               $overrides['revert'] = 'RevertFileAction';
+               return $overrides;
+       }
+
+       /**
+        * @param File $file
+        */
+       public function setFile( $file ) {
+               $this->mFile = $file;
+               $this->mFileLoaded = true;
+       }
+
+       /**
+        * @return bool
+        */
+       protected function loadFile() {
+               if ( $this->mFileLoaded ) {
+                       return true;
+               }
+               $this->mFileLoaded = true;
+
+               $this->mFile = wfFindFile( $this->mTitle );
+               if ( !$this->mFile ) {
+                       $this->mFile = wfLocalFile( $this->mTitle ); // always a File
+               }
+               $this->mRepo = $this->mFile->getRepo();
+               return true;
+       }
+
+       /**
+        * @return mixed|null|Title
+        */
+       public function getRedirectTarget() {
+               $this->loadFile();
+               if ( $this->mFile->isLocal() ) {
+                       return parent::getRedirectTarget();
+               }
+               // Foreign image page
+               $from = $this->mFile->getRedirected();
+               $to = $this->mFile->getName();
+               if ( $from == $to ) {
+                       return null;
+               }
+               $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
+               return $this->mRedirectTarget;
+       }
+
+       /**
+        * @return bool|mixed|Title
+        */
+       public function followRedirect() {
+               $this->loadFile();
+               if ( $this->mFile->isLocal() ) {
+                       return parent::followRedirect();
+               }
+               $from = $this->mFile->getRedirected();
+               $to = $this->mFile->getName();
+               if ( $from == $to ) {
+                       return false;
+               }
+               return Title::makeTitle( NS_FILE, $to );
+       }
+
+       /**
+        * @return bool
+        */
+       public function isRedirect() {
+               $this->loadFile();
+               if ( $this->mFile->isLocal() ) {
+                       return parent::isRedirect();
+               }
+
+               return (bool)$this->mFile->getRedirected();
+       }
+
+       /**
+        * @return bool
+        */
+       public function isLocal() {
+               $this->loadFile();
+               return $this->mFile->isLocal();
+       }
+
+       /**
+        * @return bool|File
+        */
+       public function getFile() {
+               $this->loadFile();
+               return $this->mFile;
+       }
+
+       /**
+        * @return array|null
+        */
+       public function getDuplicates() {
+               $this->loadFile();
+               if ( !is_null( $this->mDupes ) ) {
+                       return $this->mDupes;
+               }
+               $hash = $this->mFile->getSha1();
+               if ( !( $hash ) ) {
+                       $this->mDupes = array();
+                       return $this->mDupes;
+               }
+               $dupes = RepoGroup::singleton()->findBySha1( $hash );
+               // Remove duplicates with self and non matching file sizes
+               $self = $this->mFile->getRepoName() . ':' . $this->mFile->getName();
+               $size = $this->mFile->getSize();
+
+               /**
+                * @var $file File
+                */
+               foreach ( $dupes as $index => $file ) {
+                       $key = $file->getRepoName() . ':' . $file->getName();
+                       if ( $key == $self ) {
+                               unset( $dupes[$index] );
+                       }
+                       if ( $file->getSize() != $size ) {
+                               unset( $dupes[$index] );
+                       }
+               }
+               $this->mDupes = $dupes;
+               return $this->mDupes;
+       }
+
+       /**
+        * Override handling of action=purge
+        * @return bool
+        */
+       public function doPurge() {
+               $this->loadFile();
+               if ( $this->mFile->exists() ) {
+                       wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
+                       $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
+                       $update->doUpdate();
+                       $this->mFile->upgradeRow();
+                       $this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
+               } else {
+                       wfDebug( 'ImagePage::doPurge no image for '
+                               . $this->mFile->getName() . "; limiting purge to cache only\n" );
+                       // even if the file supposedly doesn't exist, force any cached information
+                       // to be updated (in case the cached information is wrong)
+                       $this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
+               }
+               if ( $this->mRepo ) {
+                       // Purge redirect cache
+                       $this->mRepo->invalidateImageRedirect( $this->mTitle );
+               }
+               return parent::doPurge();
+       }
+
+       /**
+        * Get the categories this file is a member of on the wiki where it was uploaded.
+        * For local files, this is the same as getCategories().
+        * For foreign API files (InstantCommons), this is not supported currently.
+        * Results will include hidden categories.
+        *
+        * @return TitleArray|Title[]
+        * @since 1.23
+        */
+       public function getForeignCategories() {
+               $this->loadFile();
+               $title = $this->mTitle;
+               $file = $this->mFile;
+
+               if ( ! $file instanceof LocalFile ) {
+                       wfDebug( __CLASS__ . '::' . __METHOD__ . " is not supported for this file\n" );
+                       return TitleArray::newFromResult( new FakeResultWrapper( array() ) );
+               }
+
+               /** @var LocalRepo $repo */
+               $repo = $file->getRepo();
+               $dbr = $repo->getSlaveDB();
+
+               $res = $dbr->select(
+                       array( 'page', 'categorylinks' ),
+                       array(
+                               'page_title' => 'cl_to',
+                               'page_namespace' => NS_CATEGORY,
+                       ),
+                       array(
+                               'page_namespace' => $title->getNamespace(),
+                               'page_title' => $title->getDBkey(),
+                       ),
+                       __METHOD__,
+                       array(),
+                       array( 'categorylinks' => array( 'INNER JOIN', 'page_id = cl_from' ) )
+               );
+
+               return TitleArray::newFromResult( $res );
+       }
+}
diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php
new file mode 100644 (file)
index 0000000..855de8e
--- /dev/null
@@ -0,0 +1,3570 @@
+<?php
+/**
+ * Base representation for a MediaWiki page.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
+ */
+interface Page {
+}
+
+/**
+ * Class representing a MediaWiki article and history.
+ *
+ * Some fields are public only for backwards-compatibility. Use accessors.
+ * In the past, this class was part of Article.php and everything was public.
+ *
+ * @internal documentation reviewed 15 Mar 2010
+ */
+class WikiPage implements Page, IDBAccessObject {
+       // Constants for $mDataLoadedFrom and related
+
+       /**
+        * @var Title
+        */
+       public $mTitle = null;
+
+       /**@{{
+        * @protected
+        */
+       public $mDataLoaded = false;         // !< Boolean
+       public $mIsRedirect = false;         // !< Boolean
+       public $mLatest = false;             // !< Integer (false means "not loaded")
+       /**@}}*/
+
+       /** @var stdclass Map of cache fields (text, parser output, ect) for a proposed/new edit */
+       public $mPreparedEdit = false;
+
+       /**
+        * @var int
+        */
+       protected $mId = null;
+
+       /**
+        * @var int One of the READ_* constants
+        */
+       protected $mDataLoadedFrom = self::READ_NONE;
+
+       /**
+        * @var Title
+        */
+       protected $mRedirectTarget = null;
+
+       /**
+        * @var Revision
+        */
+       protected $mLastRevision = null;
+
+       /**
+        * @var string Timestamp of the current revision or empty string if not loaded
+        */
+       protected $mTimestamp = '';
+
+       /**
+        * @var string
+        */
+       protected $mTouched = '19700101000000';
+
+       /**
+        * @var string
+        */
+       protected $mLinksUpdated = '19700101000000';
+
+       /**
+        * @var int|null
+        */
+       protected $mCounter = null;
+
+       /**
+        * Constructor and clear the article
+        * @param Title $title Reference to a Title object.
+        */
+       public function __construct( Title $title ) {
+               $this->mTitle = $title;
+       }
+
+       /**
+        * Create a WikiPage object of the appropriate class for the given title.
+        *
+        * @param Title $title
+        *
+        * @throws MWException
+        * @return WikiPage Object of the appropriate type
+        */
+       public static function factory( Title $title ) {
+               $ns = $title->getNamespace();
+
+               if ( $ns == NS_MEDIA ) {
+                       throw new MWException( "NS_MEDIA is a virtual namespace; use NS_FILE." );
+               } elseif ( $ns < 0 ) {
+                       throw new MWException( "Invalid or virtual namespace $ns given." );
+               }
+
+               switch ( $ns ) {
+                       case NS_FILE:
+                               $page = new WikiFilePage( $title );
+                               break;
+                       case NS_CATEGORY:
+                               $page = new WikiCategoryPage( $title );
+                               break;
+                       default:
+                               $page = new WikiPage( $title );
+               }
+
+               return $page;
+       }
+
+       /**
+        * Constructor from a page id
+        *
+        * @param int $id Article ID to load
+        * @param string|int $from One of the following values:
+        *        - "fromdb" or WikiPage::READ_NORMAL to select from a slave database
+        *        - "fromdbmaster" or WikiPage::READ_LATEST to select from the master database
+        *
+        * @return WikiPage|null
+        */
+       public static function newFromID( $id, $from = 'fromdb' ) {
+               // page id's are never 0 or negative, see bug 61166
+               if ( $id < 1 ) {
+                       return null;
+               }
+
+               $from = self::convertSelectType( $from );
+               $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
+               $row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
+               if ( !$row ) {
+                       return null;
+               }
+               return self::newFromRow( $row, $from );
+       }
+
+       /**
+        * Constructor from a database row
+        *
+        * @since 1.20
+        * @param object $row Database row containing at least fields returned by selectFields().
+        * @param string|int $from Source of $data:
+        *        - "fromdb" or WikiPage::READ_NORMAL: from a slave DB
+        *        - "fromdbmaster" or WikiPage::READ_LATEST: from the master DB
+        *        - "forupdate" or WikiPage::READ_LOCKING: from the master DB using SELECT FOR UPDATE
+        * @return WikiPage
+        */
+       public static function newFromRow( $row, $from = 'fromdb' ) {
+               $page = self::factory( Title::newFromRow( $row ) );
+               $page->loadFromRow( $row, $from );
+               return $page;
+       }
+
+       /**
+        * Convert 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
+        *
+        * @param object|string|int $type
+        * @return mixed
+        */
+       private static function convertSelectType( $type ) {
+               switch ( $type ) {
+               case 'fromdb':
+                       return self::READ_NORMAL;
+               case 'fromdbmaster':
+                       return self::READ_LATEST;
+               case 'forupdate':
+                       return self::READ_LOCKING;
+               default:
+                       // It may already be an integer or whatever else
+                       return $type;
+               }
+       }
+
+       /**
+        * Returns overrides for action handlers.
+        * Classes listed here will be used instead of the default one when
+        * (and only when) $wgActions[$action] === true. This allows subclasses
+        * to override the default behavior.
+        *
+        * @todo Move this UI stuff somewhere else
+        *
+        * @return array
+        */
+       public function getActionOverrides() {
+               $content_handler = $this->getContentHandler();
+               return $content_handler->getActionOverrides();
+       }
+
+       /**
+        * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
+        *
+        * Shorthand for ContentHandler::getForModelID( $this->getContentModel() );
+        *
+        * @return ContentHandler
+        *
+        * @since 1.21
+        */
+       public function getContentHandler() {
+               return ContentHandler::getForModelID( $this->getContentModel() );
+       }
+
+       /**
+        * Get the title object of the article
+        * @return Title Title object of this page
+        */
+       public function getTitle() {
+               return $this->mTitle;
+       }
+
+       /**
+        * Clear the object
+        * @return void
+        */
+       public function clear() {
+               $this->mDataLoaded = false;
+               $this->mDataLoadedFrom = self::READ_NONE;
+
+               $this->clearCacheFields();
+       }
+
+       /**
+        * Clear the object cache fields
+        * @return void
+        */
+       protected function clearCacheFields() {
+               $this->mId = null;
+               $this->mCounter = null;
+               $this->mRedirectTarget = null; // Title object if set
+               $this->mLastRevision = null; // Latest revision
+               $this->mTouched = '19700101000000';
+               $this->mLinksUpdated = '19700101000000';
+               $this->mTimestamp = '';
+               $this->mIsRedirect = false;
+               $this->mLatest = false;
+               // Bug 57026: do not clear mPreparedEdit since prepareTextForEdit() already checks
+               // the requested rev ID and content against the cached one for equality. For most
+               // content types, the output should not change during the lifetime of this cache.
+               // Clearing it can cause extra parses on edit for no reason.
+       }
+
+       /**
+        * Clear the mPreparedEdit cache field, as may be needed by mutable content types
+        * @return void
+        * @since 1.23
+        */
+       public function clearPreparedEdit() {
+               $this->mPreparedEdit = false;
+       }
+
+       /**
+        * Return the list of revision fields that should be selected to create
+        * a new page.
+        *
+        * @return array
+        */
+       public static function selectFields() {
+               global $wgContentHandlerUseDB;
+
+               $fields = array(
+                       'page_id',
+                       'page_namespace',
+                       'page_title',
+                       'page_restrictions',
+                       'page_counter',
+                       'page_is_redirect',
+                       'page_is_new',
+                       'page_random',
+                       'page_touched',
+                       'page_links_updated',
+                       'page_latest',
+                       'page_len',
+               );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'page_content_model';
+               }
+
+               return $fields;
+       }
+
+       /**
+        * Fetch a page record with the given conditions
+        * @param DatabaseBase $dbr
+        * @param array $conditions
+        * @param array $options
+        * @return object|bool Database result resource, or false on failure
+        */
+       protected function pageData( $dbr, $conditions, $options = array() ) {
+               $fields = self::selectFields();
+
+               wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
+
+               $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options );
+
+               wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
+
+               return $row;
+       }
+
+       /**
+        * Fetch a page record matching the Title object's namespace and title
+        * using a sanitized title string
+        *
+        * @param DatabaseBase $dbr
+        * @param Title $title
+        * @param array $options
+        * @return object|bool Database result resource, or false on failure
+        */
+       public function pageDataFromTitle( $dbr, $title, $options = array() ) {
+               return $this->pageData( $dbr, array(
+                       'page_namespace' => $title->getNamespace(),
+                       'page_title' => $title->getDBkey() ), $options );
+       }
+
+       /**
+        * Fetch a page record matching the requested ID
+        *
+        * @param DatabaseBase $dbr
+        * @param int $id
+        * @param array $options
+        * @return object|bool Database result resource, or false on failure
+        */
+       public function pageDataFromId( $dbr, $id, $options = array() ) {
+               return $this->pageData( $dbr, array( 'page_id' => $id ), $options );
+       }
+
+       /**
+        * Set the general counter, title etc data loaded from
+        * some source.
+        *
+        * @param object|string|int $from One of the following:
+        *   - A DB query result object.
+        *   - "fromdb" or WikiPage::READ_NORMAL to get from a slave DB.
+        *   - "fromdbmaster" or WikiPage::READ_LATEST to get from the master DB.
+        *   - "forupdate"  or WikiPage::READ_LOCKING to get from the master DB
+        *     using SELECT FOR UPDATE.
+        *
+        * @return void
+        */
+       public function loadPageData( $from = 'fromdb' ) {
+               $from = self::convertSelectType( $from );
+               if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
+                       // We already have the data from the correct location, no need to load it twice.
+                       return;
+               }
+
+               if ( $from === self::READ_LOCKING ) {
+                       $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle, array( 'FOR UPDATE' ) );
+               } elseif ( $from === self::READ_LATEST ) {
+                       $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
+               } elseif ( $from === self::READ_NORMAL ) {
+                       $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
+                       // Use a "last rev inserted" timestamp key to diminish the issue of slave lag.
+                       // Note that DB also stores the master position in the session and checks it.
+                       $touched = $this->getCachedLastEditTime();
+                       if ( $touched ) { // key set
+                               if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
+                                       $from = self::READ_LATEST;
+                                       $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
+                               }
+                       }
+               } else {
+                       // No idea from where the caller got this data, assume slave database.
+                       $data = $from;
+                       $from = self::READ_NORMAL;
+               }
+
+               $this->loadFromRow( $data, $from );
+       }
+
+       /**
+        * Load the object from a database row
+        *
+        * @since 1.20
+        * @param object $data Database row containing at least fields returned by selectFields()
+        * @param string|int $from One of the following:
+        *        - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
+        *        - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
+        *        - "forupdate"  or WikiPage::READ_LOCKING if the data comes from from
+        *          the master DB using SELECT FOR UPDATE
+        */
+       public function loadFromRow( $data, $from ) {
+               $lc = LinkCache::singleton();
+               $lc->clearLink( $this->mTitle );
+
+               if ( $data ) {
+                       $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
+
+                       $this->mTitle->loadFromRow( $data );
+
+                       // Old-fashioned restrictions
+                       $this->mTitle->loadRestrictions( $data->page_restrictions );
+
+                       $this->mId = intval( $data->page_id );
+                       $this->mCounter = intval( $data->page_counter );
+                       $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
+                       $this->mLinksUpdated = wfTimestampOrNull( TS_MW, $data->page_links_updated );
+                       $this->mIsRedirect = intval( $data->page_is_redirect );
+                       $this->mLatest = intval( $data->page_latest );
+                       // Bug 37225: $latest may no longer match the cached latest Revision object.
+                       // Double-check the ID of any cached latest Revision object for consistency.
+                       if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
+                               $this->mLastRevision = null;
+                               $this->mTimestamp = '';
+                       }
+               } else {
+                       $lc->addBadLinkObj( $this->mTitle );
+
+                       $this->mTitle->loadFromRow( false );
+
+                       $this->clearCacheFields();
+
+                       $this->mId = 0;
+               }
+
+               $this->mDataLoaded = true;
+               $this->mDataLoadedFrom = self::convertSelectType( $from );
+       }
+
+       /**
+        * @return int Page ID
+        */
+       public function getId() {
+               if ( !$this->mDataLoaded ) {
+                       $this->loadPageData();
+               }
+               return $this->mId;
+       }
+
+       /**
+        * @return bool Whether or not the page exists in the database
+        */
+       public function exists() {
+               if ( !$this->mDataLoaded ) {
+                       $this->loadPageData();
+               }
+               return $this->mId > 0;
+       }
+
+       /**
+        * Check if this page is something we're going to be showing
+        * some sort of sensible content for. If we return false, page
+        * views (plain action=view) will return an HTTP 404 response,
+        * so spiders and robots can know they're following a bad link.
+        *
+        * @return bool
+        */
+       public function hasViewableContent() {
+               return $this->exists() || $this->mTitle->isAlwaysKnown();
+       }
+
+       /**
+        * @return int The view count for the page
+        */
+       public function getCount() {
+               if ( !$this->mDataLoaded ) {
+                       $this->loadPageData();
+               }
+
+               return $this->mCounter;
+       }
+
+       /**
+        * Tests if the article content represents a redirect
+        *
+        * @return bool
+        */
+       public function isRedirect() {
+               $content = $this->getContent();
+               if ( !$content ) {
+                       return false;
+               }
+
+               return $content->isRedirect();
+       }
+
+       /**
+        * Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
+        *
+        * Will use the revisions actual content model if the page exists,
+        * and the page's default if the page doesn't exist yet.
+        *
+        * @return string
+        *
+        * @since 1.21
+        */
+       public function getContentModel() {
+               if ( $this->exists() ) {
+                       // look at the revision's actual content model
+                       $rev = $this->getRevision();
+
+                       if ( $rev !== null ) {
+                               return $rev->getContentModel();
+                       } else {
+                               $title = $this->mTitle->getPrefixedDBkey();
+                               wfWarn( "Page $title exists but has no (visible) revisions!" );
+                       }
+               }
+
+               // use the default model for this page
+               return $this->mTitle->getContentModel();
+       }
+
+       /**
+        * Loads page_touched and returns a value indicating if it should be used
+        * @return bool true if not a redirect
+        */
+       public function checkTouched() {
+               if ( !$this->mDataLoaded ) {
+                       $this->loadPageData();
+               }
+               return !$this->mIsRedirect;
+       }
+
+       /**
+        * Get the page_touched field
+        * @return string Containing GMT timestamp
+        */
+       public function getTouched() {
+               if ( !$this->mDataLoaded ) {
+                       $this->loadPageData();
+               }
+               return $this->mTouched;
+       }
+
+       /**
+        * Get the page_links_updated field
+        * @return string|null Containing GMT timestamp
+        */
+       public function getLinksTimestamp() {
+               if ( !$this->mDataLoaded ) {
+                       $this->loadPageData();
+               }
+               return $this->mLinksUpdated;
+       }
+
+       /**
+        * Get the page_latest field
+        * @return int rev_id of current revision
+        */
+       public function getLatest() {
+               if ( !$this->mDataLoaded ) {
+                       $this->loadPageData();
+               }
+               return (int)$this->mLatest;
+       }
+
+       /**
+        * Get the Revision object of the oldest revision
+        * @return Revision|null
+        */
+       public function getOldestRevision() {
+               wfProfileIn( __METHOD__ );
+
+               // Try using the slave database first, then try the master
+               $continue = 2;
+               $db = wfGetDB( DB_SLAVE );
+               $revSelectFields = Revision::selectFields();
+
+               $row = null;
+               while ( $continue ) {
+                       $row = $db->selectRow(
+                               array( 'page', 'revision' ),
+                               $revSelectFields,
+                               array(
+                                       'page_namespace' => $this->mTitle->getNamespace(),
+                                       'page_title' => $this->mTitle->getDBkey(),
+                                       'rev_page = page_id'
+                               ),
+                               __METHOD__,
+                               array(
+                                       'ORDER BY' => 'rev_timestamp ASC'
+                               )
+                       );
+
+                       if ( $row ) {
+                               $continue = 0;
+                       } else {
+                               $db = wfGetDB( DB_MASTER );
+                               $continue--;
+                       }
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $row ? Revision::newFromRow( $row ) : null;
+       }
+
+       /**
+        * Loads everything except the text
+        * This isn't necessary for all uses, so it's only done if needed.
+        */
+       protected function loadLastEdit() {
+               if ( $this->mLastRevision !== null ) {
+                       return; // already loaded
+               }
+
+               $latest = $this->getLatest();
+               if ( !$latest ) {
+                       return; // page doesn't exist or is missing page_latest info
+               }
+
+               // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the
+               // latest changes committed. This is true even within REPEATABLE-READ transactions, where
+               // S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to
+               // also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row
+               // UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT.
+               // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
+               $flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0;
+               $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
+               if ( $revision ) { // sanity
+                       $this->setLastEdit( $revision );
+               }
+       }
+
+       /**
+        * Set the latest revision
+        * @param Revision $revision
+        */
+       protected function setLastEdit( Revision $revision ) {
+               $this->mLastRevision = $revision;
+               $this->mTimestamp = $revision->getTimestamp();
+       }
+
+       /**
+        * Get the latest revision
+        * @return Revision|null
+        */
+       public function getRevision() {
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision;
+               }
+               return null;
+       }
+
+       /**
+        * Get the content of the current revision. No side-effects...
+        *
+        * @param int $audience int One of:
+        *   Revision::FOR_PUBLIC       to be displayed to all users
+        *   Revision::FOR_THIS_USER    to be displayed to $wgUser
+        *   Revision::RAW              get the text regardless of permissions
+        * @param User $user User object to check for, only if FOR_THIS_USER is passed
+        *   to the $audience parameter
+        * @return Content|null The content of the current revision
+        *
+        * @since 1.21
+        */
+       public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision->getContent( $audience, $user );
+               }
+               return null;
+       }
+
+       /**
+        * Get the text of the current revision. No side-effects...
+        *
+        * @param int $audience One of:
+        *   Revision::FOR_PUBLIC       to be displayed to all users
+        *   Revision::FOR_THIS_USER    to be displayed to the given user
+        *   Revision::RAW              get the text regardless of permissions
+        * @param User $user User object to check for, only if FOR_THIS_USER is passed
+        *   to the $audience parameter
+        * @return string|bool The text of the current revision
+        * @deprecated since 1.21, getContent() should be used instead.
+        */
+       public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision->getText( $audience, $user );
+               }
+               return false;
+       }
+
+       /**
+        * Get the text of the current revision. No side-effects...
+        *
+        * @return string|bool The text of the current revision. False on failure
+        * @deprecated since 1.21, getContent() should be used instead.
+        */
+       public function getRawText() {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+
+               return $this->getText( Revision::RAW );
+       }
+
+       /**
+        * @return string MW timestamp of last article revision
+        */
+       public function getTimestamp() {
+               // Check if the field has been filled by WikiPage::setTimestamp()
+               if ( !$this->mTimestamp ) {
+                       $this->loadLastEdit();
+               }
+
+               return wfTimestamp( TS_MW, $this->mTimestamp );
+       }
+
+       /**
+        * Set the page timestamp (use only to avoid DB queries)
+        * @param string $ts MW timestamp of last article revision
+        * @return void
+        */
+       public function setTimestamp( $ts ) {
+               $this->mTimestamp = wfTimestamp( TS_MW, $ts );
+       }
+
+       /**
+        * @param int $audience One of:
+        *   Revision::FOR_PUBLIC       to be displayed to all users
+        *   Revision::FOR_THIS_USER    to be displayed to the given user
+        *   Revision::RAW              get the text regardless of permissions
+        * @param User $user User object to check for, only if FOR_THIS_USER is passed
+        *   to the $audience parameter
+        * @return int user ID for the user that made the last article revision
+        */
+       public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision->getUser( $audience, $user );
+               } else {
+                       return -1;
+               }
+       }
+
+       /**
+        * Get the User object of the user who created the page
+        * @param int $audience One of:
+        *   Revision::FOR_PUBLIC       to be displayed to all users
+        *   Revision::FOR_THIS_USER    to be displayed to the given user
+        *   Revision::RAW              get the text regardless of permissions
+        * @param User $user User object to check for, only if FOR_THIS_USER is passed
+        *   to the $audience parameter
+        * @return User|null
+        */
+       public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               $revision = $this->getOldestRevision();
+               if ( $revision ) {
+                       $userName = $revision->getUserText( $audience, $user );
+                       return User::newFromName( $userName, false );
+               } else {
+                       return null;
+               }
+       }
+
+       /**
+        * @param int $audience One of:
+        *   Revision::FOR_PUBLIC       to be displayed to all users
+        *   Revision::FOR_THIS_USER    to be displayed to the given user
+        *   Revision::RAW              get the text regardless of permissions
+        * @param User $user User object to check for, only if FOR_THIS_USER is passed
+        *   to the $audience parameter
+        * @return string username of the user that made the last article revision
+        */
+       public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision->getUserText( $audience, $user );
+               } else {
+                       return '';
+               }
+       }
+
+       /**
+        * @param int $audience One of:
+        *   Revision::FOR_PUBLIC       to be displayed to all users
+        *   Revision::FOR_THIS_USER    to be displayed to the given user
+        *   Revision::RAW              get the text regardless of permissions
+        * @param User $user User object to check for, only if FOR_THIS_USER is passed
+        *   to the $audience parameter
+        * @return string Comment stored for the last article revision
+        */
+       public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision->getComment( $audience, $user );
+               } else {
+                       return '';
+               }
+       }
+
+       /**
+        * Returns true if last revision was marked as "minor edit"
+        *
+        * @return bool Minor edit indicator for the last article revision.
+        */
+       public function getMinorEdit() {
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision->isMinor();
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Get the cached timestamp for the last time the page changed.
+        * This is only used to help handle slave lag by comparing to page_touched.
+        * @return string MW timestamp
+        */
+       protected function getCachedLastEditTime() {
+               global $wgMemc;
+               $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
+               return $wgMemc->get( $key );
+       }
+
+       /**
+        * Set the cached timestamp for the last time the page changed.
+        * This is only used to help handle slave lag by comparing to page_touched.
+        * @param string $timestamp
+        * @return void
+        */
+       public function setCachedLastEditTime( $timestamp ) {
+               global $wgMemc;
+               $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
+               $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60 * 15 );
+       }
+
+       /**
+        * Determine whether a page would be suitable for being counted as an
+        * article in the site_stats table based on the title & its content
+        *
+        * @param object|bool $editInfo (false): object returned by prepareTextForEdit(),
+        *   if false, the current database state will be used
+        * @return bool
+        */
+       public function isCountable( $editInfo = false ) {
+               global $wgArticleCountMethod;
+
+               if ( !$this->mTitle->isContentPage() ) {
+                       return false;
+               }
+
+               if ( $editInfo ) {
+                       $content = $editInfo->pstContent;
+               } else {
+                       $content = $this->getContent();
+               }
+
+               if ( !$content || $content->isRedirect() ) {
+                       return false;
+               }
+
+               $hasLinks = null;
+
+               if ( $wgArticleCountMethod === 'link' ) {
+                       // nasty special case to avoid re-parsing to detect links
+
+                       if ( $editInfo ) {
+                               // ParserOutput::getLinks() is a 2D array of page links, so
+                               // to be really correct we would need to recurse in the array
+                               // but the main array should only have items in it if there are
+                               // links.
+                               $hasLinks = (bool)count( $editInfo->output->getLinks() );
+                       } else {
+                               $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+                                       array( 'pl_from' => $this->getId() ), __METHOD__ );
+                       }
+               }
+
+               return $content->isCountable( $hasLinks );
+       }
+
+       /**
+        * If this page is a redirect, get its target
+        *
+        * The target will be fetched from the redirect table if possible.
+        * If this page doesn't have an entry there, call insertRedirect()
+        * @return Title|null Title object, or null if this page is not a redirect
+        */
+       public function getRedirectTarget() {
+               if ( !$this->mTitle->isRedirect() ) {
+                       return null;
+               }
+
+               if ( $this->mRedirectTarget !== null ) {
+                       return $this->mRedirectTarget;
+               }
+
+               // Query the redirect table
+               $dbr = wfGetDB( DB_SLAVE );
+               $row = $dbr->selectRow( 'redirect',
+                       array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
+                       array( 'rd_from' => $this->getId() ),
+                       __METHOD__
+               );
+
+               // rd_fragment and rd_interwiki were added later, populate them if empty
+               if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
+                       $this->mRedirectTarget = Title::makeTitle(
+                               $row->rd_namespace, $row->rd_title,
+                               $row->rd_fragment, $row->rd_interwiki );
+                       return $this->mRedirectTarget;
+               }
+
+               // This page doesn't have an entry in the redirect table
+               $this->mRedirectTarget = $this->insertRedirect();
+               return $this->mRedirectTarget;
+       }
+
+       /**
+        * Insert an entry for this page into the redirect table.
+        *
+        * Don't call this function directly unless you know what you're doing.
+        * @return Title|null Title object or null if not a redirect
+        */
+       public function insertRedirect() {
+               // recurse through to only get the final target
+               $content = $this->getContent();
+               $retval = $content ? $content->getUltimateRedirectTarget() : null;
+               if ( !$retval ) {
+                       return null;
+               }
+               $this->insertRedirectEntry( $retval );
+               return $retval;
+       }
+
+       /**
+        * Insert or update the redirect table entry for this page to indicate
+        * it redirects to $rt .
+        * @param Title $rt Redirect target
+        */
+       public function insertRedirectEntry( $rt ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->replace( 'redirect', array( 'rd_from' ),
+                       array(
+                               'rd_from' => $this->getId(),
+                               'rd_namespace' => $rt->getNamespace(),
+                               'rd_title' => $rt->getDBkey(),
+                               'rd_fragment' => $rt->getFragment(),
+                               'rd_interwiki' => $rt->getInterwiki(),
+                       ),
+                       __METHOD__
+               );
+       }
+
+       /**
+        * Get the Title object or URL this page redirects to
+        *
+        * @return bool|Title|string false, Title of in-wiki target, or string with URL
+        */
+       public function followRedirect() {
+               return $this->getRedirectURL( $this->getRedirectTarget() );
+       }
+
+       /**
+        * Get the Title object or URL to use for a redirect. We use Title
+        * objects for same-wiki, non-special redirects and URLs for everything
+        * else.
+        * @param Title $rt Redirect target
+        * @return bool|Title|string false, Title object of local target, or string with URL
+        */
+       public function getRedirectURL( $rt ) {
+               if ( !$rt ) {
+                       return false;
+               }
+
+               if ( $rt->isExternal() ) {
+                       if ( $rt->isLocal() ) {
+                               // Offsite wikis need an HTTP redirect.
+                               //
+                               // This can be hard to reverse and may produce loops,
+                               // so they may be disabled in the site configuration.
+                               $source = $this->mTitle->getFullURL( 'redirect=no' );
+                               return $rt->getFullURL( array( 'rdfrom' => $source ) );
+                       } else {
+                               // External pages pages without "local" bit set are not valid
+                               // redirect targets
+                               return false;
+                       }
+               }
+
+               if ( $rt->isSpecialPage() ) {
+                       // Gotta handle redirects to special pages differently:
+                       // Fill the HTTP response "Location" header and ignore
+                       // the rest of the page we're on.
+                       //
+                       // Some pages are not valid targets
+                       if ( $rt->isValidRedirectTarget() ) {
+                               return $rt->getFullURL();
+                       } else {
+                               return false;
+                       }
+               }
+
+               return $rt;
+       }
+
+       /**
+        * Get a list of users who have edited this article, not including the user who made
+        * the most recent revision, which you can get from $article->getUser() if you want it
+        * @return UserArrayFromResult
+        */
+       public function getContributors() {
+               // @todo FIXME: This is expensive; cache this info somewhere.
+
+               $dbr = wfGetDB( DB_SLAVE );
+
+               if ( $dbr->implicitGroupby() ) {
+                       $realNameField = 'user_real_name';
+               } else {
+                       $realNameField = 'MIN(user_real_name) AS user_real_name';
+               }
+
+               $tables = array( 'revision', 'user' );
+
+               $fields = array(
+                       'user_id' => 'rev_user',
+                       'user_name' => 'rev_user_text',
+                       $realNameField,
+                       'timestamp' => 'MAX(rev_timestamp)',
+               );
+
+               $conds = array( 'rev_page' => $this->getId() );
+
+               // The user who made the top revision gets credited as "this page was last edited by
+               // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
+               $user = $this->getUser();
+               if ( $user ) {
+                       $conds[] = "rev_user != $user";
+               } else {
+                       $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
+               }
+
+               $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
+
+               $jconds = array(
+                       'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
+               );
+
+               $options = array(
+                       'GROUP BY' => array( 'rev_user', 'rev_user_text' ),
+                       'ORDER BY' => 'timestamp DESC',
+               );
+
+               $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
+               return new UserArrayFromResult( $res );
+       }
+
+       /**
+        * Get the last N authors
+        * @param int $num Number of revisions to get
+        * @param int|string $revLatest The latest rev_id, selected from the master (optional)
+        * @return array Array of authors, duplicates not removed
+        */
+       public function getLastNAuthors( $num, $revLatest = 0 ) {
+               wfProfileIn( __METHOD__ );
+               // First try the slave
+               // If that doesn't have the latest revision, try the master
+               $continue = 2;
+               $db = wfGetDB( DB_SLAVE );
+
+               do {
+                       $res = $db->select( array( 'page', 'revision' ),
+                               array( 'rev_id', 'rev_user_text' ),
+                               array(
+                                       'page_namespace' => $this->mTitle->getNamespace(),
+                                       'page_title' => $this->mTitle->getDBkey(),
+                                       'rev_page = page_id'
+                               ), __METHOD__,
+                               array(
+                                       'ORDER BY' => 'rev_timestamp DESC',
+                                       'LIMIT' => $num
+                               )
+                       );
+
+                       if ( !$res ) {
+                               wfProfileOut( __METHOD__ );
+                               return array();
+                       }
+
+                       $row = $db->fetchObject( $res );
+
+                       if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
+                               $db = wfGetDB( DB_MASTER );
+                               $continue--;
+                       } else {
+                               $continue = 0;
+                       }
+               } while ( $continue );
+
+               $authors = array( $row->rev_user_text );
+
+               foreach ( $res as $row ) {
+                       $authors[] = $row->rev_user_text;
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $authors;
+       }
+
+       /**
+        * Should the parser cache be used?
+        *
+        * @param ParserOptions $parserOptions ParserOptions to check
+        * @param int $oldid
+        * @return bool
+        */
+       public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
+               global $wgEnableParserCache;
+
+               return $wgEnableParserCache
+                       && $parserOptions->getStubThreshold() == 0
+                       && $this->exists()
+                       && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
+                       && $this->getContentHandler()->isParserCacheSupported();
+       }
+
+       /**
+        * Get a ParserOutput for the given ParserOptions and revision ID.
+        * The parser cache will be used if possible.
+        *
+        * @since 1.19
+        * @param ParserOptions $parserOptions ParserOptions to use for the parse operation
+        * @param null|int $oldid Revision ID to get the text from, passing null or 0 will
+        *   get the current revision (default value)
+        *
+        * @return ParserOutput|bool ParserOutput or false if the revision was not found
+        */
+       public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
+               wfProfileIn( __METHOD__ );
+
+               $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
+               wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
+               if ( $parserOptions->getStubThreshold() ) {
+                       wfIncrStats( 'pcache_miss_stub' );
+               }
+
+               if ( $useParserCache ) {
+                       $parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
+                       if ( $parserOutput !== false ) {
+                               wfProfileOut( __METHOD__ );
+                               return $parserOutput;
+                       }
+               }
+
+               if ( $oldid === null || $oldid === 0 ) {
+                       $oldid = $this->getLatest();
+               }
+
+               $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
+               $pool->execute();
+
+               wfProfileOut( __METHOD__ );
+
+               return $pool->getParserOutput();
+       }
+
+       /**
+        * Do standard deferred updates after page view (existing or missing page)
+        * @param User $user The relevant user
+        * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
+        */
+       public function doViewUpdates( User $user, $oldid = 0 ) {
+               global $wgDisableCounters;
+               if ( wfReadOnly() ) {
+                       return;
+               }
+
+               // Don't update page view counters on views from bot users (bug 14044)
+               if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->exists() ) {
+                       DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
+                       DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
+               }
+
+               // Update newtalk / watchlist notification status
+               $user->clearNotification( $this->mTitle, $oldid );
+       }
+
+       /**
+        * Perform the actions of a page purging
+        * @return bool
+        */
+       public function doPurge() {
+               global $wgUseSquid;
+
+               if ( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
+                       return false;
+               }
+
+               // Invalidate the cache
+               $this->mTitle->invalidateCache();
+
+               if ( $wgUseSquid ) {
+                       // Commit the transaction before the purge is sent
+                       $dbw = wfGetDB( DB_MASTER );
+                       $dbw->commit( __METHOD__ );
+
+                       // Send purge
+                       $update = SquidUpdate::newSimplePurge( $this->mTitle );
+                       $update->doUpdate();
+               }
+
+               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+                       // @todo move this logic to MessageCache
+
+                       if ( $this->exists() ) {
+                               // NOTE: use transclusion text for messages.
+                               //       This is consistent with  MessageCache::getMsgFromNamespace()
+
+                               $content = $this->getContent();
+                               $text = $content === null ? null : $content->getWikitextForTransclusion();
+
+                               if ( $text === null ) {
+                                       $text = false;
+                               }
+                       } else {
+                               $text = false;
+                       }
+
+                       MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
+               }
+               return true;
+       }
+
+       /**
+        * Insert a new empty page record for this article.
+        * This *must* be followed up by creating a revision
+        * and running $this->updateRevisionOn( ... );
+        * or else the record will be left in a funky state.
+        * Best if all done inside a transaction.
+        *
+        * @param DatabaseBase $dbw
+        * @return int The newly created page_id key, or false if the title already existed
+        */
+       public function insertOn( $dbw ) {
+               wfProfileIn( __METHOD__ );
+
+               $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+               $dbw->insert( 'page', array(
+                       'page_id'           => $page_id,
+                       'page_namespace'    => $this->mTitle->getNamespace(),
+                       'page_title'        => $this->mTitle->getDBkey(),
+                       'page_counter'      => 0,
+                       'page_restrictions' => '',
+                       'page_is_redirect'  => 0, // Will set this shortly...
+                       'page_is_new'       => 1,
+                       'page_random'       => wfRandom(),
+                       'page_touched'      => $dbw->timestamp(),
+                       'page_latest'       => 0, // Fill this in shortly...
+                       'page_len'          => 0, // Fill this in shortly...
+               ), __METHOD__, 'IGNORE' );
+
+               $affected = $dbw->affectedRows();
+
+               if ( $affected ) {
+                       $newid = $dbw->insertId();
+                       $this->mId = $newid;
+                       $this->mTitle->resetArticleID( $newid );
+               }
+               wfProfileOut( __METHOD__ );
+
+               return $affected ? $newid : false;
+       }
+
+       /**
+        * Update the page record to point to a newly saved revision.
+        *
+        * @param DatabaseBase $dbw
+        * @param Revision $revision For ID number, and text used to set
+        *   length and redirect status fields
+        * @param int $lastRevision If given, will not overwrite the page field
+        *   when different from the currently set value.
+        *   Giving 0 indicates the new page flag should be set on.
+        * @param bool $lastRevIsRedirect If given, will optimize adding and
+        *   removing rows in redirect table.
+        * @return bool true on success, false on failure
+        */
+       public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
+               $lastRevIsRedirect = null
+       ) {
+               global $wgContentHandlerUseDB;
+
+               wfProfileIn( __METHOD__ );
+
+               $content = $revision->getContent();
+               $len = $content ? $content->getSize() : 0;
+               $rt = $content ? $content->getUltimateRedirectTarget() : null;
+
+               $conditions = array( 'page_id' => $this->getId() );
+
+               if ( !is_null( $lastRevision ) ) {
+                       // An extra check against threads stepping on each other
+                       $conditions['page_latest'] = $lastRevision;
+               }
+
+               $now = wfTimestampNow();
+               $row = array( /* SET */
+                       'page_latest'      => $revision->getId(),
+                       'page_touched'     => $dbw->timestamp( $now ),
+                       'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
+                       'page_is_redirect' => $rt !== null ? 1 : 0,
+                       'page_len'         => $len,
+               );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $row['page_content_model'] = $revision->getContentModel();
+               }
+
+               $dbw->update( 'page',
+                       $row,
+                       $conditions,
+                       __METHOD__ );
+
+               $result = $dbw->affectedRows() > 0;
+               if ( $result ) {
+                       $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
+                       $this->setLastEdit( $revision );
+                       $this->setCachedLastEditTime( $now );
+                       $this->mLatest = $revision->getId();
+                       $this->mIsRedirect = (bool)$rt;
+                       // Update the LinkCache.
+                       LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
+                                                                                                       $this->mLatest, $revision->getContentModel() );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $result;
+       }
+
+       /**
+        * Add row to the redirect table if this is a redirect, remove otherwise.
+        *
+        * @param DatabaseBase $dbw
+        * @param Title $redirectTitle Title object pointing to the redirect target,
+        *   or NULL if this is not a redirect
+        * @param null|bool $lastRevIsRedirect If given, will optimize adding and
+        *   removing rows in redirect table.
+        * @return bool true on success, false on failure
+        * @private
+        */
+       public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
+               // Always update redirects (target link might have changed)
+               // Update/Insert if we don't know if the last revision was a redirect or not
+               // Delete if changing from redirect to non-redirect
+               $isRedirect = !is_null( $redirectTitle );
+
+               if ( !$isRedirect && $lastRevIsRedirect === false ) {
+                       return true;
+               }
+
+               wfProfileIn( __METHOD__ );
+               if ( $isRedirect ) {
+                       $this->insertRedirectEntry( $redirectTitle );
+               } else {
+                       // This is not a redirect, remove row from redirect table
+                       $where = array( 'rd_from' => $this->getId() );
+                       $dbw->delete( 'redirect', $where, __METHOD__ );
+               }
+
+               if ( $this->getTitle()->getNamespace() == NS_FILE ) {
+                       RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
+               }
+               wfProfileOut( __METHOD__ );
+
+               return ( $dbw->affectedRows() != 0 );
+       }
+
+       /**
+        * If the given revision is newer than the currently set page_latest,
+        * update the page record. Otherwise, do nothing.
+        *
+        * @deprecated since 1.24, use updateRevisionOn instead
+        *
+        * @param DatabaseBase $dbw
+        * @param Revision $revision
+        * @return bool
+        */
+       public function updateIfNewerOn( $dbw, $revision ) {
+               wfProfileIn( __METHOD__ );
+
+               $row = $dbw->selectRow(
+                       array( 'revision', 'page' ),
+                       array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
+                       array(
+                               'page_id' => $this->getId(),
+                               'page_latest=rev_id' ),
+                       __METHOD__ );
+
+               if ( $row ) {
+                       if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
+                               wfProfileOut( __METHOD__ );
+                               return false;
+                       }
+                       $prev = $row->rev_id;
+                       $lastRevIsRedirect = (bool)$row->page_is_redirect;
+               } else {
+                       // No or missing previous revision; mark the page as new
+                       $prev = 0;
+                       $lastRevIsRedirect = null;
+               }
+
+               $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
+
+               wfProfileOut( __METHOD__ );
+               return $ret;
+       }
+
+       /**
+        * Get the content that needs to be saved in order to undo all revisions
+        * between $undo and $undoafter. Revisions must belong to the same page,
+        * must exist and must not be deleted
+        * @param Revision $undo
+        * @param Revision $undoafter Must be an earlier revision than $undo
+        * @return mixed string on success, false on failure
+        * @since 1.21
+        * Before we had the Content object, this was done in getUndoText
+        */
+       public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
+               $handler = $undo->getContentHandler();
+               return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
+       }
+
+       /**
+        * Get the text that needs to be saved in order to undo all revisions
+        * between $undo and $undoafter. Revisions must belong to the same page,
+        * must exist and must not be deleted
+        * @param Revision $undo
+        * @param Revision $undoafter Must be an earlier revision than $undo
+        * @return string|bool string on success, false on failure
+        * @deprecated since 1.21: use ContentHandler::getUndoContent() instead.
+        */
+       public function getUndoText( Revision $undo, Revision $undoafter = null ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+
+               $this->loadLastEdit();
+
+               if ( $this->mLastRevision ) {
+                       if ( is_null( $undoafter ) ) {
+                               $undoafter = $undo->getPrevious();
+                       }
+
+                       $handler = $this->getContentHandler();
+                       $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
+
+                       if ( !$undone ) {
+                               return false;
+                       } else {
+                               return ContentHandler::getContentText( $undone );
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * @param string|number|null|bool $sectionId Section identifier as a number or string
+        * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
+        * or 'new' for a new section.
+        * @param string $text New text of the section.
+        * @param string $sectionTitle New section's subject, only if $section is "new".
+        * @param string $edittime Revision timestamp or null to use the current revision.
+        *
+        * @throws MWException
+        * @return string New complete article text, or null if error.
+        *
+        * @deprecated since 1.21, use replaceSectionAtRev() instead
+        */
+       public function replaceSection( $sectionId, $text, $sectionTitle = '',
+               $edittime = null
+       ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+
+               //NOTE: keep condition in sync with condition in replaceSectionContent!
+               if ( strval( $sectionId ) === '' ) {
+                       // Whole-page edit; let the whole text through
+                       return $text;
+               }
+
+               if ( !$this->supportsSections() ) {
+                       throw new MWException( "sections not supported for content model " .
+                               $this->getContentHandler()->getModelID() );
+               }
+
+               // could even make section title, but that's not required.
+               $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
+
+               $newContent = $this->replaceSectionContent( $sectionId, $sectionContent, $sectionTitle,
+                       $edittime );
+
+               return ContentHandler::getContentText( $newContent );
+       }
+
+       /**
+        * Returns true if this page's content model supports sections.
+        *
+        * @return bool
+        *
+        * @todo The skin should check this and not offer section functionality if
+        *   sections are not supported.
+        * @todo The EditPage should check this and not offer section functionality
+        *   if sections are not supported.
+        */
+       public function supportsSections() {
+               return $this->getContentHandler()->supportsSections();
+       }
+
+       /**
+        * @param string|number|null|bool $sectionId Section identifier as a number or string
+        * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
+        * or 'new' for a new section.
+        * @param Content $sectionContent New content of the section.
+        * @param string $sectionTitle New section's subject, only if $section is "new".
+        * @param string $edittime Revision timestamp or null to use the current revision.
+        *
+        * @throws MWException
+        * @return Content New complete article content, or null if error.
+        *
+        * @since 1.21
+        * @deprecated since 1.24, use replaceSectionAtRev instead
+        */
+       public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
+               $edittime = null ) {
+               wfProfileIn( __METHOD__ );
+
+               $baseRevId = null;
+               if ( $edittime && $sectionId !== 'new' ) {
+                       $dbw = wfGetDB( DB_MASTER );
+                       $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
+                       if ( $rev ) {
+                               $baseRevId = $rev->getId();
+                       }
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
+       }
+
+       /**
+        * @param string|number|null|bool $sectionId Section identifier as a number or string
+        * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page
+        * or 'new' for a new section.
+        * @param Content $sectionContent New content of the section.
+        * @param string $sectionTitle New section's subject, only if $section is "new".
+        * @param string $baseRevId integer|null
+        *
+        * @throws MWException
+        * @return Content New complete article content, or null if error.
+        *
+        * @since 1.24
+        */
+       public function replaceSectionAtRev( $sectionId, Content $sectionContent,
+               $sectionTitle = '', $baseRevId = null
+       ) {
+               wfProfileIn( __METHOD__ );
+
+               if ( strval( $sectionId ) === '' ) {
+                       // Whole-page edit; let the whole text through
+                       $newContent = $sectionContent;
+               } else {
+                       if ( !$this->supportsSections() ) {
+                               wfProfileOut( __METHOD__ );
+                               throw new MWException( "sections not supported for content model " .
+                                       $this->getContentHandler()->getModelID() );
+                       }
+
+                       // Bug 30711: always use current version when adding a new section
+                       if ( is_null( $baseRevId ) || $sectionId === 'new' ) {
+                               $oldContent = $this->getContent();
+                       } else {
+                               // TODO: try DB_SLAVE first
+                               $dbw = wfGetDB( DB_MASTER );
+                               $rev = Revision::loadFromId( $dbw, $baseRevId );
+
+                               if ( !$rev ) {
+                                       wfDebug( __METHOD__ . " asked for bogus section (page: " .
+                                               $this->getId() . "; section: $sectionId)\n" );
+                                       wfProfileOut( __METHOD__ );
+                                       return null;
+                               }
+
+                               $oldContent = $rev->getContent();
+                       }
+
+                       if ( ! $oldContent ) {
+                               wfDebug( __METHOD__ . ": no page text\n" );
+                               wfProfileOut( __METHOD__ );
+                               return null;
+                       }
+
+                       $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $newContent;
+       }
+
+       /**
+        * Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
+        * @param int $flags
+        * @return int Updated $flags
+        */
+       public function checkFlags( $flags ) {
+               if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
+                       if ( $this->exists() ) {
+                               $flags |= EDIT_UPDATE;
+                       } else {
+                               $flags |= EDIT_NEW;
+                       }
+               }
+
+               return $flags;
+       }
+
+       /**
+        * Change an existing article or create a new article. Updates RC and all necessary caches,
+        * optionally via the deferred update array.
+        *
+        * @param string $text New text
+        * @param string $summary Edit summary
+        * @param int $flags Bitfield:
+        *      EDIT_NEW
+        *          Article is known or assumed to be non-existent, create a new one
+        *      EDIT_UPDATE
+        *          Article is known or assumed to be pre-existing, update it
+        *      EDIT_MINOR
+        *          Mark this edit minor, if the user is allowed to do so
+        *      EDIT_SUPPRESS_RC
+        *          Do not log the change in recentchanges
+        *      EDIT_FORCE_BOT
+        *          Mark the edit a "bot" edit regardless of user rights
+        *      EDIT_DEFER_UPDATES
+        *          Defer some of the updates until the end of index.php
+        *      EDIT_AUTOSUMMARY
+        *          Fill in blank summaries with generated text where possible
+        *
+        * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the
+        * article will be detected. If EDIT_UPDATE is specified and the article
+        * doesn't exist, the function will return an edit-gone-missing error. If
+        * EDIT_NEW is specified and the article does exist, an edit-already-exists
+        * error will be returned. These two conditions are also possible with
+        * auto-detection due to MediaWiki's performance-optimised locking strategy.
+        *
+        * @param bool|int $baseRevId The revision ID this edit was based off, if any
+        * @param User $user The user doing the edit
+        *
+        * @throws MWException
+        * @return Status object. Possible errors:
+        *   edit-hook-aborted: The ArticleSave hook aborted the edit but didn't
+        *     set the fatal flag of $status
+        *   edit-gone-missing: In update mode, but the article didn't exist.
+        *   edit-conflict: In update mode, the article changed unexpectedly.
+        *   edit-no-change: Warning that the text was the same as before.
+        *   edit-already-exists: In creation mode, but the article already exists.
+        *
+        * Extensions may define additional errors.
+        *
+        * $return->value will contain an associative array with members as follows:
+        *     new: Boolean indicating if the function attempted to create a new article.
+        *     revision: The revision object for the inserted revision, or null.
+        *
+        * Compatibility note: this function previously returned a boolean value
+        * indicating success/failure
+        *
+        * @deprecated since 1.21: use doEditContent() instead.
+        */
+       public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+
+               $content = ContentHandler::makeContent( $text, $this->getTitle() );
+
+               return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
+       }
+
+       /**
+        * Change an existing article or create a new article. Updates RC and all necessary caches,
+        * optionally via the deferred update array.
+        *
+        * @param Content $content New content
+        * @param string $summary Edit summary
+        * @param int $flags Bitfield:
+        *      EDIT_NEW
+        *          Article is known or assumed to be non-existent, create a new one
+        *      EDIT_UPDATE
+        *          Article is known or assumed to be pre-existing, update it
+        *      EDIT_MINOR
+        *          Mark this edit minor, if the user is allowed to do so
+        *      EDIT_SUPPRESS_RC
+        *          Do not log the change in recentchanges
+        *      EDIT_FORCE_BOT
+        *          Mark the edit a "bot" edit regardless of user rights
+        *      EDIT_DEFER_UPDATES
+        *          Defer some of the updates until the end of index.php
+        *      EDIT_AUTOSUMMARY
+        *          Fill in blank summaries with generated text where possible
+        *
+        * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the
+        * article will be detected. If EDIT_UPDATE is specified and the article
+        * doesn't exist, the function will return an edit-gone-missing error. If
+        * EDIT_NEW is specified and the article does exist, an edit-already-exists
+        * error will be returned. These two conditions are also possible with
+        * auto-detection due to MediaWiki's performance-optimised locking strategy.
+        *
+        * @param bool|int $baseRevId The revision ID this edit was based off, if any
+        * @param User $user The user doing the edit
+        * @param string $serialisation_format Format for storing the content in the
+        *   database.
+        *
+        * @throws MWException
+        * @return Status object. Possible errors:
+        *     edit-hook-aborted: The ArticleSave hook aborted the edit but didn't
+        *       set the fatal flag of $status.
+        *     edit-gone-missing: In update mode, but the article didn't exist.
+        *     edit-conflict: In update mode, the article changed unexpectedly.
+        *     edit-no-change: Warning that the text was the same as before.
+        *     edit-already-exists: In creation mode, but the article already exists.
+        *
+        *  Extensions may define additional errors.
+        *
+        *  $return->value will contain an associative array with members as follows:
+        *     new: Boolean indicating if the function attempted to create a new article.
+        *     revision: The revision object for the inserted revision, or null.
+        *
+        * @since 1.21
+        */
+       public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
+               User $user = null, $serialisation_format = null
+       ) {
+               global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
+
+               // Low-level sanity check
+               if ( $this->mTitle->getText() === '' ) {
+                       throw new MWException( 'Something is trying to edit an article with an empty title' );
+               }
+
+               wfProfileIn( __METHOD__ );
+
+               if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
+                       wfProfileOut( __METHOD__ );
+                       return Status::newFatal( 'content-not-allowed-here',
+                               ContentHandler::getLocalizedName( $content->getModel() ),
+                               $this->getTitle()->getPrefixedText() );
+               }
+
+               $user = is_null( $user ) ? $wgUser : $user;
+               $status = Status::newGood( array() );
+
+               // Load the data from the master database if needed.
+               // The caller may already loaded it from the master or even loaded it using
+               // SELECT FOR UPDATE, so do not override that using clear().
+               $this->loadPageData( 'fromdbmaster' );
+
+               $flags = $this->checkFlags( $flags );
+
+               // handle hook
+               $hook_args = array( &$this, &$user, &$content, &$summary,
+                                                       $flags & EDIT_MINOR, null, null, &$flags, &$status );
+
+               if ( !wfRunHooks( 'PageContentSave', $hook_args )
+                       || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
+
+                       wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
+
+                       if ( $status->isOK() ) {
+                               $status->fatal( 'edit-hook-aborted' );
+                       }
+
+                       wfProfileOut( __METHOD__ );
+                       return $status;
+               }
+
+               // Silently ignore EDIT_MINOR if not allowed
+               $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
+               $bot = $flags & EDIT_FORCE_BOT;
+
+               $old_content = $this->getContent( Revision::RAW ); // current revision's content
+
+               $oldsize = $old_content ? $old_content->getSize() : 0;
+               $oldid = $this->getLatest();
+               $oldIsRedirect = $this->isRedirect();
+               $oldcountable = $this->isCountable();
+
+               $handler = $content->getContentHandler();
+
+               // Provide autosummaries if one is not provided and autosummaries are enabled.
+               if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
+                       if ( !$old_content ) {
+                               $old_content = null;
+                       }
+                       $summary = $handler->getAutosummary( $old_content, $content, $flags );
+               }
+
+               $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
+               $serialized = $editInfo->pst;
+
+               /**
+                * @var Content $content
+                */
+               $content = $editInfo->pstContent;
+               $newsize = $content->getSize();
+
+               $dbw = wfGetDB( DB_MASTER );
+               $now = wfTimestampNow();
+               $this->mTimestamp = $now;
+
+               if ( $flags & EDIT_UPDATE ) {
+                       // Update article, but only if changed.
+                       $status->value['new'] = false;
+
+                       if ( !$oldid ) {
+                               // Article gone missing
+                               wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
+                               $status->fatal( 'edit-gone-missing' );
+
+                               wfProfileOut( __METHOD__ );
+                               return $status;
+                       } elseif ( !$old_content ) {
+                               // Sanity check for bug 37225
+                               wfProfileOut( __METHOD__ );
+                               throw new MWException( "Could not find text for current revision {$oldid}." );
+                       }
+
+                       $revision = new Revision( array(
+                               'page'       => $this->getId(),
+                               'title'      => $this->getTitle(), // for determining the default content model
+                               'comment'    => $summary,
+                               'minor_edit' => $isminor,
+                               'text'       => $serialized,
+                               'len'        => $newsize,
+                               'parent_id'  => $oldid,
+                               'user'       => $user->getId(),
+                               'user_text'  => $user->getName(),
+                               'timestamp'  => $now,
+                               'content_model' => $content->getModel(),
+                               'content_format' => $serialisation_format,
+                       ) ); // XXX: pass content object?!
+
+                       $changed = !$content->equals( $old_content );
+
+                       if ( $changed ) {
+                               if ( !$content->isValid() ) {
+                                       wfProfileOut( __METHOD__ );
+                                       throw new MWException( "New content failed validity check!" );
+                               }
+
+                               $dbw->begin( __METHOD__ );
+                               try {
+
+                                       $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+                                       $status->merge( $prepStatus );
+
+                                       if ( !$status->isOK() ) {
+                                               $dbw->rollback( __METHOD__ );
+
+                                               wfProfileOut( __METHOD__ );
+                                               return $status;
+                                       }
+                                       $revisionId = $revision->insertOn( $dbw );
+
+                                       // Update page
+                                       //
+                                       // We check for conflicts by comparing $oldid with the current latest revision ID.
+                                       $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
+
+                                       if ( !$ok ) {
+                                               // Belated edit conflict! Run away!!
+                                               $status->fatal( 'edit-conflict' );
+
+                                               $dbw->rollback( __METHOD__ );
+
+                                               wfProfileOut( __METHOD__ );
+                                               return $status;
+                                       }
+
+                                       wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
+                                       // Update recentchanges
+                                       if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
+                                               // Mark as patrolled if the user can do so
+                                               $patrolled = $wgUseRCPatrol && !count(
+                                               $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
+                                               // Add RC row to the DB
+                                               $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
+                                                       $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
+                                                       $revisionId, $patrolled
+                                               );
+
+                                               // Log auto-patrolled edits
+                                               if ( $patrolled ) {
+                                                       PatrolLog::record( $rc, true, $user );
+                                               }
+                                       }
+                                       $user->incEditCount();
+                               } catch ( MWException $e ) {
+                                       $dbw->rollback( __METHOD__ );
+                                       // Question: Would it perhaps be better if this method turned all
+                                       // exceptions into $status's?
+                                       throw $e;
+                               }
+                               $dbw->commit( __METHOD__ );
+                       } else {
+                               // Bug 32948: revision ID must be set to page {{REVISIONID}} and
+                               // related variables correctly
+                               $revision->setId( $this->getLatest() );
+                       }
+
+                       // Update links tables, site stats, etc.
+                       $this->doEditUpdates(
+                               $revision,
+                               $user,
+                               array(
+                                       'changed' => $changed,
+                                       'oldcountable' => $oldcountable
+                               )
+                       );
+
+                       if ( !$changed ) {
+                               $status->warning( 'edit-no-change' );
+                               $revision = null;
+                               // Update page_touched, this is usually implicit in the page update
+                               // Other cache updates are done in onArticleEdit()
+                               $this->mTitle->invalidateCache();
+                       }
+               } else {
+                       // Create new article
+                       $status->value['new'] = true;
+
+                       $dbw->begin( __METHOD__ );
+                       try {
+
+                               $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+                               $status->merge( $prepStatus );
+
+                               if ( !$status->isOK() ) {
+                                       $dbw->rollback( __METHOD__ );
+
+                                       wfProfileOut( __METHOD__ );
+                                       return $status;
+                               }
+
+                               $status->merge( $prepStatus );
+
+                               // Add the page record; stake our claim on this title!
+                               // This will return false if the article already exists
+                               $newid = $this->insertOn( $dbw );
+
+                               if ( $newid === false ) {
+                                       $dbw->rollback( __METHOD__ );
+                                       $status->fatal( 'edit-already-exists' );
+
+                                       wfProfileOut( __METHOD__ );
+                                       return $status;
+                               }
+
+                               // Save the revision text...
+                               $revision = new Revision( array(
+                                       'page'       => $newid,
+                                       'title'      => $this->getTitle(), // for determining the default content model
+                                       'comment'    => $summary,
+                                       'minor_edit' => $isminor,
+                                       'text'       => $serialized,
+                                       'len'        => $newsize,
+                                       'user'       => $user->getId(),
+                                       'user_text'  => $user->getName(),
+                                       'timestamp'  => $now,
+                                       'content_model' => $content->getModel(),
+                                       'content_format' => $serialisation_format,
+                               ) );
+                               $revisionId = $revision->insertOn( $dbw );
+
+                               // Bug 37225: use accessor to get the text as Revision may trim it
+                               $content = $revision->getContent(); // sanity; get normalized version
+
+                               if ( $content ) {
+                                       $newsize = $content->getSize();
+                               }
+
+                               // Update the page record with revision data
+                               $this->updateRevisionOn( $dbw, $revision, 0 );
+
+                               wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+
+                               // Update recentchanges
+                               if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
+                                       // Mark as patrolled if the user can do so
+                                       $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
+                                               $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
+                                       // Add RC row to the DB
+                                       $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
+                                               '', $newsize, $revisionId, $patrolled );
+
+                                       // Log auto-patrolled edits
+                                       if ( $patrolled ) {
+                                               PatrolLog::record( $rc, true, $user );
+                                       }
+                               }
+                               $user->incEditCount();
+
+                       } catch ( MWException $e ) {
+                               $dbw->rollback( __METHOD__ );
+                               throw $e;
+                       }
+                       $dbw->commit( __METHOD__ );
+
+                       // Update links, etc.
+                       $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
+
+                       $hook_args = array( &$this, &$user, $content, $summary,
+                                                               $flags & EDIT_MINOR, null, null, &$flags, $revision );
+
+                       ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
+                       wfRunHooks( 'PageContentInsertComplete', $hook_args );
+               }
+
+               // Do updates right now unless deferral was requested
+               if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
+                       DeferredUpdates::doUpdates();
+               }
+
+               // Return the new revision (or null) to the caller
+               $status->value['revision'] = $revision;
+
+               $hook_args = array( &$this, &$user, $content, $summary,
+                                                       $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
+
+               ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
+               wfRunHooks( 'PageContentSaveComplete', $hook_args );
+
+               // Promote user to any groups they meet the criteria for
+               $user->addAutopromoteOnceGroups( 'onEdit' );
+
+               wfProfileOut( __METHOD__ );
+               return $status;
+       }
+
+       /**
+        * Get parser options suitable for rendering the primary article wikitext
+        *
+        * @see ContentHandler::makeParserOptions
+        *
+        * @param IContextSource|User|string $context One of the following:
+        *        - IContextSource: Use the User and the Language of the provided
+        *          context
+        *        - User: Use the provided User object and $wgLang for the language,
+        *          so use an IContextSource object if possible.
+        *        - 'canonical': Canonical options (anonymous user with default
+        *          preferences and content language).
+        * @return ParserOptions
+        */
+       public function makeParserOptions( $context ) {
+               $options = $this->getContentHandler()->makeParserOptions( $context );
+
+               if ( $this->getTitle()->isConversionTable() ) {
+                       // @todo ConversionTable should become a separate content model, so
+                       // we don't need special cases like this one.
+                       $options->disableContentConversion();
+               }
+
+               return $options;
+       }
+
+       /**
+        * Prepare text which is about to be saved.
+        * Returns a stdclass with source, pst and output members
+        *
+        * @deprecated since 1.21: use prepareContentForEdit instead.
+        * @return object
+        */
+       public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+               $content = ContentHandler::makeContent( $text, $this->getTitle() );
+               return $this->prepareContentForEdit( $content, $revid, $user );
+       }
+
+       /**
+        * Prepare content which is about to be saved.
+        * Returns a stdclass with source, pst and output members
+        *
+        * @param Content $content
+        * @param int|null $revid
+        * @param User|null $user
+        * @param string|null $serialization_format
+        *
+        * @return bool|object
+        *
+        * @since 1.21
+        */
+       public function prepareContentForEdit( Content $content, $revid = null, User $user = null,
+               $serialization_format = null
+       ) {
+               global $wgContLang, $wgUser;
+               $user = is_null( $user ) ? $wgUser : $user;
+               //XXX: check $user->getId() here???
+
+               // Use a sane default for $serialization_format, see bug 57026
+               if ( $serialization_format === null ) {
+                       $serialization_format = $content->getContentHandler()->getDefaultFormat();
+               }
+
+               if ( $this->mPreparedEdit
+                       && $this->mPreparedEdit->newContent
+                       && $this->mPreparedEdit->newContent->equals( $content )
+                       && $this->mPreparedEdit->revid == $revid
+                       && $this->mPreparedEdit->format == $serialization_format
+                       // XXX: also check $user here?
+               ) {
+                       // Already prepared
+                       return $this->mPreparedEdit;
+               }
+
+               $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
+               wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
+
+               $edit = (object)array();
+               $edit->revid = $revid;
+               $edit->timestamp = wfTimestampNow();
+
+               $edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
+
+               $edit->format = $serialization_format;
+               $edit->popts = $this->makeParserOptions( 'canonical' );
+               $edit->output = $edit->pstContent
+                       ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
+                       : null;
+
+               $edit->newContent = $content;
+               $edit->oldContent = $this->getContent( Revision::RAW );
+
+               // NOTE: B/C for hooks! don't use these fields!
+               $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
+               $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
+               $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : '';
+
+               $this->mPreparedEdit = $edit;
+               return $edit;
+       }
+
+       /**
+        * Do standard deferred updates after page edit.
+        * Update links tables, site stats, search index and message cache.
+        * Purges pages that include this page if the text was changed here.
+        * Every 100th edit, prune the recent changes table.
+        *
+        * @param Revision $revision
+        * @param User $user User object that did the revision
+        * @param array $options Array of options, following indexes are used:
+        * - changed: boolean, whether the revision changed the content (default true)
+        * - created: boolean, whether the revision created the page (default false)
+        * - oldcountable: boolean or null (default null):
+        *   - boolean: whether the page was counted as an article before that
+        *     revision, only used in changed is true and created is false
+        *   - null: don't change the article count
+        */
+       public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
+               global $wgEnableParserCache;
+
+               wfProfileIn( __METHOD__ );
+
+               $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
+               $content = $revision->getContent();
+
+               // Parse the text
+               // Be careful not to do pre-save transform twice: $text is usually
+               // already pre-save transformed once.
+               if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
+                       wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
+                       $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
+               } else {
+                       wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
+                       $editInfo = $this->mPreparedEdit;
+               }
+
+               // Save it to the parser cache
+               if ( $wgEnableParserCache ) {
+                       $parserCache = ParserCache::singleton();
+                       $parserCache->save(
+                               $editInfo->output, $this, $editInfo->popts, $editInfo->timestamp, $editInfo->revid
+                       );
+               }
+
+               // Update the links tables and other secondary data
+               if ( $content ) {
+                       $recursive = $options['changed']; // bug 50785
+                       $updates = $content->getSecondaryDataUpdates(
+                               $this->getTitle(), null, $recursive, $editInfo->output );
+                       DataUpdate::runUpdates( $updates );
+               }
+
+               wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
+
+               if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
+                       if ( 0 == mt_rand( 0, 99 ) ) {
+                               // Flush old entries from the `recentchanges` table; we do this on
+                               // random requests so as to avoid an increase in writes for no good reason
+                               RecentChange::purgeExpiredChanges();
+                       }
+               }
+
+               if ( !$this->exists() ) {
+                       wfProfileOut( __METHOD__ );
+                       return;
+               }
+
+               $id = $this->getId();
+               $title = $this->mTitle->getPrefixedDBkey();
+               $shortTitle = $this->mTitle->getDBkey();
+
+               if ( !$options['changed'] ) {
+                       $good = 0;
+               } elseif ( $options['created'] ) {
+                       $good = (int)$this->isCountable( $editInfo );
+               } elseif ( $options['oldcountable'] !== null ) {
+                       $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
+               } else {
+                       $good = 0;
+               }
+               $edits = $options['changed'] ? 1 : 0;
+               $total = $options['created'] ? 1 : 0;
+
+               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, $edits, $good, $total ) );
+               DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );
+
+               // If this is another user's talk page, update newtalk.
+               // Don't do this if $options['changed'] = false (null-edits) nor if
+               // it's a minor edit and the user doesn't want notifications for those.
+               if ( $options['changed']
+                       && $this->mTitle->getNamespace() == NS_USER_TALK
+                       && $shortTitle != $user->getTitleKey()
+                       && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
+               ) {
+                       $recipient = User::newFromName( $shortTitle, false );
+                       if ( !$recipient ) {
+                               wfDebug( __METHOD__ . ": invalid username\n" );
+                       } else {
+                               // Allow extensions to prevent user notification when a new message is added to their talk page
+                               if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
+                                       if ( User::isIP( $shortTitle ) ) {
+                                               // An anonymous user
+                                               $recipient->setNewtalk( true, $revision );
+                                       } elseif ( $recipient->isLoggedIn() ) {
+                                               $recipient->setNewtalk( true, $revision );
+                                       } else {
+                                               wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
+                                       }
+                               }
+                       }
+               }
+
+               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+                       // XXX: could skip pseudo-messages like js/css here, based on content model.
+                       $msgtext = $content ? $content->getWikitextForTransclusion() : null;
+                       if ( $msgtext === false || $msgtext === null ) {
+                               $msgtext = '';
+                       }
+
+                       MessageCache::singleton()->replace( $shortTitle, $msgtext );
+               }
+
+               if ( $options['created'] ) {
+                       self::onArticleCreate( $this->mTitle );
+               } elseif ( $options['changed'] ) { // bug 50785
+                       self::onArticleEdit( $this->mTitle );
+               }
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Edit an article without doing all that other stuff
+        * The article must already exist; link tables etc
+        * are not updated, caches are not flushed.
+        *
+        * @param string $text Text submitted
+        * @param User $user The relevant user
+        * @param string $comment Comment submitted
+        * @param bool $minor Whereas it's a minor modification
+        *
+        * @deprecated since 1.21, use doEditContent() instead.
+        */
+       public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+               ContentHandler::deprecated( __METHOD__, "1.21" );
+
+               $content = ContentHandler::makeContent( $text, $this->getTitle() );
+               $this->doQuickEditContent( $content, $user, $comment, $minor );
+       }
+
+       /**
+        * Edit an article without doing all that other stuff
+        * The article must already exist; link tables etc
+        * are not updated, caches are not flushed.
+        *
+        * @param Content $content Content submitted
+        * @param User $user The relevant user
+        * @param string $comment comment submitted
+        * @param string $serialisation_format Format for storing the content in the database
+        * @param bool $minor Whereas it's a minor modification
+        */
+       public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
+               $serialisation_format = null
+       ) {
+               wfProfileIn( __METHOD__ );
+
+               $serialized = $content->serialize( $serialisation_format );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $revision = new Revision( array(
+                       'title'      => $this->getTitle(), // for determining the default content model
+                       'page'       => $this->getId(),
+                       'user_text'  => $user->getName(),
+                       'user'       => $user->getId(),
+                       'text'       => $serialized,
+                       'length'     => $content->getSize(),
+                       'comment'    => $comment,
+                       'minor_edit' => $minor ? 1 : 0,
+               ) ); // XXX: set the content object?
+               $revision->insertOn( $dbw );
+               $this->updateRevisionOn( $dbw, $revision );
+
+               wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Update the article's restriction field, and leave a log entry.
+        * This works for protection both existing and non-existing pages.
+        *
+        * @param array $limit Set of restriction keys
+        * @param array $expiry Per restriction type expiration
+        * @param int &$cascade Set to false if cascading protection isn't allowed.
+        * @param string $reason
+        * @param User $user The user updating the restrictions
+        * @return Status
+        */
+       public function doUpdateRestrictions( array $limit, array $expiry,
+               &$cascade, $reason, User $user
+       ) {
+               global $wgCascadingRestrictionLevels, $wgContLang;
+
+               if ( wfReadOnly() ) {
+                       return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
+               }
+
+               $this->loadPageData( 'fromdbmaster' );
+               $restrictionTypes = $this->mTitle->getRestrictionTypes();
+               $id = $this->getId();
+
+               if ( !$cascade ) {
+                       $cascade = false;
+               }
+
+               // Take this opportunity to purge out expired restrictions
+               Title::purgeExpiredRestrictions();
+
+               // @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
+               // we expect a single selection, but the schema allows otherwise.
+               $isProtected = false;
+               $protect = false;
+               $changed = false;
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               foreach ( $restrictionTypes as $action ) {
+                       if ( !isset( $expiry[$action] ) ) {
+                               $expiry[$action] = $dbw->getInfinity();
+                       }
+                       if ( !isset( $limit[$action] ) ) {
+                               $limit[$action] = '';
+                       } elseif ( $limit[$action] != '' ) {
+                               $protect = true;
+                       }
+
+                       // Get current restrictions on $action
+                       $current = implode( '', $this->mTitle->getRestrictions( $action ) );
+                       if ( $current != '' ) {
+                               $isProtected = true;
+                       }
+
+                       if ( $limit[$action] != $current ) {
+                               $changed = true;
+                       } elseif ( $limit[$action] != '' ) {
+                               // Only check expiry change if the action is actually being
+                               // protected, since expiry does nothing on an not-protected
+                               // action.
+                               if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
+                                       $changed = true;
+                               }
+                       }
+               }
+
+               if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
+                       $changed = true;
+               }
+
+               // If nothing has changed, do nothing
+               if ( !$changed ) {
+                       return Status::newGood();
+               }
+
+               if ( !$protect ) { // No protection at all means unprotection
+                       $revCommentMsg = 'unprotectedarticle';
+                       $logAction = 'unprotect';
+               } elseif ( $isProtected ) {
+                       $revCommentMsg = 'modifiedarticleprotection';
+                       $logAction = 'modify';
+               } else {
+                       $revCommentMsg = 'protectedarticle';
+                       $logAction = 'protect';
+               }
+
+               // Truncate for whole multibyte characters
+               $reason = $wgContLang->truncate( $reason, 255 );
+
+               $logRelationsValues = array();
+               $logRelationsField = null;
+
+               if ( $id ) { // Protection of existing page
+                       if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
+                               return Status::newGood();
+                       }
+
+                       // Only certain restrictions can cascade...
+                       $editrestriction = isset( $limit['edit'] )
+                               ? array( $limit['edit'] )
+                               : $this->mTitle->getRestrictions( 'edit' );
+                       foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
+                               $editrestriction[$key] = 'editprotected'; // backwards compatibility
+                       }
+                       foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
+                               $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
+                       }
+
+                       $cascadingRestrictionLevels = $wgCascadingRestrictionLevels;
+                       foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
+                               $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
+                       }
+                       foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
+                               $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
+                       }
+
+                       // The schema allows multiple restrictions
+                       if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
+                               $cascade = false;
+                       }
+
+                       // insert null revision to identify the page protection change as edit summary
+                       $latest = $this->getLatest();
+                       $nullRevision = $this->insertProtectNullRevision(
+                               $revCommentMsg,
+                               $limit,
+                               $expiry,
+                               $cascade,
+                               $reason,
+                               $user
+                       );
+
+                       if ( $nullRevision === null ) {
+                               return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
+                       }
+
+                       $logRelationsField = 'pr_id';
+
+                       // Update restrictions table
+                       foreach ( $limit as $action => $restrictions ) {
+                               $dbw->delete(
+                                       'page_restrictions',
+                                       array(
+                                               'pr_page' => $id,
+                                               'pr_type' => $action
+                                       ),
+                                       __METHOD__
+                               );
+                               if ( $restrictions != '' ) {
+                                       $dbw->insert(
+                                               'page_restrictions',
+                                               array(
+                                                       'pr_id' => $dbw->nextSequenceValue( 'page_restrictions_pr_id_seq' ),
+                                                       'pr_page' => $id,
+                                                       'pr_type' => $action,
+                                                       'pr_level' => $restrictions,
+                                                       'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
+                                                       'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
+                                               ),
+                                               __METHOD__
+                                       );
+                                       $logRelationsValues[] = $dbw->insertId();
+                               }
+                       }
+
+                       // Clear out legacy restriction fields
+                       $dbw->update(
+                               'page',
+                               array( 'page_restrictions' => '' ),
+                               array( 'page_id' => $id ),
+                               __METHOD__
+                       );
+
+                       wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
+                       wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
+               } else { // Protection of non-existing page (also known as "title protection")
+                       // Cascade protection is meaningless in this case
+                       $cascade = false;
+
+                       if ( $limit['create'] != '' ) {
+                               $dbw->replace( 'protected_titles',
+                                       array( array( 'pt_namespace', 'pt_title' ) ),
+                                       array(
+                                               'pt_namespace' => $this->mTitle->getNamespace(),
+                                               'pt_title' => $this->mTitle->getDBkey(),
+                                               'pt_create_perm' => $limit['create'],
+                                               'pt_timestamp' => $dbw->timestamp(),
+                                               'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
+                                               'pt_user' => $user->getId(),
+                                               'pt_reason' => $reason,
+                                       ), __METHOD__
+                               );
+                       } else {
+                               $dbw->delete( 'protected_titles',
+                                       array(
+                                               'pt_namespace' => $this->mTitle->getNamespace(),
+                                               'pt_title' => $this->mTitle->getDBkey()
+                                       ), __METHOD__
+                               );
+                       }
+               }
+
+               $this->mTitle->flushRestrictions();
+               InfoAction::invalidateCache( $this->mTitle );
+
+               if ( $logAction == 'unprotect' ) {
+                       $params = array();
+               } else {
+                       $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
+                       $params = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
+               }
+
+               // Update the protection log
+               $log = new LogPage( 'protect' );
+               $logId = $log->addEntry( $logAction, $this->mTitle, $reason, $params, $user );
+               if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
+                       $log->addRelations( $logRelationsField, $logRelationsValues, $logId );
+               }
+
+               return Status::newGood();
+       }
+
+       /**
+        * Insert a new null revision for this page.
+        *
+        * @param string $revCommentMsg Comment message key for the revision
+        * @param array $limit Set of restriction keys
+        * @param array $expiry Per restriction type expiration
+        * @param int $cascade Set to false if cascading protection isn't allowed.
+        * @param string $reason
+        * @param User|null $user
+        * @return Revision|null Null on error
+        */
+       public function insertProtectNullRevision( $revCommentMsg, array $limit,
+               array $expiry, $cascade, $reason, $user = null
+       ) {
+               global $wgContLang;
+               $dbw = wfGetDB( DB_MASTER );
+
+               // Prepare a null revision to be added to the history
+               $editComment = $wgContLang->ucfirst(
+                       wfMessage(
+                               $revCommentMsg,
+                               $this->mTitle->getPrefixedText()
+                       )->inContentLanguage()->text()
+               );
+               if ( $reason ) {
+                       $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
+               }
+               $protectDescription = $this->protectDescription( $limit, $expiry );
+               if ( $protectDescription ) {
+                       $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+                       $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
+                               ->inContentLanguage()->text();
+               }
+               if ( $cascade ) {
+                       $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+                       $editComment .= wfMessage( 'brackets' )->params(
+                               wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
+                       )->inContentLanguage()->text();
+               }
+
+               $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true, $user );
+               if ( $nullRev ) {
+                       $nullRev->insertOn( $dbw );
+
+                       // Update page record and touch page
+                       $oldLatest = $nullRev->getParentId();
+                       $this->updateRevisionOn( $dbw, $nullRev, $oldLatest );
+               }
+
+               return $nullRev;
+       }
+
+       /**
+        * @param string $expiry 14-char timestamp or "infinity", or false if the input was invalid
+        * @return string
+        */
+       protected function formatExpiry( $expiry ) {
+               global $wgContLang;
+               $dbr = wfGetDB( DB_SLAVE );
+
+               $encodedExpiry = $dbr->encodeExpiry( $expiry );
+               if ( $encodedExpiry != 'infinity' ) {
+                       return wfMessage(
+                               'protect-expiring',
+                               $wgContLang->timeanddate( $expiry, false, false ),
+                               $wgContLang->date( $expiry, false, false ),
+                               $wgContLang->time( $expiry, false, false )
+                       )->inContentLanguage()->text();
+               } else {
+                       return wfMessage( 'protect-expiry-indefinite' )
+                               ->inContentLanguage()->text();
+               }
+       }
+
+       /**
+        * Builds the description to serve as comment for the edit.
+        *
+        * @param array $limit Set of restriction keys
+        * @param array $expiry Per restriction type expiration
+        * @return string
+        */
+       public function protectDescription( array $limit, array $expiry ) {
+               $protectDescription = '';
+
+               foreach ( array_filter( $limit ) as $action => $restrictions ) {
+                       # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
+                       # All possible message keys are listed here for easier grepping:
+                       # * restriction-create
+                       # * restriction-edit
+                       # * restriction-move
+                       # * restriction-upload
+                       $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
+                       # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
+                       # with '' filtered out. All possible message keys are listed below:
+                       # * protect-level-autoconfirmed
+                       # * protect-level-sysop
+                       $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
+
+                       $expiryText = $this->formatExpiry( $expiry[$action] );
+
+                       if ( $protectDescription !== '' ) {
+                               $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
+                       }
+                       $protectDescription .= wfMessage( 'protect-summary-desc' )
+                               ->params( $actionText, $restrictionsText, $expiryText )
+                               ->inContentLanguage()->text();
+               }
+
+               return $protectDescription;
+       }
+
+       /**
+        * Builds the description to serve as comment for the log entry.
+        *
+        * Some bots may parse IRC lines, which are generated from log entries which contain plain
+        * protect description text. Keep them in old format to avoid breaking compatibility.
+        * TODO: Fix protection log to store structured description and format it on-the-fly.
+        *
+        * @param array $limit Set of restriction keys
+        * @param array $expiry Per restriction type expiration
+        * @return string
+        */
+       public function protectDescriptionLog( array $limit, array $expiry ) {
+               global $wgContLang;
+
+               $protectDescriptionLog = '';
+
+               foreach ( array_filter( $limit ) as $action => $restrictions ) {
+                       $expiryText = $this->formatExpiry( $expiry[$action] );
+                       $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
+               }
+
+               return trim( $protectDescriptionLog );
+       }
+
+       /**
+        * Take an array of page restrictions and flatten it to a string
+        * suitable for insertion into the page_restrictions field.
+        *
+        * @param string[] $limit
+        *
+        * @throws MWException
+        * @return string
+        */
+       protected static function flattenRestrictions( $limit ) {
+               if ( !is_array( $limit ) ) {
+                       throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
+               }
+
+               $bits = array();
+               ksort( $limit );
+
+               foreach ( array_filter( $limit ) as $action => $restrictions ) {
+                       $bits[] = "$action=$restrictions";
+               }
+
+               return implode( ':', $bits );
+       }
+
+       /**
+        * Same as doDeleteArticleReal(), but returns a simple boolean. This is kept around for
+        * backwards compatibility, if you care about error reporting you should use
+        * doDeleteArticleReal() instead.
+        *
+        * Deletes the article with database consistency, writes logs, purges caches
+        *
+        * @param string $reason Delete reason for deletion log
+        * @param bool $suppress Suppress all revisions and log the deletion in
+        *        the suppression log instead of the deletion log
+        * @param int $id Article ID
+        * @param bool $commit Defaults to true, triggers transaction end
+        * @param array &$error Array of errors to append to
+        * @param User $user The deleting user
+        * @return bool true if successful
+        */
+       public function doDeleteArticle(
+               $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+       ) {
+               $status = $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user );
+               return $status->isGood();
+       }
+
+       /**
+        * Back-end article deletion
+        * Deletes the article with database consistency, writes logs, purges caches
+        *
+        * @since 1.19
+        *
+        * @param string $reason Delete reason for deletion log
+        * @param bool $suppress Suppress all revisions and log the deletion in
+        *   the suppression log instead of the deletion log
+        * @param int $id Article ID
+        * @param bool $commit Defaults to true, triggers transaction end
+        * @param array &$error Array of errors to append to
+        * @param User $user The deleting user
+        * @return Status Status object; if successful, $status->value is the log_id of the
+        *   deletion log entry. If the page couldn't be deleted because it wasn't
+        *   found, $status is a non-fatal 'cannotdelete' error
+        */
+       public function doDeleteArticleReal(
+               $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+       ) {
+               global $wgUser, $wgContentHandlerUseDB;
+
+               wfDebug( __METHOD__ . "\n" );
+
+               $status = Status::newGood();
+
+               if ( $this->mTitle->getDBkey() === '' ) {
+                       $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+                       return $status;
+               }
+
+               $user = is_null( $user ) ? $wgUser : $user;
+               if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
+                       if ( $status->isOK() ) {
+                               // Hook aborted but didn't set a fatal status
+                               $status->fatal( 'delete-hook-aborted' );
+                       }
+                       return $status;
+               }
+
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->begin( __METHOD__ );
+
+               if ( $id == 0 ) {
+                       $this->loadPageData( 'forupdate' );
+                       $id = $this->getID();
+                       if ( $id == 0 ) {
+                               $dbw->rollback( __METHOD__ );
+                               $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+                               return $status;
+                       }
+               }
+
+               // we need to remember the old content so we can use it to generate all deletion updates.
+               $content = $this->getContent( Revision::RAW );
+
+               // Bitfields to further suppress the content
+               if ( $suppress ) {
+                       $bitfield = 0;
+                       // This should be 15...
+                       $bitfield |= Revision::DELETED_TEXT;
+                       $bitfield |= Revision::DELETED_COMMENT;
+                       $bitfield |= Revision::DELETED_USER;
+                       $bitfield |= Revision::DELETED_RESTRICTED;
+               } else {
+                       $bitfield = 'rev_deleted';
+               }
+
+               // For now, shunt the revision data into the archive table.
+               // Text is *not* removed from the text table; bulk storage
+               // is left intact to avoid breaking block-compression or
+               // immutable storage schemes.
+               //
+               // For backwards compatibility, note that some older archive
+               // table entries will have ar_text and ar_flags fields still.
+               //
+               // In the future, we may keep revisions and mark them with
+               // the rev_deleted field, which is reserved for this purpose.
+
+               $row = array(
+                       'ar_namespace'  => 'page_namespace',
+                       'ar_title'      => 'page_title',
+                       'ar_comment'    => 'rev_comment',
+                       'ar_user'       => 'rev_user',
+                       'ar_user_text'  => 'rev_user_text',
+                       'ar_timestamp'  => 'rev_timestamp',
+                       'ar_minor_edit' => 'rev_minor_edit',
+                       'ar_rev_id'     => 'rev_id',
+                       'ar_parent_id'  => 'rev_parent_id',
+                       'ar_text_id'    => 'rev_text_id',
+                       'ar_text'       => '\'\'', // Be explicit to appease
+                       'ar_flags'      => '\'\'', // MySQL's "strict mode"...
+                       'ar_len'        => 'rev_len',
+                       'ar_page_id'    => 'page_id',
+                       'ar_deleted'    => $bitfield,
+                       'ar_sha1'       => 'rev_sha1',
+               );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $row['ar_content_model'] = 'rev_content_model';
+                       $row['ar_content_format'] = 'rev_content_format';
+               }
+
+               $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
+                       $row,
+                       array(
+                               'page_id' => $id,
+                               'page_id = rev_page'
+                       ), __METHOD__
+               );
+
+               // Now that it's safely backed up, delete it
+               $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
+               $ok = ( $dbw->affectedRows() > 0 ); // $id could be laggy
+
+               if ( !$ok ) {
+                       $dbw->rollback( __METHOD__ );
+                       $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+                       return $status;
+               }
+
+               if ( !$dbw->cascadingDeletes() ) {
+                       $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
+               }
+
+               // Clone the title, so we have the information we need when we log
+               $logTitle = clone $this->mTitle;
+
+               $this->doDeleteUpdates( $id, $content );
+
+               // Log the deletion, if the page was suppressed, log it at Oversight instead
+               $logtype = $suppress ? 'suppress' : 'delete';
+
+               $logEntry = new ManualLogEntry( $logtype, 'delete' );
+               $logEntry->setPerformer( $user );
+               $logEntry->setTarget( $logTitle );
+               $logEntry->setComment( $reason );
+               $logid = $logEntry->insert();
+
+               $dbw->onTransactionPreCommitOrIdle( function() use ( $dbw, $logEntry, $logid ) {
+                       // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
+                       $logEntry->publish( $logid );
+               } );
+
+               if ( $commit ) {
+                       $dbw->commit( __METHOD__ );
+               }
+
+               wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
+               $status->value = $logid;
+               return $status;
+       }
+
+       /**
+        * Do some database updates after deletion
+        *
+        * @param int $id page_id value of the page being deleted
+        * @param Content $content Optional page content to be used when determining
+        *   the required updates. This may be needed because $this->getContent()
+        *   may already return null when the page proper was deleted.
+        */
+       public function doDeleteUpdates( $id, Content $content = null ) {
+               // update site status
+               DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
+
+               // remove secondary indexes, etc
+               $updates = $this->getDeletionUpdates( $content );
+               DataUpdate::runUpdates( $updates );
+
+               // Reparse any pages transcluding this page
+               LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
+
+               // Reparse any pages including this image
+               if ( $this->mTitle->getNamespace() == NS_FILE ) {
+                       LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' );
+               }
+
+               // Clear caches
+               WikiPage::onArticleDelete( $this->mTitle );
+
+               // Reset this object and the Title object
+               $this->loadFromRow( false, self::READ_LATEST );
+
+               // Search engine
+               DeferredUpdates::addUpdate( new SearchUpdate( $id, $this->mTitle ) );
+       }
+
+       /**
+        * Roll back the most recent consecutive set of edits to a page
+        * from the same user; fails if there are no eligible edits to
+        * roll back to, e.g. user is the sole contributor. This function
+        * performs permissions checks on $user, then calls commitRollback()
+        * to do the dirty work
+        *
+        * @todo Separate the business/permission stuff out from backend code
+        *
+        * @param string $fromP Name of the user whose edits to rollback.
+        * @param string $summary Custom summary. Set to default summary if empty.
+        * @param string $token Rollback token.
+        * @param bool $bot If true, mark all reverted edits as bot.
+        *
+        * @param array $resultDetails contains result-specific array of additional values
+        *    'alreadyrolled' : 'current' (rev)
+        *    success        : 'summary' (str), 'current' (rev), 'target' (rev)
+        *
+        * @param User $user The user performing the rollback
+        * @return array Array of errors, each error formatted as
+        *   array(messagekey, param1, param2, ...).
+        * On success, the array is empty.  This array can also be passed to
+        * OutputPage::showPermissionsErrorPage().
+        */
+       public function doRollback(
+               $fromP, $summary, $token, $bot, &$resultDetails, User $user
+       ) {
+               $resultDetails = null;
+
+               // Check permissions
+               $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user );
+               $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $user );
+               $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
+
+               if ( !$user->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) ) {
+                       $errors[] = array( 'sessionfailure' );
+               }
+
+               if ( $user->pingLimiter( 'rollback' ) || $user->pingLimiter() ) {
+                       $errors[] = array( 'actionthrottledtext' );
+               }
+
+               // If there were errors, bail out now
+               if ( !empty( $errors ) ) {
+                       return $errors;
+               }
+
+               return $this->commitRollback( $fromP, $summary, $bot, $resultDetails, $user );
+       }
+
+       /**
+        * Backend implementation of doRollback(), please refer there for parameter
+        * and return value documentation
+        *
+        * NOTE: This function does NOT check ANY permissions, it just commits the
+        * rollback to the DB. Therefore, you should only call this function direct-
+        * ly if you want to use custom permissions checks. If you don't, use
+        * doRollback() instead.
+        * @param string $fromP Name of the user whose edits to rollback.
+        * @param string $summary Custom summary. Set to default summary if empty.
+        * @param bool $bot If true, mark all reverted edits as bot.
+        *
+        * @param array $resultDetails Contains result-specific array of additional values
+        * @param User $guser The user performing the rollback
+        * @return array
+        */
+       public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
+               global $wgUseRCPatrol, $wgContLang;
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               if ( wfReadOnly() ) {
+                       return array( array( 'readonlytext' ) );
+               }
+
+               // Get the last editor
+               $current = $this->getRevision();
+               if ( is_null( $current ) ) {
+                       // Something wrong... no page?
+                       return array( array( 'notanarticle' ) );
+               }
+
+               $from = str_replace( '_', ' ', $fromP );
+               // User name given should match up with the top revision.
+               // If the user was deleted then $from should be empty.
+               if ( $from != $current->getUserText() ) {
+                       $resultDetails = array( 'current' => $current );
+                       return array( array( 'alreadyrolled',
+                               htmlspecialchars( $this->mTitle->getPrefixedText() ),
+                               htmlspecialchars( $fromP ),
+                               htmlspecialchars( $current->getUserText() )
+                       ) );
+               }
+
+               // Get the last edit not by this guy...
+               // Note: these may not be public values
+               $user = intval( $current->getRawUser() );
+               $user_text = $dbw->addQuotes( $current->getRawUserText() );
+               $s = $dbw->selectRow( 'revision',
+                       array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
+                       array( 'rev_page' => $current->getPage(),
+                               "rev_user != {$user} OR rev_user_text != {$user_text}"
+                       ), __METHOD__,
+                       array( 'USE INDEX' => 'page_timestamp',
+                               'ORDER BY' => 'rev_timestamp DESC' )
+                       );
+               if ( $s === false ) {
+                       // No one else ever edited this page
+                       return array( array( 'cantrollback' ) );
+               } elseif ( $s->rev_deleted & Revision::DELETED_TEXT
+                       || $s->rev_deleted & Revision::DELETED_USER
+               ) {
+                       // Only admins can see this text
+                       return array( array( 'notvisiblerev' ) );
+               }
+
+               // Set patrolling and bot flag on the edits, which gets rollbacked.
+               // This is done before the rollback edit to have patrolling also on failure (bug 62157).
+               $set = array();
+               if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
+                       // Mark all reverted edits as bot
+                       $set['rc_bot'] = 1;
+               }
+
+               if ( $wgUseRCPatrol ) {
+                       // Mark all reverted edits as patrolled
+                       $set['rc_patrolled'] = 1;
+               }
+
+               if ( count( $set ) ) {
+                       $dbw->update( 'recentchanges', $set,
+                               array( /* WHERE */
+                                       'rc_cur_id' => $current->getPage(),
+                                       'rc_user_text' => $current->getUserText(),
+                                       'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ),
+                               ), __METHOD__
+                       );
+               }
+
+               // Generate the edit summary if necessary
+               $target = Revision::newFromId( $s->rev_id );
+               if ( empty( $summary ) ) {
+                       if ( $from == '' ) { // no public user name
+                               $summary = wfMessage( 'revertpage-nouser' );
+                       } else {
+                               $summary = wfMessage( 'revertpage' );
+                       }
+               }
+
+               // Allow the custom summary to use the same args as the default message
+               $args = array(
+                       $target->getUserText(), $from, $s->rev_id,
+                       $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
+                       $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
+               );
+               if ( $summary instanceof Message ) {
+                       $summary = $summary->params( $args )->inContentLanguage()->text();
+               } else {
+                       $summary = wfMsgReplaceArgs( $summary, $args );
+               }
+
+               // Trim spaces on user supplied text
+               $summary = trim( $summary );
+
+               // Truncate for whole multibyte characters.
+               $summary = $wgContLang->truncate( $summary, 255 );
+
+               // Save
+               $flags = EDIT_UPDATE;
+
+               if ( $guser->isAllowed( 'minoredit' ) ) {
+                       $flags |= EDIT_MINOR;
+               }
+
+               if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
+                       $flags |= EDIT_FORCE_BOT;
+               }
+
+               // Actually store the edit
+               $status = $this->doEditContent(
+                       $target->getContent(),
+                       $summary,
+                       $flags,
+                       $target->getId(),
+                       $guser
+               );
+
+               if ( !$status->isOK() ) {
+                       return $status->getErrorsArray();
+               }
+
+               // raise error, when the edit is an edit without a new version
+               if ( empty( $status->value['revision'] ) ) {
+                       $resultDetails = array( 'current' => $current );
+                       return array( array( 'alreadyrolled',
+                                       htmlspecialchars( $this->mTitle->getPrefixedText() ),
+                                       htmlspecialchars( $fromP ),
+                                       htmlspecialchars( $current->getUserText() )
+                       ) );
+               }
+
+               $revId = $status->value['revision']->getId();
+
+               wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
+
+               $resultDetails = array(
+                       'summary' => $summary,
+                       'current' => $current,
+                       'target' => $target,
+                       'newid' => $revId
+               );
+
+               return array();
+       }
+
+       /**
+        * The onArticle*() functions are supposed to be a kind of hooks
+        * which should be called whenever any of the specified actions
+        * are done.
+        *
+        * This is a good place to put code to clear caches, for instance.
+        *
+        * This is called on page move and undelete, as well as edit
+        *
+        * @param Title $title
+        */
+       public static function onArticleCreate( $title ) {
+               // Update existence markers on article/talk tabs...
+               if ( $title->isTalkPage() ) {
+                       $other = $title->getSubjectPage();
+               } else {
+                       $other = $title->getTalkPage();
+               }
+
+               $other->invalidateCache();
+               $other->purgeSquid();
+
+               $title->touchLinks();
+               $title->purgeSquid();
+               $title->deleteTitleProtection();
+       }
+
+       /**
+        * Clears caches when article is deleted
+        *
+        * @param Title $title
+        */
+       public static function onArticleDelete( $title ) {
+               // Update existence markers on article/talk tabs...
+               if ( $title->isTalkPage() ) {
+                       $other = $title->getSubjectPage();
+               } else {
+                       $other = $title->getTalkPage();
+               }
+
+               $other->invalidateCache();
+               $other->purgeSquid();
+
+               $title->touchLinks();
+               $title->purgeSquid();
+
+               // File cache
+               HTMLFileCache::clearFileCache( $title );
+               InfoAction::invalidateCache( $title );
+
+               // Messages
+               if ( $title->getNamespace() == NS_MEDIAWIKI ) {
+                       MessageCache::singleton()->replace( $title->getDBkey(), false );
+               }
+
+               // Images
+               if ( $title->getNamespace() == NS_FILE ) {
+                       $update = new HTMLCacheUpdate( $title, 'imagelinks' );
+                       $update->doUpdate();
+               }
+
+               // User talk pages
+               if ( $title->getNamespace() == NS_USER_TALK ) {
+                       $user = User::newFromName( $title->getText(), false );
+                       if ( $user ) {
+                               $user->setNewtalk( false );
+                       }
+               }
+
+               // Image redirects
+               RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
+       }
+
+       /**
+        * Purge caches on page update etc
+        *
+        * @param Title $title
+        * @todo Verify that $title is always a Title object (and never false or
+        *   null), add Title hint to parameter $title.
+        */
+       public static function onArticleEdit( $title ) {
+               // Invalidate caches of articles which include this page
+               DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
+
+               // Invalidate the caches of all pages which redirect here
+               DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' );
+
+               // Purge squid for this page only
+               $title->purgeSquid();
+
+               // Clear file cache for this page only
+               HTMLFileCache::clearFileCache( $title );
+               InfoAction::invalidateCache( $title );
+       }
+
+       /**#@-*/
+
+       /**
+        * Returns a list of categories this page is a member of.
+        * Results will include hidden categories
+        *
+        * @return TitleArray
+        */
+       public function getCategories() {
+               $id = $this->getId();
+               if ( $id == 0 ) {
+                       return TitleArray::newFromResult( new FakeResultWrapper( array() ) );
+               }
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'categorylinks',
+                       array( 'cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace' ),
+                       // Have to do that since DatabaseBase::fieldNamesWithAlias treats numeric indexes
+                       // as not being aliases, and NS_CATEGORY is numeric
+                       array( 'cl_from' => $id ),
+                       __METHOD__ );
+
+               return TitleArray::newFromResult( $res );
+       }
+
+       /**
+        * Returns a list of hidden categories this page is a member of.
+        * Uses the page_props and categorylinks tables.
+        *
+        * @return array Array of Title objects
+        */
+       public function getHiddenCategories() {
+               $result = array();
+               $id = $this->getId();
+
+               if ( $id == 0 ) {
+                       return array();
+               }
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
+                       array( 'cl_to' ),
+                       array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
+                               'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ),
+                       __METHOD__ );
+
+               if ( $res !== false ) {
+                       foreach ( $res as $row ) {
+                               $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
+                       }
+               }
+
+               return $result;
+       }
+
+       /**
+        * Return an applicable autosummary if one exists for the given edit.
+        * @param string|null $oldtext The previous text of the page.
+        * @param string|null $newtext The submitted text of the page.
+        * @param int $flags Bitmask: a bitmask of flags submitted for the edit.
+        * @return string An appropriate autosummary, or an empty string.
+        *
+        * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
+        */
+       public static function getAutosummary( $oldtext, $newtext, $flags ) {
+               // NOTE: stub for backwards-compatibility. assumes the given text is
+               // wikitext. will break horribly if it isn't.
+
+               ContentHandler::deprecated( __METHOD__, '1.21' );
+
+               $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+               $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
+               $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
+
+               return $handler->getAutosummary( $oldContent, $newContent, $flags );
+       }
+
+       /**
+        * Auto-generates a deletion reason
+        *
+        * @param bool &$hasHistory Whether the page has a history
+        * @return string|bool String containing deletion reason or empty string, or boolean false
+        *    if no revision occurred
+        */
+       public function getAutoDeleteReason( &$hasHistory ) {
+               return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
+       }
+
+       /**
+        * Update all the appropriate counts in the category table, given that
+        * we've added the categories $added and deleted the categories $deleted.
+        *
+        * @param array $added The names of categories that were added
+        * @param array $deleted The names of categories that were deleted
+        */
+       public function updateCategoryCounts( array $added, array $deleted ) {
+               $that = $this;
+               $method = __METHOD__;
+               $dbw = wfGetDB( DB_MASTER );
+
+               // Do this at the end of the commit to reduce lock wait timeouts
+               $dbw->onTransactionPreCommitOrIdle(
+                       function() use ( $dbw, $that, $method, $added, $deleted ) {
+                               $ns = $that->getTitle()->getNamespace();
+
+                               $addFields = array( 'cat_pages = cat_pages + 1' );
+                               $removeFields = array( 'cat_pages = cat_pages - 1' );
+                               if ( $ns == NS_CATEGORY ) {
+                                       $addFields[] = 'cat_subcats = cat_subcats + 1';
+                                       $removeFields[] = 'cat_subcats = cat_subcats - 1';
+                               } elseif ( $ns == NS_FILE ) {
+                                       $addFields[] = 'cat_files = cat_files + 1';
+                                       $removeFields[] = 'cat_files = cat_files - 1';
+                               }
+
+                               if ( count( $added ) ) {
+                                       $insertRows = array();
+                                       foreach ( $added as $cat ) {
+                                               $insertRows[] = array(
+                                                       'cat_title'   => $cat,
+                                                       'cat_pages'   => 1,
+                                                       'cat_subcats' => ( $ns == NS_CATEGORY ) ? 1 : 0,
+                                                       'cat_files'   => ( $ns == NS_FILE ) ? 1 : 0,
+                                               );
+                                       }
+                                       $dbw->upsert(
+                                               'category',
+                                               $insertRows,
+                                               array( 'cat_title' ),
+                                               $addFields,
+                                               $method
+                                       );
+                               }
+
+                               if ( count( $deleted ) ) {
+                                       $dbw->update(
+                                               'category',
+                                               $removeFields,
+                                               array( 'cat_title' => $deleted ),
+                                               $method
+                                       );
+                               }
+
+                               foreach ( $added as $catName ) {
+                                       $cat = Category::newFromName( $catName );
+                                       wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $that ) );
+                               }
+
+                               foreach ( $deleted as $catName ) {
+                                       $cat = Category::newFromName( $catName );
+                                       wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $that ) );
+                               }
+                       }
+               );
+       }
+
+       /**
+        * Updates cascading protections
+        *
+        * @param ParserOutput $parserOutput ParserOutput object for the current version
+        */
+       public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
+               if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
+                       return;
+               }
+
+               // templatelinks or imagelinks tables 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 and images from imagelinks
+               $id = $this->getId();
+
+               $dbLinks = array();
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( array( 'templatelinks' ),
+                       array( 'tl_namespace', 'tl_title' ),
+                       array( 'tl_from' => $id ),
+                       __METHOD__
+               );
+
+               foreach ( $res as $row ) {
+                       $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true;
+               }
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( array( 'imagelinks' ),
+                       array( 'il_to' ),
+                       array( 'il_from' => $id ),
+                       __METHOD__
+               );
+
+               foreach ( $res as $row ) {
+                       $dbLinks[NS_FILE . ":{$row->il_to}"] = true;
+               }
+
+               // Get templates and images from parser output.
+               $poLinks = array();
+               foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
+                       foreach ( $templates as $dbk => $id ) {
+                               $poLinks["$ns:$dbk"] = true;
+                       }
+               }
+               foreach ( $parserOutput->getImages() as $dbk => $id ) {
+                       $poLinks[NS_FILE . ":$dbk"] = true;
+               }
+
+               // Get the diff
+               $links_diff = array_diff_key( $poLinks, $dbLinks );
+
+               if ( count( $links_diff ) > 0 ) {
+                       // Whee, link updates time.
+                       // Note: we are only interested in links here. We don't need to get
+                       // other DataUpdate items from the parser output.
+                       $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
+                       $u->doUpdate();
+               }
+       }
+
+       /**
+        * Return a list of templates used by this article.
+        * Uses the templatelinks table
+        *
+        * @deprecated since 1.19; use Title::getTemplateLinksFrom()
+        * @return array Array of Title objects
+        */
+       public function getUsedTemplates() {
+               return $this->mTitle->getTemplateLinksFrom();
+       }
+
+       /**
+        * This function is called right before saving the wikitext,
+        * so we can do things like signatures and links-in-context.
+        *
+        * @deprecated since 1.19; use Parser::preSaveTransform() instead
+        * @param string $text Article contents
+        * @param User $user User doing the edit
+        * @param ParserOptions $popts Parser options, default options for
+        *   the user loaded if null given
+        * @return string Article contents with altered wikitext markup (signatures
+        *      converted, {{subst:}}, templates, etc.)
+        */
+       public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
+               global $wgParser, $wgUser;
+
+               wfDeprecated( __METHOD__, '1.19' );
+
+               $user = is_null( $user ) ? $wgUser : $user;
+
+               if ( $popts === null ) {
+                       $popts = ParserOptions::newFromUser( $user );
+               }
+
+               return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+       }
+
+       /**
+        * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
+        *
+        * @deprecated since 1.19; use Title::isBigDeletion() instead.
+        * @return bool
+        */
+       public function isBigDeletion() {
+               wfDeprecated( __METHOD__, '1.19' );
+               return $this->mTitle->isBigDeletion();
+       }
+
+       /**
+        * Get the  approximate revision count of this page.
+        *
+        * @deprecated since 1.19; use Title::estimateRevisionCount() instead.
+        * @return int
+        */
+       public function estimateRevisionCount() {
+               wfDeprecated( __METHOD__, '1.19' );
+               return $this->mTitle->estimateRevisionCount();
+       }
+
+       /**
+        * Update the article's restriction field, and leave a log entry.
+        *
+        * @deprecated since 1.19
+        * @param array $limit Set of restriction keys
+        * @param string $reason
+        * @param int &$cascade Set to false if cascading protection isn't allowed.
+        * @param array $expiry Per restriction type expiration
+        * @param User $user The user updating the restrictions
+        * @return bool true on success
+        */
+       public function updateRestrictions(
+               $limit = array(), $reason = '', &$cascade = 0, $expiry = array(), User $user = null
+       ) {
+               global $wgUser;
+
+               $user = is_null( $user ) ? $wgUser : $user;
+
+               return $this->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user )->isOK();
+       }
+
+       /**
+        * Returns a list of updates to be performed when this page is deleted. The
+        * updates should remove any information about this page from secondary data
+        * stores such as links tables.
+        *
+        * @param Content|null $content Optional Content object for determining the
+        *   necessary updates.
+        * @return array An array of DataUpdates objects
+        */
+       public function getDeletionUpdates( Content $content = null ) {
+               if ( !$content ) {
+                       // load content object, which may be used to determine the necessary updates
+                       // XXX: the content may not be needed to determine the updates, then this would be overhead.
+                       $content = $this->getContent( Revision::RAW );
+               }
+
+               if ( !$content ) {
+                       $updates = array();
+               } else {
+                       $updates = $content->getDeletionUpdates( $this );
+               }
+
+               wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
+               return $updates;
+       }
+}
diff --git a/includes/poolcounter/PoolWorkArticleView.php b/includes/poolcounter/PoolWorkArticleView.php
new file mode 100644 (file)
index 0000000..4cdb0ff
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+class PoolWorkArticleView extends PoolCounterWork {
+       /** @var Page */
+       private $page;
+
+       /** @var string */
+       private $cacheKey;
+
+       /** @var int */
+       private $revid;
+
+       /** @var ParserOptions */
+       private $parserOptions;
+
+       /** @var Content|null */
+       private $content = null;
+
+       /** @var ParserOutput|bool */
+       private $parserOutput = false;
+
+       /** @var bool */
+       private $isDirty = false;
+
+       /** @var Status|bool */
+       private $error = false;
+
+       /**
+        * @param Page $page
+        * @param int $revid ID of the revision being parsed.
+        * @param bool $useParserCache Whether to use the parser cache.
+        * @param ParserOptions $parserOptions ParserOptions to use for the parse
+        *   operation.
+        * @param Content|string $content Content to parse or null to load it; may
+        *   also be given as a wikitext string, for BC.
+        */
+       public function __construct( Page $page, ParserOptions $parserOptions,
+               $revid, $useParserCache, $content = null
+       ) {
+               if ( is_string( $content ) ) { // BC: old style call
+                       $modelId = $page->getRevision()->getContentModel();
+                       $format = $page->getRevision()->getContentFormat();
+                       $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
+               }
+
+               $this->page = $page;
+               $this->revid = $revid;
+               $this->cacheable = $useParserCache;
+               $this->parserOptions = $parserOptions;
+               $this->content = $content;
+               $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
+               parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
+       }
+
+       /**
+        * Get the ParserOutput from this object, or false in case of failure
+        *
+        * @return ParserOutput
+        */
+       public function getParserOutput() {
+               return $this->parserOutput;
+       }
+
+       /**
+        * Get whether the ParserOutput is a dirty one (i.e. expired)
+        *
+        * @return bool
+        */
+       public function getIsDirty() {
+               return $this->isDirty;
+       }
+
+       /**
+        * Get a Status object in case of error or false otherwise
+        *
+        * @return Status|bool
+        */
+       public function getError() {
+               return $this->error;
+       }
+
+       /**
+        * @return bool
+        */
+       public function doWork() {
+               global $wgUseFileCache;
+
+               // @todo several of the methods called on $this->page are not declared in Page, but present
+               //        in WikiPage and delegated by Article.
+
+               $isCurrent = $this->revid === $this->page->getLatest();
+
+               if ( $this->content !== null ) {
+                       $content = $this->content;
+               } elseif ( $isCurrent ) {
+                       // XXX: why use RAW audience here, and PUBLIC (default) below?
+                       $content = $this->page->getContent( Revision::RAW );
+               } else {
+                       $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
+
+                       if ( $rev === null ) {
+                               $content = null;
+                       } else {
+                               // XXX: why use PUBLIC audience here (default), and RAW above?
+                               $content = $rev->getContent();
+                       }
+               }
+
+               if ( $content === null ) {
+                       return false;
+               }
+
+               // Reduce effects of race conditions for slow parses (bug 46014)
+               $cacheTime = wfTimestampNow();
+
+               $time = - microtime( true );
+               $this->parserOutput = $content->getParserOutput(
+                       $this->page->getTitle(),
+                       $this->revid,
+                       $this->parserOptions
+               );
+               $time += microtime( true );
+
+               // Timing hack
+               if ( $time > 3 ) {
+                       wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
+                               $this->page->getTitle()->getPrefixedDBkey() ) );
+               }
+
+               if ( $this->cacheable && $this->parserOutput->isCacheable() && $isCurrent ) {
+                       ParserCache::singleton()->save(
+                               $this->parserOutput, $this->page, $this->parserOptions, $cacheTime, $this->revid );
+               }
+
+               // 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->parserOutput->isCacheable() || $this->parserOutput->containsOldMagic() ) {
+                       $wgUseFileCache = false;
+               }
+
+               if ( $isCurrent ) {
+                       $this->page->doCascadeProtectionUpdates( $this->parserOutput );
+               }
+
+               return true;
+       }
+
+       /**
+        * @return bool
+        */
+       public function getCachedWork() {
+               $this->parserOutput = ParserCache::singleton()->get( $this->page, $this->parserOptions );
+
+               if ( $this->parserOutput === false ) {
+                       wfDebug( __METHOD__ . ": parser cache miss\n" );
+                       return false;
+               } else {
+                       wfDebug( __METHOD__ . ": parser cache hit\n" );
+                       return true;
+               }
+       }
+
+       /**
+        * @return bool
+        */
+       public function fallback() {
+               $this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions );
+
+               if ( $this->parserOutput === false ) {
+                       wfDebugLog( 'dirty', 'dirty missing' );
+                       wfDebug( __METHOD__ . ": no dirty cache\n" );
+                       return false;
+               } else {
+                       wfDebug( __METHOD__ . ": sending dirty output\n" );
+                       wfDebugLog( 'dirty', "dirty output {$this->cacheKey}" );
+                       $this->isDirty = true;
+                       return true;
+               }
+       }
+
+       /**
+        * @param Status $status
+        * @return bool
+        */
+       public function error( $status ) {
+               $this->error = $status;
+               return false;
+       }
+}