Merge "Remove unused $wgDebugDBTransactions"
[lhc/web/wiklou.git] / includes / diff / DifferenceEngine.php
index d6c4109..e8f35f0 100644 (file)
@@ -18,24 +18,25 @@ define( 'MW_DIFF_VERSION', '1.11a' );
  * @todo document
  * @ingroup DifferenceEngine
  */
-class DifferenceEngine {
+class DifferenceEngine extends ContextSource {
        /**#@+
         * @private
         */
        var $mOldid, $mNewid;
-       var $mOldtitle, $mNewtitle, $mPagetitle;
        var $mOldtext, $mNewtext;
+       protected $mDiffLang;
 
        /**
         * @var Title
         */
-       var $mOldPage, $mNewPage, $mTitle;
+       var $mOldPage, $mNewPage;
        var $mRcidMarkPatrolled;
 
        /**
         * @var Revision
         */
        var $mOldRev, $mNewRev;
+       private $mRevisionsIdsLoaded = false; // Have the revisions IDs been loaded
        var $mRevisionsLoaded = false; // Have the revisions been loaded
        var $mTextLoaded = 0; // How many text blobs have been loaded, 0, 1 or 2?
        var $mCacheHit = false; // Was the diff fetched from cache?
@@ -51,49 +52,32 @@ class DifferenceEngine {
        // readability and conserve space with many small diffs.
        protected $mReducedLineNumbers = false;
 
+       // Link to action=markpatrolled
+       protected $mMarkPatrolledLink = null;
+
        protected $unhide = false; # show rev_deleted content if allowed
        /**#@-*/
 
        /**
         * Constructor
-        * @param $titleObj Title object that the diff is associated with
+        * @param $context IContextSource context to use, anything else will be ignored
         * @param $old Integer old ID we want to show and diff with.
         * @param $new String either 'prev' or 'next'.
         * @param $rcid Integer ??? FIXME (default 0)
         * @param $refreshCache boolean If set, refreshes the diff cache
         * @param $unhide boolean If set, allow viewing deleted revs
         */
-       function __construct( $titleObj = null, $old = 0, $new = 0, $rcid = 0,
+       function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
                $refreshCache = false, $unhide = false )
        {
-               if ( $titleObj ) {
-                       $this->mTitle = $titleObj;
-               } else {
-                       global $wgTitle;
-                       $this->mTitle = $wgTitle; // @TODO: get rid of this
+               if ( $context instanceof IContextSource ) {
+                       $this->setContext( $context );
                }
+
                wfDebug( "DifferenceEngine old '$old' new '$new' rcid '$rcid'\n" );
 
-               if ( 'prev' === $new ) {
-                       # Show diff between revision $old and the previous one.
-                       # Get previous one from DB.
-                       $this->mNewid = intval( $old );
-                       $this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid );
-               } elseif ( 'next' === $new ) {
-                       # Show diff between revision $old and the next one.
-                       # Get next one from DB.
-                       $this->mOldid = intval( $old );
-                       $this->mNewid = $this->mTitle->getNextRevisionID( $this->mOldid );
-                       if ( false === $this->mNewid ) {
-                               # if no result, NewId points to the newest old revision. The only newer
-                               # revision is cur, which is "0".
-                               $this->mNewid = 0;
-                       }
-               } else {
-                       $this->mOldid = intval( $old );
-                       $this->mNewid = intval( $new );
-                       wfRunHooks( 'NewDifferenceEngine', array( &$titleObj, &$this->mOldid, &$this->mNewid, $old, $new ) );
-               }
+               $this->mOldid = $old;
+               $this->mNewid = $new;
                $this->mRcidMarkPatrolled = intval( $rcid );  # force it to be an integer
                $this->mRefreshCache = $refreshCache;
                $this->unhide = $unhide;
@@ -107,10 +91,14 @@ class DifferenceEngine {
        }
 
        /**
-        * @return Title
+        * @return Language
         */
-       function getTitle() {
-               return $this->mTitle;
+       function getDiffLang() {
+               if ( $this->mDiffLang === null ) {
+                       # Default language in which the diff text is written.
+                       $this->mDiffLang = $this->getTitle()->getPageLanguage();
+               }
+               return $this->mDiffLang;
        }
 
        /**
@@ -124,6 +112,7 @@ class DifferenceEngine {
         * @return int
         */
        function getOldid() {
+               $this->loadRevisionIds();
                return $this->mOldid;
        }
 
@@ -131,6 +120,7 @@ class DifferenceEngine {
         * @return Bool|int
         */
        function getNewid() {
+               $this->loadRevisionIds();
                return $this->mNewid;
        }
 
@@ -142,8 +132,7 @@ class DifferenceEngine {
         * @return mixed URL or false
         */
        function deletedLink( $id ) {
-               global $wgUser;
-               if ( $wgUser->isAllowed( 'deletedhistory' ) ) {
+               if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
                        $dbr = wfGetDB( DB_SLAVE );
                        $row = $dbr->selectRow('archive', '*',
                                array( 'ar_rev_id' => $id ),
@@ -176,281 +165,214 @@ class DifferenceEngine {
        }
 
        function showDiffPage( $diffOnly = false ) {
-               global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
                wfProfileIn( __METHOD__ );
 
                # Allow frames except in certain special cases
-               $wgOut->allowClickjacking();
+               $out = $this->getOutput();
+               $out->allowClickjacking();
+               $out->setRobotPolicy( 'noindex,nofollow' );
 
-               # If external diffs are enabled both globally and for the user,
-               # we'll use the application/x-external-editor interface to call
-               # an external diff tool like kompare, kdiff3, etc.
-               if ( $wgUseExternalEditor && $wgUser->getOption( 'externaldiff' ) ) {
-                       global $wgServer, $wgScript, $wgLang;
-                       $wgOut->disable();
-                       header ( "Content-type: application/x-external-editor; charset=UTF-8" );
-                       $url1 = $this->mTitle->getFullURL( array(
-                               'action' => 'raw',
-                               'oldid' => $this->mOldid
-                       ) );
-                       $url2 = $this->mTitle->getFullURL( array(
-                               'action' => 'raw',
-                               'oldid' => $this->mNewid
-                       ) );
-                       $special = $wgLang->getNsText( NS_SPECIAL );
-                       $control = <<<CONTROL
-                       [Process]
-                       Type=Diff text
-                       Engine=MediaWiki
-                       Script={$wgServer}{$wgScript}
-                       Special namespace={$special}
-
-                       [File]
-                       Extension=wiki
-                       URL=$url1
-
-                       [File 2]
-                       Extension=wiki
-                       URL=$url2
-CONTROL;
-                       echo( $control );
-
-                       wfProfileOut( __METHOD__ );
-                       return;
-               }
-
-               $wgOut->setArticleFlag( false );
                if ( !$this->loadRevisionData() ) {
                        // Sounds like a deleted revision... Let's see what we can do.
-                       $t = $this->mTitle->getPrefixedText();
-                       $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ),
+                       $t = $this->getTitle()->getPrefixedText();
+                       $d = $this->msg( 'missingarticle-diff',
                                $this->deletedIdMarker( $this->mOldid ),
-                               $this->deletedIdMarker( $this->mNewid ) );
-                       $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
-                       $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", "<span class='plainlinks'>$d</span>" );
+                               $this->deletedIdMarker( $this->mNewid ) )->escaped();
+                       $out->setPageTitle( $this->msg( 'errorpagetitle' ) );
+                       $out->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", "<span class='plainlinks'>$d</span>" );
                        wfProfileOut( __METHOD__ );
                        return;
                }
 
-               wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
-
-               if ( $this->mNewRev->isCurrent() ) {
-                       $wgOut->setArticleFlag( true );
+               $user = $this->getUser();
+               $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user );
+               if ( $this->mOldPage ) { # mOldPage might not be set, see below.
+                       $permErrors = wfMergeErrorArrays( $permErrors,
+                               $this->mOldPage->getUserPermissionsErrors( 'read', $user ) );
                }
-
-               # mOldid is false if the difference engine is called with a "vague" query for
-               # a diff between a version V and its previous version V' AND the version V
-               # is the first version of that article. In that case, V' does not exist.
-               if ( $this->mOldid === false ) {
-                       $this->showFirstRevision();
-                       $this->renderNewRevision();  // should we respect $diffOnly here or not?
+               if ( count( $permErrors ) ) {
                        wfProfileOut( __METHOD__ );
-                       return;
+                       throw new PermissionsError( 'read', $permErrors );
                }
 
-               $oldTitle = $this->mOldPage->getPrefixedText();
-               $newTitle = $this->mNewPage->getPrefixedText();
-               if ( $oldTitle == $newTitle ) {
-                       $wgOut->setPageTitle( $newTitle );
-               } else {
-                       $wgOut->setPageTitle( $oldTitle . ', ' . $newTitle );
-               }
-               if ( $this->mNewPage->equals( $this->mOldPage ) ) {
-                       $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) );
-               } else {
-                       $wgOut->setSubtitle( wfMsgExt( 'difference-multipage', array( 'parseinline' ) ) );
-               }
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
+               # If external diffs are enabled both globally and for the user,
+               # we'll use the application/x-external-editor interface to call
+               # an external diff tool like kompare, kdiff3, etc.
+               if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) {
+                       $urls = array(
+                               'File' => array( 'Extension' => 'wiki', 'URL' =>
+                                       # This should be mOldPage, but it may not be set, see below.
+                                       $this->mNewPage->getCanonicalURL( array(
+                                               'action' => 'raw', 'oldid' => $this->mOldid ) )
+                               ),
+                               'File2' => array( 'Extension' => 'wiki', 'URL' =>
+                                       $this->mNewPage->getCanonicalURL( array(
+                                               'action' => 'raw', 'oldid' => $this->mNewid ) )
+                               ),
+                       );
+
+                       $externalEditor = new ExternalEdit( $this->getContext(), $urls );
+                       $externalEditor->execute();
 
-               if ( !$this->mOldPage->userCanRead() || !$this->mNewPage->userCanRead() ) {
-                       $wgOut->loginToUse();
-                       $wgOut->output();
-                       $wgOut->disable();
                        wfProfileOut( __METHOD__ );
                        return;
                }
 
-               $sk = $wgUser->getSkin();
-               if ( method_exists( $sk, 'suppressQuickbar' ) ) {
-                       $sk->suppressQuickbar();
+               $rollback = '';
+               $undoLink = '';
+
+               $query = array();
+               # Carry over 'diffonly' param via navigation links
+               if ( $diffOnly != $user->getBoolOption( 'diffonly' ) ) {
+                       $query['diffonly'] = $diffOnly;
                }
+               # Cascade unhide param in links for easy deletion browsing
+               if ( $this->unhide ) {
+                       $query['unhide'] = 1;
+               }
+
+               # Check if one of the revisions is deleted/suppressed
+               $deleted = $suppressed = false;
+               $allowed = $this->mNewRev->userCan( Revision::DELETED_TEXT, $user );
 
-               // Check if page is editable
-               $editable = $this->mNewRev->getTitle()->userCan( 'edit' );
-               if ( $editable && $this->mNewRev->isCurrent() && $wgUser->isAllowed( 'rollback' ) ) {
-                       $wgOut->preventClickjacking();
-                       $rollback = '&#160;&#160;&#160;' . $sk->generateRollback( $this->mNewRev );
+               # mOldRev is false if the difference engine is called with a "vague" query for
+               # a diff between a version V and its previous version V' AND the version V
+               # is the first version of that article. In that case, V' does not exist.
+               if ( $this->mOldRev === false ) {
+                       $out->setPageTitle( $this->mNewPage->getPrefixedText() );
+                       $out->addSubtitle( $this->msg( 'difference' ) );
+                       $samePage = true;
+                       $oldHeader = '';
                } else {
-                       $rollback = '';
-               }
+                       wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
+
+                       $sk = $this->getSkin();
+                       if ( method_exists( $sk, 'suppressQuickbar' ) ) {
+                               $sk->suppressQuickbar();
+                       }
 
-               // Prepare a change patrol link, if applicable
-               if ( $wgUseRCPatrol && $this->mTitle->userCan( 'patrol' ) ) {
-                       // If we've been given an explicit change identifier, use it; saves time
-                       if ( $this->mRcidMarkPatrolled ) {
-                               $rcid = $this->mRcidMarkPatrolled;
-                               $rc = RecentChange::newFromId( $rcid );
-                               // Already patrolled?
-                               $rcid = is_object( $rc ) && !$rc->getAttribute( 'rc_patrolled' ) ? $rcid : 0;
+                       if ( $this->mNewPage->equals( $this->mOldPage ) ) {
+                               $out->setPageTitle( $this->mNewPage->getPrefixedText() );
+                               $out->addSubtitle( $this->msg( 'difference' ) );
+                               $samePage = true;
                        } else {
-                               // Look for an unpatrolled change corresponding to this diff
-                               $db = wfGetDB( DB_SLAVE );
-                               $change = RecentChange::newFromConds(
-                                       array(
-                                       // Redundant user,timestamp condition so we can use the existing index
-                                               'rc_user_text'  => $this->mNewRev->getRawUserText(),
-                                               'rc_timestamp'  => $db->timestamp( $this->mNewRev->getTimestamp() ),
-                                               'rc_this_oldid' => $this->mNewid,
-                                               'rc_last_oldid' => $this->mOldid,
-                                               'rc_patrolled'  => 0
-                                       ),
-                                       __METHOD__
-                               );
-                               if ( $change instanceof RecentChange ) {
-                                       $rcid = $change->mAttribs['rc_id'];
-                                       $this->mRcidMarkPatrolled = $rcid;
-                               } else {
-                                       // None found
-                                       $rcid = 0;
+                               $out->setPageTitle( $this->mOldPage->getPrefixedText() . ', ' . $this->mNewPage->getPrefixedText() );
+                               $out->addSubtitle( $this->msg( 'difference-multipage' ) );
+                               $samePage = false;
+                       }
+
+                       if ( $samePage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
+                               if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
+                                       $out->preventClickjacking();
+                                       $rollback = '&#160;&#160;&#160;' . Linker::generateRollback( $this->mNewRev );
+                               }
+                               if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+                                       $undoLink = ' ' . $this->msg( 'parentheses' )->rawParams(
+                                               Html::element( 'a', array(
+                                                       'href' => $this->mNewPage->getLocalUrl( array(
+                                                               'action' => 'edit',
+                                                               'undoafter' => $this->mOldid,
+                                                               'undo' => $this->mNewid ) ),
+                                                       'title' => Linker::titleAttrib( 'undo' )
+                                               ),
+                                               $this->msg( 'editundo' )->text()
+                                       ) )->escaped();
                                }
                        }
-                       // Build the link
-                       if ( $rcid ) {
-                               $wgOut->preventClickjacking();
-                               $token = $wgUser->editToken( $rcid );
-                               $patrol = ' <span class="patrollink">[' . $sk->link(
-                                       $this->mTitle,
-                                       wfMsgHtml( 'markaspatrolleddiff' ),
-                                       array(),
-                                       array(
-                                               'action' => 'markpatrolled',
-                                               'rcid' => $rcid,
-                                               'token' => $token,
-                                       ),
-                                       array(
-                                               'known',
-                                               'noclasses'
-                                       )
-                               ) . ']</span>';
+
+                       # Make "previous revision link"
+                       if ( $samePage && $this->mOldRev->getPrevious() ) {
+                               $prevlink = Linker::linkKnown(
+                                       $this->mOldPage,
+                                       $this->msg( 'previousdiff' )->escaped(),
+                                       array( 'id' => 'differences-prevlink' ),
+                                       array( 'diff' => 'prev', 'oldid' => $this->mOldid ) + $query
+                               );
                        } else {
-                               $patrol = '';
+                               $prevlink = '&#160;';
                        }
-               } else {
-                       $patrol = '';
-               }
 
-               # Carry over 'diffonly' param via navigation links
-               if ( $diffOnly != $wgUser->getBoolOption( 'diffonly' ) ) {
-                       $query['diffonly'] = $diffOnly;
-               }
+                       if ( $this->mOldRev->isMinor() ) {
+                               $oldminor = ChangesList::flag( 'minor' );
+                       } else {
+                               $oldminor = '';
+                       }
 
-               # Make "previous revision link"
-               $query['diff'] = 'prev';
-               $query['oldid'] = $this->mOldid;
-               # Cascade unhide param in links for easy deletion browsing
-               if ( $this->unhide ) {
-                       $query['unhide'] = 1;
-               }
-               if ( !$this->mOldRev->getPrevious() ) {
-                       $prevlink = '&#160;';
-               } else {
-                       $prevlink = $sk->link(
-                               $this->mTitle,
-                               wfMsgHtml( 'previousdiff' ),
-                               array(
-                                       'id' => 'differences-prevlink'
-                               ),
-                               $query,
-                               array(
-                                       'known',
-                                       'noclasses'
-                               )
-                       );
+                       $ldel = $this->revisionDeleteLink( $this->mOldRev );
+                       $oldRevisionHeader = $this->getRevisionHeader( $this->mOldRev, 'complete' );
+
+                       $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader . '</strong></div>' .
+                               '<div id="mw-diff-otitle2">' .
+                                       Linker::revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
+                               '<div id="mw-diff-otitle3">' . $oldminor .
+                                       Linker::revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
+                               '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
+
+                       if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
+                               $deleted = true; // old revisions text is hidden
+                               if ( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+                                       $suppressed = true; // also suppressed
+                               }
+                       }
+
+                       # Check if this user can see the revisions
+                       if ( !$this->mOldRev->userCan( Revision::DELETED_TEXT, $user ) ) {
+                               $allowed = false;
+                       }
                }
 
                # Make "next revision link"
-               $query['diff'] = 'next';
-               $query['oldid'] = $this->mNewid;
                # Skip next link on the top revision
-               if ( $this->mNewRev->isCurrent() ) {
-                       $nextlink = '&#160;';
-               } else {
-                       $nextlink = $sk->link(
-                               $this->mTitle,
-                               wfMsgHtml( 'nextdiff' ),
-                               array(
-                                       'id' => 'differences-nextlink'
-                               ),
-                               $query,
-                               array(
-                                       'known',
-                                       'noclasses'
-                               )
+               if ( $samePage && !$this->mNewRev->isCurrent() ) {
+                       $nextlink = Linker::linkKnown(
+                               $this->mNewPage,
+                               $this->msg( 'nextdiff' )->escaped(),
+                               array( 'id' => 'differences-nextlink' ),
+                               array( 'diff' => 'next', 'oldid' => $this->mNewid ) + $query
                        );
+               } else {
+                       $nextlink = '&#160;';
                }
 
-               $oldminor = '';
-               $newminor = '';
-
-               if ( $this->mOldRev->isMinor() ) {
-                       $oldminor = ChangesList::flag( 'minor' );
-               }
                if ( $this->mNewRev->isMinor() ) {
                        $newminor = ChangesList::flag( 'minor' );
+               } else {
+                       $newminor = '';
                }
 
                # Handle RevisionDelete links...
-               $ldel = $this->revisionDeleteLink( $this->mOldRev );
                $rdel = $this->revisionDeleteLink( $this->mNewRev );
+               $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) . $undoLink;
 
-               $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $this->mOldtitle . '</strong></div>' .
-                       '<div id="mw-diff-otitle2">' .
-                               $sk->revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
-                       '<div id="mw-diff-otitle3">' . $oldminor .
-                               $sk->revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
-                       '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
-               $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $this->mNewtitle . '</strong></div>' .
-                       '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev, !$this->unhide ) .
+               $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' .
+                       '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $this->mNewRev, !$this->unhide ) .
                                " $rollback</div>" .
                        '<div id="mw-diff-ntitle3">' . $newminor .
-                               $sk->revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
-                       '<div id="mw-diff-ntitle4">' . $nextlink . $patrol . '</div>';
+                               Linker::revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
+                       '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>';
 
-               # Check if this user can see the revisions
-               $allowed = $this->mOldRev->userCan( Revision::DELETED_TEXT )
-                       && $this->mNewRev->userCan( Revision::DELETED_TEXT );
-               # Check if one of the revisions is deleted/suppressed
-               $deleted = $suppressed = false;
-               if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
-                       $deleted = true; // old revisions text is hidden
-                       if ( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) )
-                               $suppressed = true; // also suppressed
-               }
                if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
                        $deleted = true; // new revisions text is hidden
                        if ( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) )
                                $suppressed = true; // also suppressed
                }
+
                # If the diff cannot be shown due to a deleted revision, then output
                # the diff header and links to unhide (if available)...
                if ( $deleted && ( !$this->unhide || !$allowed ) ) {
                        $this->showDiffStyle();
                        $multi = $this->getMultiNotice();
-                       $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) );
+                       $out->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) );
                        if ( !$allowed ) {
                                $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff';
                                # Give explanation for why revision is not visible
-                               $wgOut->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
+                               $out->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
                                        array( $msg ) );
                        } else {
                                # Give explanation and add a link to view the diff...
-                               $link = $this->mTitle->getFullUrl( array(
-                                       'diff' => $this->mNewid,
-                                       'oldid' => $this->mOldid,
-                                       'unhide' => 1
-                               ) );
+                               $link = $this->getTitle()->getFullUrl( $this->getRequest()->appendQueryValue( 'unhide', '1', true ) );
                                $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
-                               $wgOut->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
+                               $out->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
                        }
                # Otherwise, output a regular diff...
                } else {
@@ -458,7 +380,7 @@ CONTROL;
                        $notice = '';
                        if ( $deleted ) {
                                $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view';
-                               $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" . wfMsgExt( $msg, 'parseinline' ) . "</div>\n";
+                               $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" . $this->msg( $msg )->parse() . "</div>\n";
                        }
                        $this->showDiff( $oldHeader, $newHeader, $notice );
                        if ( !$diffOnly ) {
@@ -469,29 +391,79 @@ CONTROL;
        }
 
        /**
-        * @param $rev Revision
+        * Get a link to mark the change as patrolled, or '' if there's either no
+        * revision to patrol or the user is not allowed to to it.
+        * Side effect: this method will call OutputPage::preventClickjacking()
+        * when a link is builded.
+        *
         * @return String
         */
-       protected function revisionDeleteLink( $rev ) {
-               global $wgUser;
-               $link = '';
-               $canHide = $wgUser->isAllowed( 'deleterevision' );
-               // Show del/undel link if:
-               // (a) the user can delete revisions, or
-               // (b) the user can view deleted revision *and* this one is deleted
-               if ( $canHide || ( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) ) {
-                       $sk = $wgUser->getSkin();
-                       if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
-                               $link = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+       protected function markPatrolledLink() {
+               global $wgUseRCPatrol;
+
+               if ( $this->mMarkPatrolledLink === null ) {
+                       // Prepare a change patrol link, if applicable
+                       if ( $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $this->getUser() ) ) {
+                               // If we've been given an explicit change identifier, use it; saves time
+                               if ( $this->mRcidMarkPatrolled ) {
+                                       $rcid = $this->mRcidMarkPatrolled;
+                                       $rc = RecentChange::newFromId( $rcid );
+                                       // Already patrolled?
+                                       $rcid = is_object( $rc ) && !$rc->getAttribute( 'rc_patrolled' ) ? $rcid : 0;
+                               } else {
+                                       // Look for an unpatrolled change corresponding to this diff
+                                       $db = wfGetDB( DB_SLAVE );
+                                       $change = RecentChange::newFromConds(
+                                               array(
+                                               // Redundant user,timestamp condition so we can use the existing index
+                                                       'rc_user_text'  => $this->mNewRev->getRawUserText(),
+                                                       'rc_timestamp'  => $db->timestamp( $this->mNewRev->getTimestamp() ),
+                                                       'rc_this_oldid' => $this->mNewid,
+                                                       'rc_last_oldid' => $this->mOldid,
+                                                       'rc_patrolled'  => 0
+                                               ),
+                                               __METHOD__
+                                       );
+                                       if ( $change instanceof RecentChange ) {
+                                               $rcid = $change->mAttribs['rc_id'];
+                                               $this->mRcidMarkPatrolled = $rcid;
+                                       } else {
+                                               // None found
+                                               $rcid = 0;
+                                       }
+                               }
+                               // Build the link
+                               if ( $rcid ) {
+                                       $this->getOutput()->preventClickjacking();
+                                       $token = $this->getUser()->getEditToken( $rcid );
+                                       $this->mMarkPatrolledLink = ' <span class="patrollink">[' . Linker::linkKnown(
+                                               $this->mNewPage,
+                                               $this->msg( 'markaspatrolleddiff' )->escaped(),
+                                               array(),
+                                               array(
+                                                       'action' => 'markpatrolled',
+                                                       'rcid' => $rcid,
+                                                       'token' => $token,
+                                               )
+                                       ) . ']</span>';
+                               } else {
+                                       $this->mMarkPatrolledLink = '';
+                               }
                        } else {
-                               $query = array(
-                                       'type'   => 'revision',
-                                       'target' => $rev->mTitle->getPrefixedDbkey(),
-                                       'ids'    => $rev->getId()
-                               );
-                               $link = $sk->revDeleteLink( $query,
-                                       $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
+                               $this->mMarkPatrolledLink = '';
                        }
+               }
+
+               return $this->mMarkPatrolledLink;
+       }
+
+       /**
+        * @param $rev Revision
+        * @return String
+        */
+       protected function revisionDeleteLink( $rev ) {
+               $link = Linker::getRevDeleteLink( $this->getUser(), $rev, $rev->getTitle() );
+               if ( $link !== '' ) {
                        $link = '&#160;&#160;&#160;' . $link . ' ';
                }
                return $link;
@@ -501,156 +473,81 @@ CONTROL;
         * Show the new revision of the page.
         */
        function renderNewRevision() {
-               global $wgOut, $wgUser;
                wfProfileIn( __METHOD__ );
+               $out = $this->getOutput();
+               $revHeader = $this->getRevisionHeader( $this->mNewRev );
                # Add "current version as of X" title
-               $wgOut->addHTML( "<hr class='diff-hr' />
-               <h2 class='diff-currentversion-title'>{$this->mPagetitle}</h2>\n" );
+               $out->addHTML( "<hr class='diff-hr' />
+               <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
                # Page content may be handled by a hooked call instead...
-               if ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $wgOut ) ) ) {
-                       # Use the current version parser cache if applicable
-                       $pCache = true;
-                       if ( !$this->mNewRev->isCurrent() ) {
-                               $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
-                               $pCache = false;
-                       }
-
+               if ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $out ) ) ) {
                        $this->loadNewText();
-                       $wgOut->setRevisionId( $this->mNewRev->getId() );
+                       $out->setRevisionId( $this->mNewid );
+                       $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
+                       $out->setArticleFlag( true );
 
-                       if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+                       if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
                                // Stolen from Article::view --AG 2007-10-11
                                // Give hooks a chance to customise the output
                                // @TODO: standardize this crap into one function
-                               if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mTitle, $wgOut ) ) ) {
+                               if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
                                        // Wrap the whole lot in a <pre> and don't parse
                                        $m = array();
-                                       preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
-                                       $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
-                                       $wgOut->addHTML( htmlspecialchars( $this->mNewtext ) );
-                                       $wgOut->addHTML( "\n</pre>\n" );
+                                       preg_match( '!\.(css|js)$!u', $this->mNewPage->getText(), $m );
+                                       $out->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
+                                       $out->addHTML( htmlspecialchars( $this->mNewtext ) );
+                                       $out->addHTML( "\n</pre>\n" );
                                }
-                       } elseif ( $pCache ) {
-                               $article = new Article( $this->mTitle, 0 );
-                               $pOutput = ParserCache::singleton()->get( $article, $wgOut->parserOptions() );
-                               if( $pOutput ) {
-                                       $wgOut->addParserOutput( $pOutput );
+                       } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
+                               // Handled by extension
+                       } else {
+                               // Normal page
+                               if ( $this->getTitle()->equals( $this->mNewPage ) ) {
+                                       // If the Title stored in the context is the same as the one
+                                       // of the new revision, we can use its associated WikiPage
+                                       // object.
+                                       $wikiPage = $this->getWikiPage();
                                } else {
-                                       $article->doViewParse();
+                                       // Otherwise we need to create our own WikiPage object
+                                       $wikiPage = WikiPage::factory( $this->mNewPage );
                                }
-                       } else {
-                               $wgOut->addWikiTextTidy( $this->mNewtext );
-                       }
 
-                       if ( !$this->mNewRev->isCurrent() ) {
-                               $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
-                       }
-               }
-               # Add redundant patrol link on bottom...
-               if ( $this->mRcidMarkPatrolled && $this->mTitle->quickUserCan( 'patrol' ) ) {
-                       $sk = $wgUser->getSkin();
-                       $token = $wgUser->editToken( $this->mRcidMarkPatrolled );
-                       $wgOut->preventClickjacking();
-                       $wgOut->addHTML(
-                               "<div class='patrollink'>[" . $sk->link(
-                                       $this->mTitle,
-                                       wfMsgHtml( 'markaspatrolleddiff' ),
-                                       array(),
-                                       array(
-                                               'action'        => 'markpatrolled',
-                                               'rcid'          => $this->mRcidMarkPatrolled,
-                                               'token'         => $token,
-                                       )
-                               ) . ']</div>'
-                        );
-               }
+                               $parserOptions = ParserOptions::newFromContext( $this->getContext() );
+                               $parserOptions->enableLimitReport();
+                               $parserOptions->setTidy( true );
 
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Show the first revision of an article. Uses normal diff headers in
-        * contrast to normal "old revision" display style.
-        */
-       function showFirstRevision() {
-               global $wgOut, $wgUser;
-               wfProfileIn( __METHOD__ );
-
-               # Get article text from the DB
-               #
-               if ( ! $this->loadNewText() ) {
-                       $t = $this->mTitle->getPrefixedText();
-                       $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ),
-                                       $this->deletedIdMarker( $this->mOldid ),
-                                       $this->deletedIdMarker( $this->mNewid ) );
-                       $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
-                       $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", "<span class='plainlinks'>$d</span>" );
-                       wfProfileOut( __METHOD__ );
-                       return;
-               }
-               if ( $this->mNewRev->isCurrent() ) {
-                       $wgOut->setArticleFlag( true );
-               }
-
-               # Check if user is allowed to look at this page. If not, bail out.
-               #
-               if ( !$this->mTitle->userCanRead() ) {
-                       $wgOut->loginToUse();
-                       $wgOut->output();
-                       wfProfileOut( __METHOD__ );
-                       throw new MWException( "Permission Error: you do not have access to view this page" );
-               }
+                               if ( !$this->mNewRev->isCurrent() ) {
+                                       $parserOptions->setEditSection( false );
+                               }
 
-               # Prepare the header box
-               #
-               $sk = $wgUser->getSkin();
+                               $parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
 
-               $next = $this->mTitle->getNextRevisionID( $this->mNewid );
-               if ( !$next ) {
-                       $nextlink = '';
-               } else {
-                       $nextlink = '<br />' . $sk->link(
-                               $this->mTitle,
-                               wfMsgHtml( 'nextdiff' ),
-                               array(
-                                       'id' => 'differences-nextlink'
-                               ),
-                               array(
-                                       'diff' => 'next',
-                                       'oldid' => $this->mNewid,
-                               ),
-                               array(
-                                       'known',
-                                       'noclasses'
-                               )
-                       );
+                               # WikiPage::getParserOutput() should not return false, but just in case
+                               if( $parserOutput ) {
+                                       $out->addParserOutput( $parserOutput );
+                               }
+                       }
                }
-               $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\">" .
-                       $sk->revUserTools( $this->mNewRev ) . "<br />" . $sk->revComment( $this->mNewRev ) . $nextlink . "</div>\n";
-
-               $wgOut->addHTML( $header );
-
-               $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) );
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
+               # Add redundant patrol link on bottom...
+               $out->addHTML( $this->markPatrolledLink() );
 
                wfProfileOut( __METHOD__ );
        }
 
        /**
-        * Get the diff text, send it to $wgOut
+        * Get the diff text, send it to the OutputPage object
         * Returns false if the diff could not be generated, otherwise returns true
         *
         * @return bool
         */
        function showDiff( $otitle, $ntitle, $notice = '' ) {
-               global $wgOut;
                $diff = $this->getDiff( $otitle, $ntitle, $notice );
                if ( $diff === false ) {
-                       $wgOut->addWikiMsg( 'missing-article', "<nowiki>(fixme, bug)</nowiki>", '' );
+                       $this->getOutput()->addWikiMsg( 'missing-article', "<nowiki>(fixme, bug)</nowiki>", '' );
                        return false;
                } else {
                        $this->showDiffStyle();
-                       $wgOut->addHTML( $diff );
+                       $this->getOutput()->addHTML( $diff );
                        return true;
                }
        }
@@ -659,8 +556,7 @@ CONTROL;
         * Add style sheets and supporting JS for diff display.
         */
        function showDiffStyle() {
-               global $wgOut;
-               $wgOut->addModuleStyles( 'mediawiki.legacy.diff' );
+               $this->getOutput()->addModuleStyles( 'mediawiki.action.history.diff' );
        }
 
        /**
@@ -694,16 +590,17 @@ CONTROL;
                if ( !$this->loadRevisionData() ) {
                        wfProfileOut( __METHOD__ );
                        return false;
-               } elseif ( $this->mOldRev && !$this->mOldRev->userCan( Revision::DELETED_TEXT ) ) {
+               } elseif ( $this->mOldRev && !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
                        wfProfileOut( __METHOD__ );
                        return false;
-               } elseif ( $this->mNewRev && !$this->mNewRev->userCan( Revision::DELETED_TEXT ) ) {
+               } elseif ( $this->mNewRev && !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
                        wfProfileOut( __METHOD__ );
                        return false;
                }
                // Short-circuit
-               if ( $this->mOldRev && $this->mNewRev
-                       && $this->mOldRev->getID() == $this->mNewRev->getID() )
+               // If mOldRev is false, it means that the 
+               if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
+                       && $this->mOldRev->getID() == $this->mNewRev->getID() ) )
                {
                        wfProfileOut( __METHOD__ );
                        return '';
@@ -775,10 +672,13 @@ CONTROL;
         *
         * @param $otext String: old text, must be already segmented
         * @param $ntext String: new text, must be already segmented
+        * @return bool|string
         */
        function generateDiffBody( $otext, $ntext ) {
                global $wgExternalDiffEngine, $wgContLang;
 
+               wfProfileIn( __METHOD__ );
+
                $otext = str_replace( "\r\n", "\n", $otext );
                $ntext = str_replace( "\r\n", "\n", $ntext );
 
@@ -789,6 +689,7 @@ CONTROL;
                        # input text to be HTML-escaped already
                        $otext = htmlspecialchars ( $wgContLang->segmentForDiff( $otext ) );
                        $ntext = htmlspecialchars ( $wgContLang->segmentForDiff( $ntext ) );
+                       wfProfileOut( __METHOD__ );
                        return $wgContLang->unsegmentForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
                        $this->debug( 'wikidiff1' );
                }
@@ -800,6 +701,7 @@ CONTROL;
                        $text = wikidiff2_do_diff( $otext, $ntext, 2 );
                        $text .= $this->debug( 'wikidiff2' );
                        wfProfileOut( 'wikidiff2_do_diff' );
+                       wfProfileOut( __METHOD__ );
                        return $text;
                }
                if ( $wgExternalDiffEngine != 'wikidiff3' && $wgExternalDiffEngine !== false ) {
@@ -846,6 +748,7 @@ CONTROL;
        /**
         * Generate a debug comment indicating diff generating time,
         * server node, and generator backend.
+        * @return string
         */
        protected function debug( $generator = "internal" ) {
                global $wgShowHostnames;
@@ -867,6 +770,7 @@ CONTROL;
 
        /**
         * Replace line numbers with the text in the user's language
+        * @return mixed
         */
        function localiseLineNumbers( $text ) {
                return preg_replace_callback( '/<!--LINE (\d+)-->/',
@@ -874,9 +778,8 @@ CONTROL;
        }
 
        function localiseLineNumbersCb( $matches ) {
-               global $wgLang;
                if ( $matches[1] === '1' && $this->mReducedLineNumbers ) return '';
-               return wfMsgExt( 'lineno', 'escape', $wgLang->formatNum( $matches[1] ) );
+               return $this->msg( 'lineno' )->numParams( $matches[1] )->escaped();
        }
 
 
@@ -900,10 +803,10 @@ CONTROL;
                        $newRev = $this->mNewRev;
                }
 
-               $nEdits = $this->mTitle->countRevisionsBetween( $oldRev, $newRev );
+               $nEdits = $this->mNewPage->countRevisionsBetween( $oldRev, $newRev );
                if ( $nEdits > 0 ) {
                        $limit = 100; // use diff-multi-manyusers if too many users
-                       $numUsers = $this->mTitle->countAuthorsBetween( $oldRev, $newRev, $limit );
+                       $numUsers = $this->mNewPage->countAuthorsBetween( $oldRev, $newRev, $limit );
                        return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
                }
                return ''; // nothing
@@ -917,15 +820,63 @@ CONTROL;
         * @return string
         */
        public static function intermediateEditsMsg( $numEdits, $numUsers, $limit ) {
-               global $wgLang;
                if ( $numUsers > $limit ) {
                        $msg = 'diff-multi-manyusers';
                        $numUsers = $limit;
                } else {
                        $msg = 'diff-multi';
                }
-               return wfMsgExt( $msg, 'parseinline',
-                       $wgLang->formatnum( $numEdits ), $wgLang->formatnum( $numUsers ) );
+               return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
+       }
+
+       /**
+        * Get a header for a specified revision.
+        *
+        * @param $rev Revision
+        * @param $complete String: 'complete' to get the header wrapped depending
+        *        the visibility of the revision and a link to edit the page.
+        * @return String HTML fragment
+        */
+       private function getRevisionHeader( Revision $rev, $complete = '' ) {
+               $lang = $this->getLanguage();
+               $user = $this->getUser();
+               $revtimestamp = $rev->getTimestamp();
+               $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
+               $dateofrev = $lang->userDate( $revtimestamp, $user );
+               $timeofrev = $lang->userTime( $revtimestamp, $user );
+
+               $header = $this->msg(
+                       $rev->isCurrent() ? 'currentrev-asof' : 'revisionasof',
+                       $timestamp,
+                       $dateofrev,
+                       $timeofrev
+               )->escaped();
+
+               if ( $complete !== 'complete' ) {
+                       return $header;
+               }
+
+               $title = $rev->getTitle();
+
+               $header = Linker::linkKnown( $title, $header, array(),
+                       array( 'oldid' => $rev->getID() ) );
+
+               if ( $rev->userCan( Revision::DELETED_TEXT, $user ) ) {
+                       $editQuery = array( 'action' => 'edit' );
+                       if ( !$rev->isCurrent() ) {
+                               $editQuery['oldid'] = $rev->getID();
+                       }
+
+                       $msg = $this->msg( $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold' )->escaped();
+                       $header .= ' (' . Linker::linkKnown( $title, $msg, array(), $editQuery ) . ')';
+                       if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+                               $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
+                       }
+               } else {
+                       $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
+               }
+
+               return $header;
        }
 
        /**
@@ -934,28 +885,36 @@ CONTROL;
         * @return string
         */
        function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
-               // shared.css sets diff in interface language/dir,
-               // but the actual content should be in the page language/dir
-               $pageLang = $this->mTitle->getPageLanguage();
-               $tableClass = 'diff diff-contentalign-' . htmlspecialchars( $pageLang->alignStart() );
+               // shared.css sets diff in interface language/dir, but the actual content
+               // is often in a different language, mostly the page content language/dir
+               $tableClass = 'diff diff-contentalign-' . htmlspecialchars( $this->getDiffLang()->alignStart() );
                $header = "<table class='$tableClass'>";
-               if ( $diff ) { // Safari/Chrome show broken output if cols not used
+
+               if ( !$diff && !$otitle ) {
                        $header .= "
-                       <col class='diff-marker' />
-                       <col class='diff-content' />
-                       <col class='diff-marker' />
-                       <col class='diff-content' />";
-                       $colspan = 2;
-                       $multiColspan = 4;
+                       <tr valign='top'>
+                       <td class='diff-ntitle'>{$ntitle}</td>
+                       </tr>";
+                       $multiColspan = 1;
                } else {
-                       $colspan = 1;
-                       $multiColspan = 2;
+                       if ( $diff ) { // Safari/Chrome show broken output if cols not used
+                               $header .= "
+                               <col class='diff-marker' />
+                               <col class='diff-content' />
+                               <col class='diff-marker' />
+                               <col class='diff-content' />";
+                               $colspan = 2;
+                               $multiColspan = 4;
+                       } else {
+                               $colspan = 1;
+                               $multiColspan = 2;
+                       }
+                       $header .= "
+                       <tr valign='top'>
+                       <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
+                       <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
+                       </tr>";
                }
-               $header .= "
-               <tr valign='top'>
-               <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
-               <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
-               </tr>";
 
                if ( $multi != '' ) {
                        $header .= "<tr><td colspan='{$multiColspan}' align='center' class='diff-multi'>{$multi}</td></tr>";
@@ -977,6 +936,50 @@ CONTROL;
                $this->mRevisionsLoaded = true;
        }
 
+       /**
+        * Set the language in which the diff text is written
+        * (Defaults to page content language).
+        * @since 1.19
+        */
+       function setTextLanguage( $lang ) {
+               $this->mDiffLang = wfGetLangObj( $lang );
+       }
+
+       /**
+        * Load revision IDs
+        */
+       private function loadRevisionIds() {
+               if ( $this->mRevisionsIdsLoaded ) {
+                       return;
+               }
+
+               $this->mRevisionsIdsLoaded = true;
+
+               $old = $this->mOldid;
+               $new = $this->mNewid;
+
+               if ( $new === 'prev' ) {
+                       # Show diff between revision $old and the previous one.
+                       # Get previous one from DB.
+                       $this->mNewid = intval( $old );
+                       $this->mOldid = $this->getTitle()->getPreviousRevisionID( $this->mNewid );
+               } elseif ( $new === 'next' ) {
+                       # Show diff between revision $old and the next one.
+                       # Get next one from DB.
+                       $this->mOldid = intval( $old );
+                       $this->mNewid = $this->getTitle()->getNextRevisionID( $this->mOldid );
+                       if ( $this->mNewid === false ) {
+                               # if no result, NewId points to the newest old revision. The only newer
+                               # revision is cur, which is "0".
+                               $this->mNewid = 0;
+                       }
+               } else {
+                       $this->mOldid = intval( $old );
+                       $this->mNewid = intval( $new );
+                       wfRunHooks( 'NewDifferenceEngine', array( $this->getTitle(), &$this->mOldid, &$this->mNewid, $old, $new ) );
+               }
+       }
+
        /**
         * Load revision metadata for the specified articles. If newid is 0, then compare
         * the old article in oldid to the current article; if oldid is 0, then
@@ -990,72 +993,27 @@ CONTROL;
         * @return bool
         */
        function loadRevisionData() {
-               global $wgLang, $wgUser;
                if ( $this->mRevisionsLoaded ) {
                        return true;
-               } else {
-                       // Whether it succeeds or fails, we don't want to try again
-                       $this->mRevisionsLoaded = true;
                }
 
+               // Whether it succeeds or fails, we don't want to try again
+               $this->mRevisionsLoaded = true;
+
+               $this->loadRevisionIds();
+
                // Load the new revision object
                $this->mNewRev = $this->mNewid
                        ? Revision::newFromId( $this->mNewid )
-                       : Revision::newFromTitle( $this->mTitle );
+                       : Revision::newFromTitle( $this->getTitle() );
+
                if ( !$this->mNewRev instanceof Revision ) {
                        return false;
                }
 
                // Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
                $this->mNewid = $this->mNewRev->getId();
-
-               // Check if page is editable
-               $editable = $this->mNewRev->getTitle()->userCan( 'edit' );
-
-               // Set assorted variables
-               $timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(), true );
-               $dateofrev = $wgLang->date( $this->mNewRev->getTimestamp(), true );
-               $timeofrev = $wgLang->time( $this->mNewRev->getTimestamp(), true );
                $this->mNewPage = $this->mNewRev->getTitle();
-               if ( $this->mNewRev->isCurrent() ) {
-                       $newLink = $this->mNewPage->escapeLocalUrl( array(
-                               'oldid' => $this->mNewid
-                       ) );
-                       $this->mPagetitle = htmlspecialchars( wfMsg(
-                               'currentrev-asof',
-                               $timestamp,
-                               $dateofrev,
-                               $timeofrev
-                       ) );
-                       $newEdit = $this->mNewPage->escapeLocalUrl( array(
-                               'action' => 'edit'
-                       ) );
-
-                       $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
-                       $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
-               } else {
-                       $newLink = $this->mNewPage->escapeLocalUrl( array(
-                               'oldid' => $this->mNewid
-                       ) );
-                       $newEdit = $this->mNewPage->escapeLocalUrl( array(
-                               'action' => 'edit',
-                               'oldid' => $this->mNewid
-                       ) );
-                       $this->mPagetitle = htmlspecialchars( wfMsg(
-                               'revisionasof',
-                               $timestamp,
-                               $dateofrev,
-                               $timeofrev
-                       ) );
-
-                       $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
-                       $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
-               }
-               if ( !$this->mNewRev->userCan( Revision::DELETED_TEXT ) ) {
-                       $this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
-               } elseif ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
-                       $this->mNewtitle = "<span class='history-deleted'>{$this->mNewtitle}</span>";
-               }
 
                // Load the old revision object
                $this->mOldRev = false;
@@ -1079,38 +1037,6 @@ CONTROL;
 
                if ( $this->mOldRev ) {
                        $this->mOldPage = $this->mOldRev->getTitle();
-
-                       $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
-                       $dateofrev = $wgLang->date( $this->mOldRev->getTimestamp(), true );
-                       $timeofrev = $wgLang->time( $this->mOldRev->getTimestamp(), true );
-                       $oldLink = $this->mOldPage->escapeLocalUrl( array(
-                               'oldid' => $this->mOldid
-                       ) );
-                       $oldEdit = $this->mOldPage->escapeLocalUrl( array(
-                               'action' => 'edit',
-                               'oldid' => $this->mOldid
-                       ) );
-                       $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t, $dateofrev, $timeofrev ) );
-
-                       $this->mOldtitle = "<a href='$oldLink'>{$this->mOldPagetitle}</a>"
-                       . " (<a href='$oldEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
-                       // Add an "undo" link
-                       if ( $editable && !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
-                               $undoLink = Html::element( 'a', array(
-                                       'href' => $this->mNewPage->getLocalUrl( array(
-                                               'action' => 'edit',
-                                               'undoafter' => $this->mOldid,
-                                               'undo' => $this->mNewid ) ),
-                                       'title' => $wgUser->getSkin()->titleAttrib( 'undo' )
-                               ), wfMsg( 'editundo' ) );
-                               $this->mNewtitle .= ' (' . $undoLink . ')';
-                       }
-
-                       if ( !$this->mOldRev->userCan( Revision::DELETED_TEXT ) ) {
-                               $this->mOldtitle = '<span class="history-deleted">' . $this->mOldPagetitle . '</span>';
-                       } elseif ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
-                               $this->mOldtitle = '<span class="history-deleted">' . $this->mOldtitle . '</span>';
-                       }
                }
 
                return true;