messing with wfMerge
[lhc/web/wiklou.git] / includes / EditPage.php
index b34184f..91d2be4 100644 (file)
  * headaches, which may be fatal.
  */
 class EditPage {
+
+       /**
+        * Status: Article successfully updated
+        */
        const AS_SUCCESS_UPDATE            = 200;
+
+       /**
+        * Status: Article successfully created
+        */
        const AS_SUCCESS_NEW_ARTICLE       = 201;
+
+       /**
+        * Status: Article update aborted by a hook function
+        */
        const AS_HOOK_ERROR                = 210;
+
+       /**
+        * Status: The filter function set in $wgFilterCallback returned true (= block it) 
+        */
        const AS_FILTERING                 = 211;
+
+       /**
+        * Status: A hook function returned an error
+        */
        const AS_HOOK_ERROR_EXPECTED       = 212;
+
+       /**
+        * Status: User is blocked from editting this page
+        */
        const AS_BLOCKED_PAGE_FOR_USER     = 215;
+
+       /**
+        * Status: Content too big (> $wgMaxArticleSize)
+        */
        const AS_CONTENT_TOO_BIG           = 216;
+
+       /**
+        * Status: User cannot edit? (not used)
+        */
        const AS_USER_CANNOT_EDIT          = 217;
+
+       /**
+        * Status: this anonymous user is not allowed to edit this page
+        */
        const AS_READ_ONLY_PAGE_ANON       = 218;
+
+       /**
+        * Status: this logged in user is not allowed to edit this page
+        */
        const AS_READ_ONLY_PAGE_LOGGED     = 219;
+
+       /**
+        * Status: wiki is in readonly mode (wfReadOnly() == true)
+        */
        const AS_READ_ONLY_PAGE            = 220;
+
+       /**
+        * Status: rate limiter for action 'edit' was tripped
+        */
        const AS_RATE_LIMITED              = 221;
+
+       /**
+        * Status: article was deleted while editting and param wpRecreate == false or form
+        * was not posted
+        */
        const AS_ARTICLE_WAS_DELETED       = 222;
+
+       /**
+        * Status: user tried to create this page, but is not allowed to do that
+        * ( Title->usercan('create') == false )
+        */
        const AS_NO_CREATE_PERMISSION      = 223;
+
+       /**
+        * Status: user tried to create a blank page
+        */
        const AS_BLANK_ARTICLE             = 224;
+
+       /**
+        * Status: (non-resolvable) edit conflict
+        */
        const AS_CONFLICT_DETECTED         = 225;
+
+       /**
+        * Status: no edit summary given and the user has forceeditsummary set and the user is not
+        * editting in his own userspace or talkspace and wpIgnoreBlankSummary == false
+        */
        const AS_SUMMARY_NEEDED            = 226;
+
+       /**
+        * Status: user tried to create a new section without content
+        */
        const AS_TEXTBOX_EMPTY             = 228;
+
+       /**
+        * Status: article is too big (> $wgMaxArticleSize), after merging in the new section
+        */
        const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
+
+       /**
+        * not used
+        */
        const AS_OK                        = 230;
+
+       /**
+        * Status: WikiPage::doEdit() was unsuccessfull
+        */
        const AS_END                       = 231;
+
+       /**
+        * Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex
+        */
        const AS_SPAM_ERROR                = 232;
+
+       /**
+        * Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
+        */
        const AS_IMAGE_REDIRECT_ANON       = 233;
+
+       /**
+        * Status: logged in user is not allowed to upload (User::isAllowed('upload') == false)
+        */
        const AS_IMAGE_REDIRECT_LOGGED     = 234;
 
        /**
@@ -55,7 +154,7 @@ class EditPage {
         */
        var $mTitle;
        private $mContextTitle = null;
-       var $action;
+       var $action = 'submit';
        var $isConflict = false;
        var $isCssJsSubpage = false;
        var $isCssSubpage = false;
@@ -91,20 +190,20 @@ class EditPage {
        var $save = false, $preview = false, $diff = false;
        var $minoredit = false, $watchthis = false, $recreate = false;
        var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
-       var $edittime = '', $section = '', $starttime = '';
+       var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
        var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
 
        # Placeholders for text injection by hooks (must be HTML)
        # extensions should take care to _append_ to the present value
-       public $editFormPageTop; // Before even the preview
-       public $editFormTextTop;
-       public $editFormTextBeforeContent;
-       public $editFormTextAfterWarn;
-       public $editFormTextAfterTools;
-       public $editFormTextBottom;
-       public $editFormTextAfterContent;
-       public $previewTextAfterContent;
-       public $mPreloadText;
+       public $editFormPageTop = ''; // Before even the preview
+       public $editFormTextTop = '';
+       public $editFormTextBeforeContent = '';
+       public $editFormTextAfterWarn = '';
+       public $editFormTextAfterTools = '';
+       public $editFormTextBottom = '';
+       public $editFormTextAfterContent = '';
+       public $previewTextAfterContent = '';
+       public $mPreloadText = '';
 
        /* $didSave should be set to true whenever an article was succesfully altered. */
        public $didSave = false;
@@ -113,33 +212,28 @@ class EditPage {
        public $suppressIntro = false;
 
        /**
-        * @todo document
         * @param $article Article
         */
-       function __construct( $article ) {
-               $this->mArticle =& $article;
+       public function __construct( Article $article ) {
+               $this->mArticle = $article;
                $this->mTitle = $article->getTitle();
-               $this->action = 'submit';
-
-               # Placeholders for text injection by hooks (empty per default)
-               $this->editFormPageTop =
-               $this->editFormTextTop =
-               $this->editFormTextBeforeContent =
-               $this->editFormTextAfterWarn =
-               $this->editFormTextAfterTools =
-               $this->editFormTextBottom =
-               $this->editFormTextAfterContent =
-               $this->previewTextAfterContent =
-               $this->mPreloadText = "";
        }
 
        /**
         * @return Article
         */
-       function getArticle() {
+       public function getArticle() {
                return $this->mArticle;
        }
 
+       /**
+        * @since 1.19
+        * @return Title
+        */
+       public function getTitle() {
+               return $this->mTitle;
+       }
+
        /**
         * Set the context Title object
         *
@@ -165,196 +259,6 @@ class EditPage {
                }
        }
 
-       /**
-        * Fetch initial editing page content.
-        *
-        * @param $def_text string
-        * @return mixed string on success, $def_text for invalid sections
-        * @private
-        */
-       function getContent( $def_text = '' ) {
-               global $wgOut, $wgRequest, $wgParser;
-
-               wfProfileIn( __METHOD__ );
-               # Get variables from query string :P
-               $section = $wgRequest->getVal( 'section' );
-
-               $preload = $wgRequest->getVal( 'preload',
-                       // Custom preload text for new sections
-                       $section === 'new' ? 'MediaWiki:addsection-preload' : '' );
-               $undoafter = $wgRequest->getVal( 'undoafter' );
-               $undo = $wgRequest->getVal( 'undo' );
-
-               // For message page not locally set, use the i18n message.
-               // For other non-existent articles, use preload text if any.
-               if ( !$this->mTitle->exists() ) {
-                       if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-                               # If this is a system message, get the default text.
-                               $text = $this->mTitle->getDefaultMessageText();
-                               if( $text === false ) {
-                                       $text = $this->getPreloadedText( $preload );
-                               }
-                       } else {
-                               # If requested, preload some text.
-                               $text = $this->getPreloadedText( $preload );
-                       }
-               // For existing pages, get text based on "undo" or section parameters.
-               } else {
-                       $text = $this->mArticle->getContent();
-                       if ( $undo > 0 && $undoafter > 0 && $undo < $undoafter ) {
-                               # If they got undoafter and undo round the wrong way, switch them
-                               list( $undo, $undoafter ) = array( $undoafter, $undo );
-                       }
-                       if ( $undo > 0 && $undo > $undoafter ) {
-                               # Undoing a specific edit overrides section editing; section-editing
-                               # doesn't work with undoing.
-                               if ( $undoafter ) {
-                                       $undorev = Revision::newFromId( $undo );
-                                       $oldrev = Revision::newFromId( $undoafter );
-                               } else {
-                                       $undorev = Revision::newFromId( $undo );
-                                       $oldrev = $undorev ? $undorev->getPrevious() : null;
-                               }
-
-                               # Sanity check, make sure it's the right page,
-                               # the revisions exist and they were not deleted.
-                               # Otherwise, $text will be left as-is.
-                               if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
-                                       $undorev->getPage() == $oldrev->getPage() &&
-                                       $undorev->getPage() == $this->mArticle->getID() &&
-                                       !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
-                                       !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
-
-                                       $undotext = $this->mArticle->getUndoText( $undorev, $oldrev );
-                                       if ( $undotext === false ) {
-                                               # Warn the user that something went wrong
-                                               $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-failure">' .
-                                                       wfMsgNoTrans( 'undo-failure' ) . '</div>', true, /* interface */true );
-                                       } else {
-                                               $text = $undotext;
-                                               # Inform the user of our success and set an automatic edit summary
-                                               $this->editFormPageTop .= $wgOut->parse( '<div class="mw-undo-success">' .
-                                                       wfMsgNoTrans( 'undo-success' ) . '</div>', true, /* interface */true );
-                                               $firstrev = $oldrev->getNext();
-                                               # If we just undid one rev, use an autosummary
-                                               if ( $firstrev->mId == $undo ) {
-                                                       $this->summary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
-                                                       $this->undidRev = $undo;
-                                               }
-                                               $this->formtype = 'diff';
-                                       }
-                               } else {
-                                       // Failed basic sanity checks.
-                                       // Older revisions may have been removed since the link
-                                       // was created, or we may simply have got bogus input.
-                                       $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-norev">' .
-                                               wfMsgNoTrans( 'undo-norev' ) . '</div>', true, /* interface */true );
-                               }
-                       } elseif ( $section != '' ) {
-                               if ( $section == 'new' ) {
-                                       $text = $this->getPreloadedText( $preload );
-                               } else {
-                                       // Get section edit text (returns $def_text for invalid sections)
-                                       $text = $wgParser->getSection( $text, $section, $def_text );
-                               }
-                       }
-               }
-
-               wfProfileOut( __METHOD__ );
-               return $text;
-       }
-
-       /**
-        * Use this method before edit() to preload some text into the edit box
-        *
-        * @param $text string
-        */
-       public function setPreloadedText( $text ) {
-               $this->mPreloadText = $text;
-       }
-
-       /**
-        * Get the contents to be preloaded into the box, either set by
-        * an earlier setPreloadText() or by loading the given page.
-        *
-        * @param $preload String: representing the title to preload from.
-        * @return String
-        */
-       protected function getPreloadedText( $preload ) {
-               global $wgUser, $wgParser;
-               if ( !empty( $this->mPreloadText ) ) {
-                       return $this->mPreloadText;
-               } elseif ( $preload !== '' ) {
-                       $title = Title::newFromText( $preload );
-                       # Check for existence to avoid getting MediaWiki:Noarticletext
-                       if ( isset( $title ) && $title->exists() && $title->userCanRead() ) {
-                               $article = new Article( $title );
-
-                               if ( $article->isRedirect() ) {
-                                       $title = Title::newFromRedirectRecurse( $article->getContent() );
-                                       # Redirects to missing titles are displayed, to hidden pages are followed
-                                       # Copying observed behaviour from ?action=view
-                                       if ( $title->exists() ) {
-                                               if ($title->userCanRead() ) {
-                                                       $article = new Article( $title );
-                                               } else {
-                                                       return "";
-                                               }
-                                       }
-                               }
-                               $parserOptions = ParserOptions::newFromUser( $wgUser );
-                               return $wgParser->getPreloadText( $article->getContent(), $title, $parserOptions );
-                       }
-               }
-               return '';
-       }
-
-       /**
-        * Check if a page was deleted while the user was editing it, before submit.
-        * Note that we rely on the logging table, which hasn't been always there,
-        * but that doesn't matter, because this only applies to brand new
-        * deletes.
-        */
-       protected function wasDeletedSinceLastEdit() {
-               if ( $this->deletedSinceEdit !== null ) {
-                       return $this->deletedSinceEdit;
-               }
-
-               $this->deletedSinceEdit = false;
-
-               if ( $this->mTitle->isDeletedQuick() ) {
-                       $this->lastDelete = $this->getLastDelete();
-                       if ( $this->lastDelete ) {
-                               $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
-                               if ( $deleteTime > $this->starttime ) {
-                                       $this->deletedSinceEdit = true;
-                               }
-                       }
-               }
-
-               return $this->deletedSinceEdit;
-       }
-
-       /**
-        * Checks whether the user entered a skin name in uppercase,
-        * e.g. "User:Example/Monobook.css" instead of "monobook.css"
-        *
-        * @return bool
-        */
-       protected function isWrongCaseCssJsPage() {
-               if( $this->mTitle->isCssJsSubpage() ) {
-                       $name = $this->mTitle->getSkinFromCssJsSubpage();
-                       $skins = array_merge(
-                               array_keys( Skin::getSkinNames() ),
-                               array( 'common' )
-                       );
-                       return !in_array( $name, $skins )
-                               && in_array( strtolower( $name ), $skins );
-               } else {
-                       return false;
-               }
-       }
-
        function submit() {
                $this->edit();
        }
@@ -380,6 +284,13 @@ class EditPage {
                wfProfileIn( __METHOD__ );
                wfDebug( __METHOD__.": enter\n" );
 
+               // If they used redlink=1 and the page exists, redirect to the main article
+               if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
+                       $wgOut->redirect( $this->mTitle->getFullURL() );
+                       wfProfileOut( __METHOD__ );
+                       return;
+               }
+
                $this->importFormData( $wgRequest );
                $this->firsttime = false;
 
@@ -395,45 +306,31 @@ class EditPage {
                        $this->preview = true;
                }
 
-               $wgOut->addModules( array( 'mediawiki.action.edit' ) );
-
-               if ( $wgUser->getOption( 'uselivepreview', false ) ) {
-                       $wgOut->addModules( 'mediawiki.legacy.preview' );
+               if ( $this->save ) {
+                       $this->formtype = 'save';
+               } elseif ( $this->preview ) {
+                       $this->formtype = 'preview';
+               } elseif ( $this->diff ) {
+                       $this->formtype = 'diff';
+               } else { # First time through
+                       $this->firsttime = true;
+                       if ( $this->previewOnOpen() ) {
+                               $this->formtype = 'preview';
+                       } else {
+                               $this->formtype = 'initial';
+                       }
                }
-               // Bug #19334: textarea jumps when editing articles in IE8
-               $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
 
                $permErrors = $this->getEditPermissionErrors();
                if ( $permErrors ) {
+                       wfDebug( __METHOD__ . ": User can't edit\n" );
                        // Auto-block user's IP if the account was "hard" blocked
                        $wgUser->spreadAnyEditBlock();
 
-                       wfDebug( __METHOD__ . ": User can't edit\n" );
-                       $content = $this->getContent( null );
-                       $content = $content === '' ? null : $content;
-                       $this->readOnlyPage( $content, true, $permErrors, 'edit' );
+                       $this->displayPermissionsError( $permErrors );
+
                        wfProfileOut( __METHOD__ );
                        return;
-               } else {
-                       if ( $this->save ) {
-                               $this->formtype = 'save';
-                       } elseif ( $this->preview ) {
-                               $this->formtype = 'preview';
-                       } elseif ( $this->diff ) {
-                               $this->formtype = 'diff';
-                       } else { # First time through
-                               $this->firsttime = true;
-                               if ( $this->previewOnOpen() ) {
-                                       $this->formtype = 'preview';
-                               } else {
-                                       $this->formtype = 'initial';
-                               }
-                       }
-               }
-
-               // If they used redlink=1 and the page exists, redirect to the main article
-               if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
-                       $wgOut->redirect( $this->mTitle->getFullURL() );
                }
 
                wfProfileIn( __METHOD__."-business-end" );
@@ -447,36 +344,8 @@ class EditPage {
                $this->isNew                = !$this->mTitle->exists() || $this->section == 'new';
 
                # Show applicable editing introductions
-               if ( $this->formtype == 'initial' || $this->firsttime )
+               if ( $this->formtype == 'initial' || $this->firsttime ) {
                        $this->showIntro();
-
-               if ( $this->mTitle->isTalkPage() ) {
-                       $wgOut->addWikiMsg( 'talkpagetext' );
-               }
-
-               # Optional notices on a per-namespace and per-page basis
-               $editnotice_ns   = 'editnotice-'.$this->mTitle->getNamespace();
-               $editnotice_ns_message = wfMessage( $editnotice_ns )->inContentLanguage();
-               if ( $editnotice_ns_message->exists() ) {
-                       $wgOut->addWikiText( $editnotice_ns_message->plain() );
-               }
-               if ( MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
-                       $parts = explode( '/', $this->mTitle->getDBkey() );
-                       $editnotice_base = $editnotice_ns;
-                       while ( count( $parts ) > 0 ) {
-                               $editnotice_base .= '-'.array_shift( $parts );
-                               $editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
-                               if ( $editnotice_base_msg->exists() ) {
-                                       $wgOut->addWikiText( $editnotice_base_msg->plain()  );
-                               }
-                       }
-               } else {
-                       # Even if there are no subpages in namespace, we still don't want / in MW ns.
-                       $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
-                       $editnoticeMsg = wfMessage( $editnoticeText )->inContentLanguage();
-                       if ( $editnoticeMsg->exists() ) {
-                               $wgOut->addWikiText( $editnoticeMsg->plain() );
-                       }
                }
 
                # Attempt submission here.  This will check for edit conflicts,
@@ -537,11 +406,70 @@ class EditPage {
        }
 
        /**
-        * Show a read-only error
-        * Parameters are the same as OutputPage:readOnlyPage()
-        * Redirect to the article page if redlink=1
+        * Display a permissions error page, like OutputPage::showPermissionsErrorPage(),
+        * but with the following differences:
+        * - If redlink=1, the user will be redirected to the page
+        * - If there is content to display or the error occurs while either saving,
+        *   previewing or showing the difference, it will be a
+        *   "View source for ..." page displaying the source code after the error message.
+        *
+        * @since 1.19
+        * @param $permErrors Array of permissions errors, as returned by
+        *                    Title::getUserPermissionsErrors().
+        */
+       protected function displayPermissionsError( array $permErrors ) {
+               global $wgRequest, $wgOut;
+
+               if ( $wgRequest->getBool( 'redlink' ) ) {
+                       // The edit page was reached via a red link.
+                       // Redirect to the article page and let them click the edit tab if
+                       // they really want a permission error.
+                       $wgOut->redirect( $this->mTitle->getFullUrl() );
+                       return;
+               }
+
+               $content = $this->getContent();
+
+               # Use the normal message if there's nothing to display
+               if ( $this->firsttime && $content === '' ) {
+                       $action = $this->mTitle->exists() ? 'edit' :
+                               ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
+                       throw new PermissionsError( $action, $permErrors );
+               }
+
+               $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
+               $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
+               $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
+               $wgOut->addHTML( "<hr />\n" );
+
+               # If the user made changes, preserve them when showing the markup
+               # (This happens when a user is blocked during edit, for instance)
+               if ( !$this->firsttime ) {
+                       $content = $this->textbox1;
+                       $wgOut->addWikiMsg( 'viewyourtext' );
+               } else {
+                       $wgOut->addWikiMsg( 'viewsourcetext' );
+               }
+
+               $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
+
+               $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
+                       Linker::formatTemplates( $this->getTemplates() ) ) );
+
+               if ( $this->mTitle->exists() ) {
+                       $wgOut->returnToMain( null, $this->mTitle );
+               }
+       }
+
+       /**
+        * Show a read-only error
+        * Parameters are the same as OutputPage:readOnlyPage()
+        * Redirect to the article page if redlink=1
+        * @deprecated in 1.19; use displayPermissionsError() instead
         */
        function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
+               wfDeprecated( __METHOD__, '1.19' );
+               
                global $wgRequest, $wgOut;
                if ( $wgRequest->getBool( 'redlink' ) ) {
                        // The edit page was reached via a red link.
@@ -584,30 +512,37 @@ class EditPage {
        }
 
        /**
-        * Does this EditPage class support section editing?
-        * This is used by EditPage subclasses to indicate their ui cannot handle section edits
+        * Checks whether the user entered a skin name in uppercase,
+        * e.g. "User:Example/Monobook.css" instead of "monobook.css"
         *
         * @return bool
         */
-       protected function isSectionEditSupported() {
-               return true;
+       protected function isWrongCaseCssJsPage() {
+               if( $this->mTitle->isCssJsSubpage() ) {
+                       $name = $this->mTitle->getSkinFromCssJsSubpage();
+                       $skins = array_merge(
+                               array_keys( Skin::getSkinNames() ),
+                               array( 'common' )
+                       );
+                       return !in_array( $name, $skins )
+                               && in_array( strtolower( $name ), $skins );
+               } else {
+                       return false;
+               }
        }
 
        /**
-        * Returns the URL to use in the form's action attribute.
-        * This is used by EditPage subclasses when simply customizing the action
-        * variable in the constructor is not enough. This can be used when the
-        * EditPage lives inside of a Special page rather than a custom page action.
+        * Does this EditPage class support section editing?
+        * This is used by EditPage subclasses to indicate their ui cannot handle section edits
         *
-        * @param $title Title object for which is being edited (where we go to for &action= links)
-        * @return string
+        * @return bool
         */
-       protected function getActionURL( Title $title ) {
-               return $title->getLocalURL( array( 'action' => $this->action ) );
+       protected function isSectionEditSupported() {
+               return true;
        }
 
        /**
-        * @todo document
+        * This function collects the form data and uses it to populate various member variables.
         * @param $request WebRequest
         */
        function importFormData( &$request ) {
@@ -637,15 +572,25 @@ class EditPage {
                        # Truncate for whole multibyte characters. +5 bytes for ellipsis
                        $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250 );
 
-                       # Remove extra headings from summaries and new sections.
-                       $this->summary = preg_replace('/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary);
+                       # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
+                       # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
+                       # section titles.
+                       $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
+                       
+                       # Treat sectiontitle the same way as summary.
+                       # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
+                       # currently doing double duty as both edit summary and section title. Right now this
+                       # is just to allow API edits to work around this limitation, but this should be
+                       # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
+                       $this->sectiontitle = $wgLang->truncate( $request->getText( 'wpSectionTitle' ), 250 );
+                       $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
 
                        $this->edittime = $request->getVal( 'wpEdittime' );
                        $this->starttime = $request->getVal( 'wpStarttime' );
 
                        $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
 
-                       if ($this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null) {
+                       if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
                                // wpTextbox1 field is missing, possibly due to being "too big"
                                // according to some filter rules such as Suhosin's setting for
                                // suhosin.request.max_value_length (d'oh)
@@ -712,19 +657,24 @@ class EditPage {
                } else {
                        # Not a posted form? Start with nothing.
                        wfDebug( __METHOD__ . ": Not a posted form.\n" );
-                       $this->textbox1  = '';
-                       $this->summary   = '';
-                       $this->edittime  = '';
-                       $this->starttime = wfTimestampNow();
-                       $this->edit      = false;
-                       $this->preview   = false;
-                       $this->save      = false;
-                       $this->diff      = false;
-                       $this->minoredit = false;
-                       $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
-                       $this->recreate  = false;
-
+                       $this->textbox1     = '';
+                       $this->summary      = '';
+                       $this->sectiontitle = '';
+                       $this->edittime     = '';
+                       $this->starttime    = wfTimestampNow();
+                       $this->edit         = false;
+                       $this->preview      = false;
+                       $this->save         = false;
+                       $this->diff         = false;
+                       $this->minoredit    = false;
+                       $this->watchthis    = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
+                       $this->recreate     = false;
+                       
+                       // When creating a new section, we can preload a section title by passing it as the
+                       // preloadtitle parameter in the URL (Bug 13100)
                        if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
+                               $this->sectiontitle = $request->getVal( 'preloadtitle' );
+                               // Once wpSummary isn't being use for setting section titles, we should delete this.
                                $this->summary = $request->getVal( 'preloadtitle' );
                        }
                        elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
@@ -739,7 +689,6 @@ class EditPage {
                $this->bot = $request->getBool( 'bot', true );
                $this->nosummary = $request->getBool( 'nosummary' );
 
-               // @todo FIXME: Unused variable?
                $this->oldid = $request->getInt( 'oldid' );
 
                $this->live = $request->getCheck( 'live' );
@@ -766,142 +715,366 @@ class EditPage {
        }
 
        /**
-        * Make sure the form isn't faking a user's credentials.
-        *
-        * @param $request WebRequest
-        * @return bool
-        * @private
+        * Initialise form fields in the object
+        * Called on the first invocation, e.g. when a user clicks an edit link
+        * @return bool -- if the requested section is valid
         */
-       function tokenOk( &$request ) {
+       function initialiseForm() {
                global $wgUser;
-               $token = $request->getVal( 'wpEditToken' );
-               $this->mTokenOk = $wgUser->matchEditToken( $token );
-               $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
-               return $this->mTokenOk;
+               $this->edittime = $this->mArticle->getTimestamp();
+               $this->textbox1 = $this->getContent( false );
+               // activate checkboxes if user wants them to be always active
+               # Sort out the "watch" checkbox
+               if ( $wgUser->getOption( 'watchdefault' ) ) {
+                       # Watch all edits
+                       $this->watchthis = true;
+               } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
+                       # Watch creations
+                       $this->watchthis = true;
+               } elseif ( $this->mTitle->userIsWatching() ) {
+                       # Already watched
+                       $this->watchthis = true;
+               }
+               if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
+                       $this->minoredit = true;
+               }
+               if ( $this->textbox1 === false ) {
+                       return false;
+               }
+               wfProxyCheck();
+               return true;
        }
 
        /**
-        * Show all applicable editing introductions
+        * Fetch initial editing page content.
+        *
+        * @param $def_text string
+        * @return mixed string on success, $def_text for invalid sections
+        * @private
         */
-       protected function showIntro() {
-               global $wgOut, $wgUser;
-               if ( $this->suppressIntro ) {
-                       return;
-               }
+       function getContent( $def_text = '' ) {
+               global $wgOut, $wgRequest, $wgParser;
 
-               $namespace = $this->mTitle->getNamespace();
+               wfProfileIn( __METHOD__ );
 
-               if ( $namespace == NS_MEDIAWIKI ) {
-                       # Show a warning if editing an interface message
-                       $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
-               }
+               $text = false;
 
-               # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
-               # Show log extract when the user is currently blocked
-               if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
-                       $parts = explode( '/', $this->mTitle->getText(), 2 );
-                       $username = $parts[0];
-                       $user = User::newFromName( $username, false /* allow IP users*/ );
-                       $ip = User::isIP( $username );
-                       if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
-                               $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
-                                       array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
-                       } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
-                               LogEventsList::showLogExtract(
-                                       $wgOut,
-                                       'block',
-                                       $user->getUserPage(),
-                                       '',
-                                       array(
-                                               'lim' => 1,
-                                               'showIfEmpty' => false,
-                                               'msgKey' => array(
-                                                       'blocked-notice-logextract',
-                                                       $user->getName() # Support GENDER in notice
-                                               )
-                                       )
-                               );
+               // For message page not locally set, use the i18n message.
+               // For other non-existent articles, use preload text if any.
+               if ( !$this->mTitle->exists() || $this->section == 'new' ) {
+                       if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
+                               # If this is a system message, get the default text.
+                               $text = $this->mTitle->getDefaultMessageText();
                        }
-               }
-               # Try to add a custom edit intro, or use the standard one if this is not possible.
-               if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
-                       if ( $wgUser->isLoggedIn() ) {
-                               $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1\n</div>", 'newarticletext' );
+                       if ( $text === false ) {
+                               # If requested, preload some text.
+                               $preload = $wgRequest->getVal( 'preload',
+                                       // Custom preload text for new sections
+                                       $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
+                               $text = $this->getPreloadedText( $preload );
+                       }
+               // For existing pages, get text based on "undo" or section parameters.
+               } else {
+                       if ( $this->section != '' ) {
+                               // Get section edit text (returns $def_text for invalid sections)
+                               $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
                        } else {
-                               $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1\n</div>", 'newarticletextanon' );
+                               $undoafter = $wgRequest->getInt( 'undoafter' );
+                               $undo = $wgRequest->getInt( 'undo' );
+
+                               if ( $undo > 0 && $undoafter > 0 ) {
+                                       if ( $undo < $undoafter ) {
+                                               # If they got undoafter and undo round the wrong way, switch them
+                                               list( $undo, $undoafter ) = array( $undoafter, $undo );
+                                       }
+
+                                       $undorev = Revision::newFromId( $undo );
+                                       $oldrev = Revision::newFromId( $undoafter );
+
+                                       # Sanity check, make sure it's the right page,
+                                       # the revisions exist and they were not deleted.
+                                       # Otherwise, $text will be left as-is.
+                                       if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
+                                               $undorev->getPage() == $oldrev->getPage() &&
+                                               $undorev->getPage() == $this->mTitle->getArticleId() &&
+                                               !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
+                                               !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
+
+                                               $text = $this->mArticle->getUndoText( $undorev, $oldrev );
+                                               if ( $text === false ) {
+                                                       # Warn the user that something went wrong
+                                                       $undoMsg = 'failure';
+                                               } else {
+                                                       # Inform the user of our success and set an automatic edit summary
+                                                       $undoMsg = 'success';
+
+                                                       # If we just undid one rev, use an autosummary
+                                                       $firstrev = $oldrev->getNext();
+                                                       if ( $firstrev->getId() == $undo ) {
+                                                               $undoSummary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
+                                                               if ( $this->summary === '' ) {
+                                                                       $this->summary = $undoSummary;
+                                                               } else {
+                                                                       $this->summary = $undoSummary . wfMsgForContent( 'colon-separator' ) . $this->summary;
+                                                               }
+                                                               $this->undidRev = $undo;
+                                                       }
+                                                       $this->formtype = 'diff';
+                                               }
+                                       } else {
+                                               // Failed basic sanity checks.
+                                               // Older revisions may have been removed since the link
+                                               // was created, or we may simply have got bogus input.
+                                               $undoMsg = 'norev';
+                                       }
+
+                                       $this->editFormPageTop .= $wgOut->parse( "<div class=\"error mw-undo-{$undoMsg}\">" .
+                                               wfMsgNoTrans( 'undo-' . $undoMsg ) . '</div>', true, /* interface */true );
+                               }
+
+                               if ( $text === false ) {
+                                       $text = $this->getOriginalContent();
+                               }
                        }
                }
-               # Give a notice if the user is editing a deleted/moved page...
-               if ( !$this->mTitle->exists() ) {
-                       LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
-                               '', array( 'lim' => 10,
-                                          'conds' => array( "log_action != 'revision'" ),
-                                          'showIfEmpty' => false,
-                                          'msgKey' => array( 'recreate-moveddeleted-warn') )
-                       );
+
+               wfProfileOut( __METHOD__ );
+               return $text;
+       }
+
+       /**
+        * Get the content of the wanted revision, without section extraction.
+        *
+        * The result of this function can be used to compare user's input with
+        * section replaced in its context (using WikiPage::replaceSection())
+        * to the original text of the edit.
+        *
+        * This difers from Article::getContent() that when a missing revision is
+        * encountered the result will be an empty string and not the
+        * 'missing-article' message.
+        *
+        * @since 1.19
+        * @return string
+        */
+       private function getOriginalContent() {
+               if ( $this->section == 'new' ) {
+                       return $this->getCurrentText();
                }
+               $revision = $this->mArticle->getRevisionFetched();
+               if ( $revision === null ) {
+                       return '';
+               }
+
+        $content = $this->mArticle->getContentObject();
+               return $content->getRawData(); # this editor is for editing the raw text. so use the raw text.
        }
 
        /**
-        * Attempt to show a custom editing introduction, if supplied
+        * Get the actual text of the page. This is basically similar to
+        * WikiPage::getRawText() except that when the page doesn't exist an empty
+        * string is returned instead of false.
         *
-        * @return bool
+        * @since 1.19
+        * @return string
         */
-       protected function showCustomIntro() {
-               if ( $this->editintro ) {
-                       $title = Title::newFromText( $this->editintro );
-                       if ( $title instanceof Title && $title->exists() && $title->userCanRead() ) {
-                               global $wgOut;
-                               // Added using template syntax, to take <noinclude>'s into account.
-                               $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
-                               return true;
-                       } else {
-                               return false;
-                       }
+       private function getCurrentText() {
+               $text = $this->mArticle->getRawText();
+               if ( $text === false ) {
+                       return '';
                } else {
-                       return false;
+                       return $text;
                }
        }
 
        /**
-        * Attempt submission (no UI)
-        *
-        * @param $result
-        * @param $bot bool
+        * Use this method before edit() to preload some text into the edit box
         *
-        * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value,
+        * @param $text string
+        */
+       public function setPreloadedText( $text ) {
+               $this->mPreloadText = $text;
+       }
+
+       /**
+        * Get the contents to be preloaded into the box, either set by
+        * an earlier setPreloadText() or by loading the given page.
         *
-        * FIXME: This interface is TERRIBLE, but hard to get rid of due to various error display idiosyncrasies. There are
-        * also lots of cases where error metadata is set in the object and retrieved later instead of being returned, e.g.
-        * AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time.
+        * @param $preload String: representing the title to preload from.
+        * @return String
         */
-       function internalAttemptSave( &$result, $bot = false ) {
-               global $wgFilterCallback, $wgUser, $wgRequest, $wgParser;
-               global $wgMaxArticleSize;
+       protected function getPreloadedText( $preload ) { #FIXME: change to getPreloadedContent()
+               global $wgUser, $wgParser;
 
-               $status = Status::newGood();
+               if ( !empty( $this->mPreloadText ) ) {
+                       return $this->mPreloadText;
+               }
+               
+               if ( $preload === '' ) {
+                       return '';
+               }
 
-               wfProfileIn( __METHOD__  );
-               wfProfileIn( __METHOD__ . '-checks' );
+               $title = Title::newFromText( $preload );
+               # Check for existence to avoid getting MediaWiki:Noarticletext
+               if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
+                       return '';
+               }
 
-               if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
-                       wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
-                       $status->fatal( 'hookaborted' );
-                       $status->value = self::AS_HOOK_ERROR;
-                       wfProfileOut( __METHOD__ . '-checks' );
-                       wfProfileOut( __METHOD__  );
-                       return $status;
+               $page = WikiPage::factory( $title );
+               if ( $page->isRedirect() ) {
+                       $title = $page->getRedirectTarget();
+                       # Same as before
+                       if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
+                               return '';
+                       }
+                       $page = WikiPage::factory( $title );
                }
 
-               # Check image redirect
-               if ( $this->mTitle->getNamespace() == NS_FILE &&
-                       Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
-                       !$wgUser->isAllowed( 'upload' ) ) {
-                               $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
-                               $status->setResult( false, $code );
+               $parserOptions = ParserOptions::newFromUser( $wgUser );
+               return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions ); #FIXME: create Content::getPreloadCopy
+       }
 
-                               wfProfileOut( __METHOD__ . '-checks' );
-                               wfProfileOut( __METHOD__  );
+       /**
+        * Make sure the form isn't faking a user's credentials.
+        *
+        * @param $request WebRequest
+        * @return bool
+        * @private
+        */
+       function tokenOk( &$request ) {
+               global $wgUser;
+               $token = $request->getVal( 'wpEditToken' );
+               $this->mTokenOk = $wgUser->matchEditToken( $token );
+               $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
+               return $this->mTokenOk;
+       }
+
+       /**
+        * Attempt submission
+        * @return bool false if output is done, true if the rest of the form should be displayed
+        */
+       function attemptSave() {
+               global $wgUser, $wgOut;
+
+               $resultDetails = false;
+               # Allow bots to exempt some edits from bot flagging
+               $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
+               $status = $this->internalAttemptSave( $resultDetails, $bot );
+               // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
+
+               if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
+                       $this->didSave = true;
+               }
+
+               switch ( $status->value ) {
+                       case self::AS_HOOK_ERROR_EXPECTED:
+                       case self::AS_CONTENT_TOO_BIG:
+                       case self::AS_ARTICLE_WAS_DELETED:
+                       case self::AS_CONFLICT_DETECTED:
+                       case self::AS_SUMMARY_NEEDED:
+                       case self::AS_TEXTBOX_EMPTY:
+                       case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
+                       case self::AS_END:
+                               return true;
+
+                       case self::AS_HOOK_ERROR:
+                       case self::AS_FILTERING:
+                               return false;
+
+                       case self::AS_SUCCESS_NEW_ARTICLE:
+                               $query = $resultDetails['redirect'] ? 'redirect=no' : '';
+                               $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
+                               $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
+                               return false;
+
+                       case self::AS_SUCCESS_UPDATE:
+                               $extraQuery = '';
+                               $sectionanchor = $resultDetails['sectionanchor'];
+
+                               // Give extensions a chance to modify URL query on update
+                               wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
+
+                               if ( $resultDetails['redirect'] ) {
+                                       if ( $extraQuery == '' ) {
+                                               $extraQuery = 'redirect=no';
+                                       } else {
+                                               $extraQuery = 'redirect=no&' . $extraQuery;
+                                       }
+                               }
+                               $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
+                               return false;
+
+                       case self::AS_BLANK_ARTICLE:
+                               $wgOut->redirect( $this->getContextTitle()->getFullURL() );
+                               return false;
+
+                       case self::AS_SPAM_ERROR:
+                               $this->spamPageWithContent( $resultDetails['spam'] );
+                               return false;
+
+                       case self::AS_BLOCKED_PAGE_FOR_USER:
+                               throw new UserBlockedError( $wgUser->mBlock );
+
+                       case self::AS_IMAGE_REDIRECT_ANON:
+                       case self::AS_IMAGE_REDIRECT_LOGGED:
+                               throw new PermissionsError( 'upload' );
+
+                       case self::AS_READ_ONLY_PAGE_ANON:
+                       case self::AS_READ_ONLY_PAGE_LOGGED:
+                               throw new PermissionsError( 'edit' );
+
+                       case self::AS_READ_ONLY_PAGE:
+                               throw new ReadOnlyError;
+
+                       case self::AS_RATE_LIMITED:
+                               throw new ThrottledError();
+
+                       case self::AS_NO_CREATE_PERMISSION:
+                               $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
+                               throw new PermissionsError( $permission );
+
+               }
+               return false;
+       }
+
+       /**
+        * Attempt submission (no UI)
+        *
+        * @param $result
+        * @param $bot bool
+        *
+        * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value,
+        *
+        * FIXME: This interface is TERRIBLE, but hard to get rid of due to various error display idiosyncrasies. There are
+        * also lots of cases where error metadata is set in the object and retrieved later instead of being returned, e.g.
+        * AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time.
+        */
+       function internalAttemptSave( &$result, $bot = false ) {
+               global $wgFilterCallback, $wgUser, $wgRequest, $wgParser;
+               global $wgMaxArticleSize;
+
+               $status = Status::newGood();
+
+               wfProfileIn( __METHOD__  );
+               wfProfileIn( __METHOD__ . '-checks' );
+
+               if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
+                       wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
+                       $status->fatal( 'hookaborted' );
+                       $status->value = self::AS_HOOK_ERROR;
+                       wfProfileOut( __METHOD__ . '-checks' );
+                       wfProfileOut( __METHOD__  );
+                       return $status;
+               }
+
+               # Check image redirect
+               if ( $this->mTitle->getNamespace() == NS_FILE &&
+                       Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
+                       !$wgUser->isAllowed( 'upload' ) ) {
+                               $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
+                               $status->setResult( false, $code );
+
+                               wfProfileOut( __METHOD__ . '-checks' );
+                               wfProfileOut( __METHOD__  );
 
                                return $status;
                }
@@ -1055,8 +1228,33 @@ class EditPage {
                        }
 
                        $text = $this->textbox1;
-                       if ( $this->section == 'new' && $this->summary != '' ) {
-                               $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
+                       $result['sectionanchor'] = '';
+                       if ( $this->section == 'new' ) {
+                               if ( $this->sectiontitle !== '' ) {
+                                       // Insert the section title above the content.
+                                       $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $text;
+                                       
+                                       // Jump to the new section
+                                       $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+                                       
+                                       // If no edit summary was specified, create one automatically from the section
+                                       // title and have it link to the new section. Otherwise, respect the summary as
+                                       // passed.
+                                       if ( $this->summary === '' ) {
+                                               $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+                                               $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+                                       }
+                               } elseif ( $this->summary !== '' ) {
+                                       // Insert the section title above the content.
+                                       $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
+                                       
+                                       // Jump to the new section
+                                       $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+
+                                       // Create a link to the new section from the edit summary.
+                                       $cleanSummary = $wgParser->stripSectionName( $this->summary );
+                                       $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+                               }
                        }
 
                        $status->value = self::AS_SUCCESS_NEW_ARTICLE;
@@ -1066,10 +1264,11 @@ class EditPage {
                        # Article exists. Check for edit conflict.
 
                        $this->mArticle->clear(); # Force reload of dates, etc.
+                       $timestamp = $this->mArticle->getTimestamp();
 
-                       wfDebug( "timestamp: {$this->mArticle->getTimestamp()}, edittime: {$this->edittime}\n" );
+                       wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
 
-                       if ( $this->mArticle->getTimestamp() != $this->edittime ) {
+                       if ( $timestamp != $this->edittime ) {
                                $this->isConflict = true;
                                if ( $this->section == 'new' ) {
                                        if ( $this->mArticle->getUserText() == $wgUser->getName() &&
@@ -1089,14 +1288,23 @@ class EditPage {
                                        $this->isConflict = false;
                                }
                        }
-
+                       
+                       // If sectiontitle is set, use it, otherwise use the summary as the section title (for
+                       // backwards compatibility with old forms/bots).
+                       if ( $this->sectiontitle !== '' ) {
+                               $sectionTitle = $this->sectiontitle;
+                       } else {
+                               $sectionTitle = $this->summary;
+                       }
+                       
                        if ( $this->isConflict ) {
-                               wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '" .
-                                       $this->mArticle->getTimestamp() . "')\n" );
-                               $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime );
+                               wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
+                               $cnt = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
+                $text = ContentHandler::getContentText($cnt); #FIXME: use Content object throughout, make edit form aware of content model and serialization format
                        } else {
                                wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
-                               $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary );
+                $cnt = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+                $text = ContentHandler::getContentText($cnt); #FIXME: use Content object throughout, make edit form aware of content model and serialization format
                        }
                        if ( is_null( $text ) ) {
                                wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
@@ -1104,7 +1312,7 @@ class EditPage {
                                $text = $this->textbox1; // do not try to merge here!
                        } elseif ( $this->isConflict ) {
                                # Attempt merge
-                               if ( $this->mergeChangesInto( $text ) ) {
+                               if ( $this->mergeChangesInto( $text ) ) { #FIXME: passe/receive Content object
                                        // Successful merge! Maybe we should tell the user the good news?
                                        $this->isConflict = false;
                                        wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
@@ -1138,7 +1346,7 @@ class EditPage {
 
                        # Handle the user preference to force summaries here, but not for null edits
                        if ( $this->section != 'new' && !$this->allowBlankSummary
-                               && 0 != strcmp( $this->mArticle->getContent(), $text )
+                               && $this->getOriginalContent() != $text
                                && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
                        {
                                if ( md5( $this->summary ) == $this->autoSumm ) {
@@ -1173,7 +1381,16 @@ class EditPage {
                                        wfProfileOut( __METHOD__ );
                                        return $status;
                                }
-                               if ( $this->summary != '' ) {
+                               if ( $this->sectiontitle !== '' ) {
+                                       $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+                                       // If no edit summary was specified, create one automatically from the section
+                                       // title and have it link to the new section. Otherwise, respect the summary as
+                                       // passed.
+                                       if ( $this->summary === '' ) {
+                                               $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+                                               $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+                                       }
+                               } elseif ( $this->summary !== '' ) {
                                        $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
                                        # This is a new section, so create a link to the new section
                                        # in the revision summary.
@@ -1266,7 +1483,7 @@ class EditPage {
                $res = $dbw->select( 'revision',
                        'rev_user',
                        array(
-                               'rev_page' => $this->mArticle->getId(),
+                               'rev_page' => $this->mTitle->getArticleId(),
                                'rev_timestamp > '.$dbw->addQuotes( $dbw->timestamp($edittime) )
                        ),
                        __METHOD__,
@@ -1279,6 +1496,63 @@ class EditPage {
                return true;
        }
 
+       /**
+        * @private
+        * @todo document
+        *
+        * @parma $editText string
+        *
+        * @return bool
+        */
+       function mergeChangesInto( &$editText ){
+               wfProfileIn( __METHOD__ );
+
+               $db = wfGetDB( DB_MASTER );
+
+               // This is the revision the editor started from
+               $baseRevision = $this->getBaseRevision();
+               if ( is_null( $baseRevision ) ) {
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+               $baseContent = $baseRevision->getContent();
+
+               // The current state, we want to merge updates into it
+               $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
+               if ( is_null( $currentRevision ) ) {
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+               $currentContent = $currentRevision->getContent();
+
+        $handler = ContentHandler::getForModelName( $baseContent->getModelName() );
+        $editContent = $handler->unserialize( $editText ); #FIXME: supply serialization fomrat from edit form!
+
+               $result = $handler->merge3( $baseContent, $editContent, $currentContent );
+               if ( $result ) {
+                       $editText = ContentHandler::getContentText($result);  #FIXME: supply serialization fomrat from edit form!
+                       wfProfileOut( __METHOD__ );
+                       return true;
+               } else {
+                       wfProfileOut( __METHOD__ );
+                       return false;
+               }
+       }
+
+       /**
+        * @return Revision
+        */
+       function getBaseRevision() {
+               if ( !$this->mBaseRevision ) {
+                       $db = wfGetDB( DB_MASTER );
+                       $baseRevision = Revision::loadFromTimestamp(
+                               $db, $this->mTitle, $this->edittime );
+                       return $this->mBaseRevision = $baseRevision;
+               } else {
+                       return $this->mBaseRevision;
+               }
+       }
+
        /**
         * Check given input text against $wgSpamRegex, and return the text of the first match.
         *
@@ -1321,48 +1595,27 @@ class EditPage {
                return false;
        }
 
-       /**
-        * Initialise form fields in the object
-        * Called on the first invocation, e.g. when a user clicks an edit link
-        * @return bool -- if the requested section is valid
-        */
-       function initialiseForm() {
-               global $wgUser;
-               $this->edittime = $this->mArticle->getTimestamp();
-               $this->textbox1 = $this->getContent( false );
-               // activate checkboxes if user wants them to be always active
-               # Sort out the "watch" checkbox
-               if ( $wgUser->getOption( 'watchdefault' ) ) {
-                       # Watch all edits
-                       $this->watchthis = true;
-               } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
-                       # Watch creations
-                       $this->watchthis = true;
-               } elseif ( $this->mTitle->userIsWatching() ) {
-                       # Already watched
-                       $this->watchthis = true;
-               }
-               if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
-                       $this->minoredit = true;
-               }
-               if ( $this->textbox1 === false ) {
-                       return false;
+       function setHeaders() {
+               global $wgOut, $wgUser;
+
+               $wgOut->addModules( 'mediawiki.action.edit' );
+
+               if ( $wgUser->getOption( 'uselivepreview', false ) ) {
+                       $wgOut->addModules( 'mediawiki.legacy.preview' );
                }
-               wfProxyCheck();
-               return true;
-       }
+               // Bug #19334: textarea jumps when editing articles in IE8
+               $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
 
-       function setHeaders() {
-               global $wgOut;
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               if ( $this->formtype == 'preview' ) {
-                       $wgOut->setPageTitleActionText( wfMsg( 'preview' ) );
-               }
+
+               # Enabled article-related sidebar, toplinks, etc.
+               $wgOut->setArticleRelated( true );
+
                if ( $this->isConflict ) {
-                       $wgOut->setPageTitle( wfMsg( 'editconflict', $this->getContextTitle()->getPrefixedText() ) );
+                       $wgOut->setPageTitle( wfMessage( 'editconflict', $this->getContextTitle()->getPrefixedText() ) );
                } elseif ( $this->section != '' ) {
                        $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
-                       $wgOut->setPageTitle( wfMsg( $msg, $this->getContextTitle()->getPrefixedText() ) );
+                       $wgOut->setPageTitle( wfMessage( $msg, $this->getContextTitle()->getPrefixedText() ) );
                } else {
                        # Use the title defined by DISPLAYTITLE magic word when present
                        if ( isset( $this->mParserOutput )
@@ -1371,50 +1624,120 @@ class EditPage {
                        } else {
                                $title = $this->getContextTitle()->getPrefixedText();
                        }
-                       $wgOut->setPageTitle( wfMsg( 'editing', $title ) );
+                       $wgOut->setPageTitle( wfMessage( 'editing', $title ) );
                }
        }
 
        /**
-        * Send the edit form and related headers to $wgOut
-        * @param $formCallback Callback that takes an OutputPage parameter; will be called
-        *     during form output near the top, for captchas and the like.
+        * Show all applicable editing introductions
         */
-       function showEditForm( $formCallback = null ) {
+       protected function showIntro() {
                global $wgOut, $wgUser;
-
-               wfProfileIn( __METHOD__ );
-
-               #need to parse the preview early so that we know which templates are used,
-               #otherwise users with "show preview after edit box" will get a blank list
-               #we parse this near the beginning so that setHeaders can do the title
-               #setting work instead of leaving it in getPreviewText
-               $previewOutput = '';
-               if ( $this->formtype == 'preview' ) {
-                       $previewOutput = $this->getPreviewText();
+               if ( $this->suppressIntro ) {
+                       return;
                }
 
-               wfRunHooks( 'EditPage::showEditForm:initial', array( &$this ) );
+               $namespace = $this->mTitle->getNamespace();
 
-               $this->setHeaders();
+               if ( $namespace == NS_MEDIAWIKI ) {
+                       # Show a warning if editing an interface message
+                       $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
+               }
 
-               # Enabled article-related sidebar, toplinks, etc.
-               $wgOut->setArticleRelated( true );
+               # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
+               # Show log extract when the user is currently blocked
+               if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
+                       $parts = explode( '/', $this->mTitle->getText(), 2 );
+                       $username = $parts[0];
+                       $user = User::newFromName( $username, false /* allow IP users*/ );
+                       $ip = User::isIP( $username );
+                       if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
+                               $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
+                                       array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
+                       } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
+                               LogEventsList::showLogExtract(
+                                       $wgOut,
+                                       'block',
+                                       $user->getUserPage(),
+                                       '',
+                                       array(
+                                               'lim' => 1,
+                                               'showIfEmpty' => false,
+                                               'msgKey' => array(
+                                                       'blocked-notice-logextract',
+                                                       $user->getName() # Support GENDER in notice
+                                               )
+                                       )
+                               );
+                       }
+               }
+               # Try to add a custom edit intro, or use the standard one if this is not possible.
+               if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
+                       if ( $wgUser->isLoggedIn() ) {
+                               $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1\n</div>", 'newarticletext' );
+                       } else {
+                               $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1\n</div>", 'newarticletextanon' );
+                       }
+               }
+               # Give a notice if the user is editing a deleted/moved page...
+               if ( !$this->mTitle->exists() ) {
+                       LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
+                               '', array( 'lim' => 10,
+                                          'conds' => array( "log_action != 'revision'" ),
+                                          'showIfEmpty' => false,
+                                          'msgKey' => array( 'recreate-moveddeleted-warn') )
+                       );
+               }
+       }
 
-               if ( $this->showHeader() === false ) {
-                       wfProfileOut( __METHOD__ );
-                       return;
+       /**
+        * Attempt to show a custom editing introduction, if supplied
+        *
+        * @return bool
+        */
+       protected function showCustomIntro() {
+               if ( $this->editintro ) {
+                       $title = Title::newFromText( $this->editintro );
+                       if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
+                               global $wgOut;
+                               // Added using template syntax, to take <noinclude>'s into account.
+                               $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
+                               return true;
+                       } else {
+                               return false;
+                       }
+               } else {
+                       return false;
                }
+       }
 
-               $action = htmlspecialchars( $this->getActionURL( $this->getContextTitle() ) );
+       /**
+        * Send the edit form and related headers to $wgOut
+        * @param $formCallback Callback that takes an OutputPage parameter; will be called
+        *     during form output near the top, for captchas and the like.
+        */
+       function showEditForm( $formCallback = null ) {
+               global $wgOut, $wgUser;
 
-               if ( $wgUser->getOption( 'showtoolbar' ) and !$this->isCssJsSubpage ) {
-                       # prepare toolbar for edit buttons
-                       $toolbar = EditPage::getEditToolbar();
-               } else {
-                       $toolbar = '';
+               wfProfileIn( __METHOD__ );
+
+               #need to parse the preview early so that we know which templates are used,
+               #otherwise users with "show preview after edit box" will get a blank list
+               #we parse this near the beginning so that setHeaders can do the title
+               #setting work instead of leaving it in getPreviewText
+               $previewOutput = '';
+               if ( $this->formtype == 'preview' ) {
+                       $previewOutput = $this->getPreviewText();
                }
 
+               wfRunHooks( 'EditPage::showEditForm:initial', array( &$this ) );
+
+               $this->setHeaders();
+
+               if ( $this->showHeader() === false ) {
+                       wfProfileOut( __METHOD__ );
+                       return;
+               }
 
                $wgOut->addHTML( $this->editFormPageTop );
 
@@ -1424,26 +1747,21 @@ class EditPage {
 
                $wgOut->addHTML( $this->editFormTextTop );
 
-               $templates = $this->getTemplates();
-               $formattedtemplates = Linker::formatTemplates( $templates, $this->preview, $this->section != '');
-
-               $hiddencats = $this->mArticle->getHiddenCategories();
-               $formattedhiddencats = Linker::formatHiddenCategories( $hiddencats );
-
-               if ( $this->wasDeletedSinceLastEdit() && 'save' != $this->formtype ) {
-                       $wgOut->wrapWikiMsg(
-                               "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
-                               'deletedwhileediting' );
-               } elseif ( $this->wasDeletedSinceLastEdit() ) {
-                       // Hide the toolbar and edit area, user can click preview to get it back
-                       // Add an confirmation checkbox and explanation.
-                       $toolbar = '';
-                       // @todo move this to a cleaner conditional instead of blanking a variable
+               $showToolbar = true;
+               if ( $this->wasDeletedSinceLastEdit() ) {
+                       if ( $this->formtype == 'save' ) {
+                               // Hide the toolbar and edit area, user can click preview to get it back
+                               // Add an confirmation checkbox and explanation.
+                               $showToolbar = false;
+                       } else {
+                               $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
+                                       'deletedwhileediting' );
+                       }
                }
-               $wgOut->addHTML( <<<HTML
-<form id="editform" name="editform" method="post" action="$action" enctype="multipart/form-data">
-HTML
-);
+
+               $wgOut->addHTML( Html::openElement( 'form', array( 'id' => 'editform', 'name' => 'editform',
+                       'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
+                       'enctype' => 'multipart/form-data' ) ) );
 
                if ( is_callable( $formCallback ) ) {
                        call_user_func_array( $formCallback, array( &$wgOut ) );
@@ -1480,13 +1798,14 @@ HTML
                #####
                # For a bit more sophisticated detection of blank summaries, hash the
                # automatic one and pass that in the hidden field wpAutoSummary.
-               if ( $this->missingSummary ||
-                       ( $this->section == 'new' && $this->nosummary ) )
-                               $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
+               if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
+                       $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
+               }
+
                $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
                $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
 
-               $wgOut->addHTML( Html::hidden( 'oldid', $this->mArticle->getOldID() ) );
+               $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
 
                if ( $this->section == 'new' ) {
                        $this->showSummaryInput( true, $this->summary );
@@ -1495,14 +1814,19 @@ HTML
 
                $wgOut->addHTML( $this->editFormTextBeforeContent );
 
-               $wgOut->addHTML( $toolbar );
+               if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
+                       $wgOut->addHTML( EditPage::getEditToolbar() );
+               }
 
                if ( $this->isConflict ) {
                        // In an edit conflict bypass the overrideable content form method
                        // and fallback to the raw wpTextbox1 since editconflicts can't be
                        // resolved between page source edits and custom ui edits using the
                        // custom edit ui.
-                       $this->showTextbox1( null, $this->getContent() );
+                       $this->textbox2 = $this->textbox1;
+                       $this->textbox1 = $this->getCurrentText();
+
+                       $this->showTextbox1();
                } else {
                        $this->showContentForm();
                }
@@ -1510,32 +1834,31 @@ HTML
                $wgOut->addHTML( $this->editFormTextAfterContent );
 
                $wgOut->addWikiText( $this->getCopywarn() );
-               if ( isset($this->editFormTextAfterWarn) && $this->editFormTextAfterWarn !== '' )
-                       $wgOut->addHTML( $this->editFormTextAfterWarn );
+
+               $wgOut->addHTML( $this->editFormTextAfterWarn );
 
                $this->showStandardInputs();
 
                $this->showFormAfterText();
 
                $this->showTosSummary();
+
                $this->showEditTools();
 
-               $wgOut->addHTML( <<<HTML
-{$this->editFormTextAfterTools}
-<div class='templatesUsed'>
-{$formattedtemplates}
-</div>
-<div class='hiddencats'>
-{$formattedhiddencats}
-</div>
-HTML
-);
+               $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
+
+               $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
+                       Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
+
+               $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
+                       Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
 
-               if ( $this->isConflict )
+               if ( $this->isConflict ) {
                        $this->showConflict();
+               }
+
+               $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
 
-               $wgOut->addHTML( $this->editFormTextBottom );
-               $wgOut->addHTML( "</form>\n" );
                if ( !$wgUser->getOption( 'previewontop' ) ) {
                        $this->displayPreviewArea( $previewOutput, false );
                }
@@ -1543,8 +1866,54 @@ HTML
                wfProfileOut( __METHOD__ );
        }
 
+       /**
+        * Extract the section title from current section text, if any.
+        *
+        * @param string $text
+        * @return Mixed|string or false
+        */
+       public static function extractSectionTitle( $text ) {
+               preg_match( "/^(=+)(.+)\\1(\n|$)/i", $text, $matches );
+               if ( !empty( $matches[2] ) ) {
+                       global $wgParser;
+                       return $wgParser->stripSectionName(trim($matches[2]));
+               } else {
+                       return false;
+               }
+       }
+
        protected function showHeader() {
                global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
+
+               if ( $this->mTitle->isTalkPage() ) {
+                       $wgOut->addWikiMsg( 'talkpagetext' );
+               }
+
+               # Optional notices on a per-namespace and per-page basis
+               $editnotice_ns   = 'editnotice-'.$this->mTitle->getNamespace();
+               $editnotice_ns_message = wfMessage( $editnotice_ns )->inContentLanguage();
+               if ( $editnotice_ns_message->exists() ) {
+                       $wgOut->addWikiText( $editnotice_ns_message->plain() );
+               }
+               if ( MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
+                       $parts = explode( '/', $this->mTitle->getDBkey() );
+                       $editnotice_base = $editnotice_ns;
+                       while ( count( $parts ) > 0 ) {
+                               $editnotice_base .= '-'.array_shift( $parts );
+                               $editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
+                               if ( $editnotice_base_msg->exists() ) {
+                                       $wgOut->addWikiText( $editnotice_base_msg->plain()  );
+                               }
+                       }
+               } else {
+                       # Even if there are no subpages in namespace, we still don't want / in MW ns.
+                       $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
+                       $editnoticeMsg = wfMessage( $editnoticeText )->inContentLanguage();
+                       if ( $editnoticeMsg->exists() ) {
+                               $wgOut->addWikiText( $editnoticeMsg->plain() );
+                       }
+               }
+
                if ( $this->isConflict ) {
                        $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
                        $this->edittime = $this->mArticle->getTimestamp();
@@ -1558,14 +1927,10 @@ HTML
                        }
 
                        if ( $this->section != '' && $this->section != 'new' ) {
-                               $matches = array();
                                if ( !$this->summary && !$this->preview && !$this->diff ) {
-                                       preg_match( "/^(=+)(.+)\\1/mi", $this->textbox1, $matches );
-                                       if ( !empty( $matches[2] ) ) {
-                                               global $wgParser;
-                                               $this->summary = "/* " .
-                                                       $wgParser->stripSectionName(trim($matches[2])) .
-                                                       " */ ";
+                                       $sectionTitle = self::extractSectionTitle( $this->textbox1 );
+                                       if ( $sectionTitle !== false ) {
+                                               $this->summary = "/* $sectionTitle */ ";
                                        }
                                }
                        }
@@ -1590,18 +1955,27 @@ HTML
                                $wgOut->addWikiMsg( 'nonunicodebrowser' );
                        }
 
-                       if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) ) {
-                       // Let sysop know that this will make private content public if saved
+                       if ( $this->section != 'new' ) {
+                               $revision = $this->mArticle->getRevisionFetched();
+                               if ( $revision ) {
+                                       // Let sysop know that this will make private content public if saved
 
-                               if ( !$this->mArticle->mRevision->userCan( Revision::DELETED_TEXT ) ) {
-                                       $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
-                               } elseif ( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
-                                       $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
-                               }
+                                       if ( !$revision->userCan( Revision::DELETED_TEXT ) ) {
+                                               $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
+                                       } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+                                               $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
+                                       }
 
-                               if ( !$this->mArticle->mRevision->isCurrent() ) {
-                                       $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() );
-                                       $wgOut->addWikiMsg( 'editingold' );
+                                       if ( !$revision->isCurrent() ) {
+                                               $this->mArticle->setOldSubtitle( $revision->getId() );
+                                               $wgOut->addWikiMsg( 'editingold' );
+                                       }
+                               } elseif ( $this->mTitle->exists() ) {
+                                       // Something went wrong
+
+                                       $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
+                                               array( 'missing-article', $this->mTitle->getPrefixedText(),
+                                               wfMsgNoTrans( 'missingarticle-rev', $this->oldid ) ) );
                                }
                        }
                }
@@ -1795,7 +2169,7 @@ HTML
                 * include the constant suffix to prevent editing from
                 * broken text-mangling proxies.
                 */
-               $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->editToken() ) . "\n" );
+               $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
        }
 
        /**
@@ -1818,34 +2192,40 @@ HTML
         * @param $customAttribs An array of html attributes to use in the textarea
         * @param $textoverride String: optional text to override $this->textarea1 with
         */
-       protected function showTextbox1($customAttribs = null, $textoverride = null) {
-               $classes = array(); // Textarea CSS
-               if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
-                       # Is the title semi-protected?
-                       if ( $this->mTitle->isSemiProtected() ) {
-                               $classes[] = 'mw-textarea-sprotected';
-                       } else {
-                               # Then it must be protected based on static groups (regular)
-                               $classes[] = 'mw-textarea-protected';
+       protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
+               if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
+                       $attribs = array( 'style' => 'display:none;' );
+               } else {
+                       $classes = array(); // Textarea CSS
+                       if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+                               # Is the title semi-protected?
+                               if ( $this->mTitle->isSemiProtected() ) {
+                                       $classes[] = 'mw-textarea-sprotected';
+                               } else {
+                                       # Then it must be protected based on static groups (regular)
+                                       $classes[] = 'mw-textarea-protected';
+                               }
+                               # Is the title cascade-protected?
+                               if ( $this->mTitle->isCascadeProtected() ) {
+                                       $classes[] = 'mw-textarea-cprotected';
+                               }
                        }
-                       # Is the title cascade-protected?
-                       if ( $this->mTitle->isCascadeProtected() ) {
-                               $classes[] = 'mw-textarea-cprotected';
+
+                       $attribs = array( 'tabindex' => 1 );
+
+                       if ( is_array( $customAttribs ) ) {
+                               $attribs += $customAttribs;
                        }
-               }
-               $attribs = array( 'tabindex' => 1 );
-               if ( is_array($customAttribs) )
-                       $attribs += $customAttribs;
 
-               if ( $this->wasDeletedSinceLastEdit() )
-                       $attribs['type'] = 'hidden';
-               if ( !empty( $classes ) ) {
-                       if ( isset($attribs['class']) )
-                               $classes[] = $attribs['class'];
-                       $attribs['class'] = implode( ' ', $classes );
+                       if ( count( $classes ) ) {
+                               if ( isset( $attribs['class'] ) ) {
+                                       $classes[] = $attribs['class'];
+                               }
+                               $attribs['class'] = implode( ' ', $classes );
+                       }
                }
 
-               $this->showTextbox( isset($textoverride) ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
+               $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
        }
 
        protected function showTextbox2() {
@@ -1923,6 +2303,40 @@ HTML
                }
        }
 
+       /**
+        * Get a diff between the current contents of the edit box and the
+        * version of the page we're editing from.
+        *
+        * If this is a section edit, we'll replace the section as for final
+        * save and then make a comparison.
+        */
+       function showDiff() {
+               global $wgUser, $wgContLang, $wgParser, $wgOut;
+
+               $oldtext = $this->getOriginalContent();
+               $newtext = $this->mArticle->replaceSection(
+                       $this->section, $this->textbox1, $this->summary, $this->edittime );
+
+               wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+
+               $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
+               $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
+
+               if ( $oldtext !== false  || $newtext != '' ) {
+                       $oldtitle = wfMsgExt( 'currentrev', array( 'parseinline' ) );
+                       $newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) );
+
+                       $de = new DifferenceEngine( $this->mArticle->getContext() );
+                       $de->setText( $oldtext, $newtext );
+                       $difftext = $de->getDiff( $oldtitle, $newtitle );
+                       $de->showDiffStyle();
+               } else {
+                       $difftext = '';
+               }
+
+               $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
+       }
+
        /**
         * Give a chance for site and per-namespace customizations of
         * terms of service summary link that might exist separately
@@ -1999,12 +2413,11 @@ HTML
         */
        protected function showConflict() {
                global $wgOut;
-               $this->textbox2 = $this->textbox1;
-               $this->textbox1 = $this->getContent();
+
                if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
 
-                       $de = new DifferenceEngine( $this->mTitle );
+                       $de = new DifferenceEngine( $this->mArticle->getContext() );
                        $de->setText( $this->textbox2, $this->textbox1 );
                        $de->showDiff( wfMsgExt( 'yourtext', 'parseinline' ), wfMsg( 'storedversion' ) );
 
@@ -2013,6 +2426,62 @@ HTML
                }
        }
 
+       /**
+        * @return string
+        */
+       public function getCancelLink() {
+               $cancelParams = array();
+               if ( !$this->isConflict && $this->oldid > 0 ) {
+                       $cancelParams['oldid'] = $this->oldid;
+               }
+
+               return Linker::linkKnown(
+                       $this->getContextTitle(),
+                       wfMsgExt( 'cancel', array( 'parseinline' ) ),
+                       array( 'id' => 'mw-editform-cancel' ),
+                       $cancelParams
+               );
+       }
+
+       /**
+        * Returns the URL to use in the form's action attribute.
+        * This is used by EditPage subclasses when simply customizing the action
+        * variable in the constructor is not enough. This can be used when the
+        * EditPage lives inside of a Special page rather than a custom page action.
+        *
+        * @param $title Title object for which is being edited (where we go to for &action= links)
+        * @return string
+        */
+       protected function getActionURL( Title $title ) {
+               return $title->getLocalURL( array( 'action' => $this->action ) );
+       }
+
+       /**
+        * Check if a page was deleted while the user was editing it, before submit.
+        * Note that we rely on the logging table, which hasn't been always there,
+        * but that doesn't matter, because this only applies to brand new
+        * deletes.
+        */
+       protected function wasDeletedSinceLastEdit() {
+               if ( $this->deletedSinceEdit !== null ) {
+                       return $this->deletedSinceEdit;
+               }
+
+               $this->deletedSinceEdit = false;
+
+               if ( $this->mTitle->isDeletedQuick() ) {
+                       $this->lastDelete = $this->getLastDelete();
+                       if ( $this->lastDelete ) {
+                               $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
+                               if ( $deleteTime > $this->starttime ) {
+                                       $this->deletedSinceEdit = true;
+                               }
+                       }
+               }
+
+               return $this->deletedSinceEdit;
+       }
+
        protected function getLastDelete() {
                $dbr = wfGetDB( DB_SLAVE );
                $data = $dbr->selectRow(
@@ -2050,10 +2519,25 @@ HTML
         * @return string
         */
        function getPreviewText() {
-               global $wgOut, $wgUser, $wgParser;
+               global $wgOut, $wgUser, $wgParser, $wgRawHtml;
 
                wfProfileIn( __METHOD__ );
 
+               if ( $wgRawHtml && !$this->mTokenOk ) {
+                       // Could be an offsite preview attempt. This is very unsafe if
+                       // HTML is enabled, as it could be an attack.
+                       $parsedNote = '';
+                       if ( $this->textbox1 !== '' ) {
+                               // Do not put big scary notice, if previewing the empty
+                               // string, which happens when you initially edit
+                               // a category page, due to automatic preview-on-open.
+                               $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
+                                       wfMsg( 'session_fail_preview_html' ) . "</div>", true, /* interface */true );
+                       }
+                       wfProfileOut( __METHOD__ );
+                       return $parsedNote;
+               }
+
                if ( $this->mTriedSave && !$this->mTokenOk ) {
                        if ( $this->mTokenOkExceptSuffix ) {
                                $note = wfMsg( 'token_suffix_mismatch' );
@@ -2068,28 +2552,14 @@ HTML
 
                $parserOptions = ParserOptions::newFromUser( $wgUser );
                $parserOptions->setEditSection( false );
+               $parserOptions->setTidy( true );
                $parserOptions->setIsPreview( true );
                $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
 
-               global $wgRawHtml;
-               if ( $wgRawHtml && !$this->mTokenOk ) {
-                       // Could be an offsite preview attempt. This is very unsafe if
-                       // HTML is enabled, as it could be an attack.
-                       $parsedNote = '';
-                       if ( $this->textbox1 !== '' ) {
-                               // Do not put big scary notice, if previewing the empty
-                               // string, which happens when you initially edit
-                               // a category page, due to automatic preview-on-open.
-                               $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
-                                       wfMsg( 'session_fail_preview_html' ) . "</div>", true, /* interface */true );
-                       }
-                       wfProfileOut( __METHOD__ );
-                       return $parsedNote;
-               }
-
                # don't parse non-wikitext pages, show message about preview
                # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
 
+        #FIXME: get appropriate content handler!
                if ( $this->isCssJsSubpage || !$this->mTitle->isWikitextPage() ) {
                        if( $this->mTitle->isCssJsSubpage() ) {
                                $level = 'user';
@@ -2102,6 +2572,7 @@ HTML
                        # Used messages to make sure grep find them:
                        # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
                        if( $level ) {
+                           #FIXME: move this crud into ContentHandler class!
                                if (preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
                                        $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
                                        $class = "mw-code mw-css";
@@ -2113,7 +2584,6 @@ HTML
                                }
                        }
 
-                       $parserOptions->setTidy( true );
                        $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
                        $previewHTML = $parserOutput->mText;
                        $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
@@ -2127,15 +2597,15 @@ HTML
                                # If we're adding a comment, we need to show the
                                # summary as the headline
                                if ( $this->section == "new" && $this->summary != "" ) {
-                                       $toparse = "== {$this->summary} ==\n\n" . $toparse;
+                                       $toparse = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $toparse;
                                }
 
                                wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
 
-                               $parserOptions->setTidy( true );
                                $parserOptions->enableLimitReport();
-                               $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $toparse ),
-                                       $this->mTitle, $parserOptions );
+
+                               $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
+                               $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
 
                                $previewHTML = $parserOutput->getText();
                                $this->mParserOutput = $parserOutput;
@@ -2160,222 +2630,30 @@ HTML
                $pageLang = $this->mTitle->getPageLanguage();
                $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
                        'class' => 'mw-content-'.$pageLang->getDir() );
-               $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
-
-               wfProfileOut( __METHOD__ );
-               return $previewhead . $previewHTML . $this->previewTextAfterContent;
-       }
-
-       /**
-        * @return Array
-        */
-       function getTemplates() {
-               if ( $this->preview || $this->section != '' ) {
-                       $templates = array();
-                       if ( !isset( $this->mParserOutput ) ) {
-                               return $templates;
-                       }
-                       foreach( $this->mParserOutput->getTemplates() as $ns => $template) {
-                               foreach( array_keys( $template ) as $dbk ) {
-                                       $templates[] = Title::makeTitle($ns, $dbk);
-                               }
-                       }
-                       return $templates;
-               } else {
-                       return $this->mArticle->getUsedTemplates();
-               }
-       }
-
-       /**
-        * Call the stock "user is blocked" page
-        */
-       function blockedPage() {
-               global $wgOut;
-               $wgOut->blockedPage( false ); # Standard block notice on the top, don't 'return'
-
-               # If the user made changes, preserve them when showing the markup
-               # (This happens when a user is blocked during edit, for instance)
-               $first = $this->firsttime || ( !$this->save && $this->textbox1 == '' );
-               if ( $first ) {
-                       $source = $this->mTitle->exists() ? $this->getContent() : false;
-               } else {
-                       $source = $this->textbox1;
-               }
-
-               # Spit out the source or the user's modified version
-               if ( $source !== false ) {
-                       $wgOut->addHTML( '<hr />' );
-                       $wgOut->addWikiMsg( $first ? 'blockedoriginalsource' : 'blockededitsource', $this->mTitle->getPrefixedText() );
-                       $this->showTextbox1( array( 'readonly' ), $source );
-               }
-       }
-
-       /**
-        * Produce the stock "please login to edit pages" page
-        */
-       function userNotLoggedInPage() {
-               global $wgOut;
-
-               $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
-               $loginLink = Linker::linkKnown(
-                       $loginTitle,
-                       wfMsgHtml( 'loginreqlink' ),
-                       array(),
-                       array( 'returnto' => $this->getContextTitle()->getPrefixedText() )
-               );
-
-               $wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) );
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               $wgOut->setArticleRelated( false );
-
-               $wgOut->addHTML( wfMessage( 'whitelistedittext' )->rawParams( $loginLink )->parse() );
-               $wgOut->returnToMain( false, $this->getContextTitle() );
-       }
-
-       /**
-        * Creates a basic error page which informs the user that
-        * they have attempted to edit a nonexistent section.
-        */
-       function noSuchSectionPage() {
-               global $wgOut;
-
-               $wgOut->setPageTitle( wfMsg( 'nosuchsectiontitle' ) );
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               $wgOut->setArticleRelated( false );
-
-               $res = wfMsgExt( 'nosuchsectiontext', 'parse', $this->section );
-               wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
-               $wgOut->addHTML( $res );
-
-               $wgOut->returnToMain( false, $this->mTitle );
-       }
-
-       /**
-        * Produce the stock "your edit contains spam" page
-        *
-        * @param $match Text which triggered one or more filters
-        * @deprecated since 1.17 Use method spamPageWithContent() instead
-        */
-       static function spamPage( $match = false ) {
-               global $wgOut, $wgTitle;
-
-               $wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) );
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               $wgOut->setArticleRelated( false );
-
-               $wgOut->addHTML( '<div id="spamprotected">' );
-               $wgOut->addWikiMsg( 'spamprotectiontext' );
-               if ( $match ) {
-                       $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
-               }
-               $wgOut->addHTML( '</div>' );
-
-               $wgOut->returnToMain( false, $wgTitle );
-       }
-
-       /**
-        * Show "your edit contains spam" page with your diff and text
-        *
-        * @param $match Text which triggered one or more filters
-        */
-       public function spamPageWithContent( $match = false ) {
-               global $wgOut;
-               $this->textbox2 = $this->textbox1;
-
-               $wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) );
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               $wgOut->setArticleRelated( false );
-
-               $wgOut->addHTML( '<div id="spamprotected">' );
-               $wgOut->addWikiMsg( 'spamprotectiontext' );
-               if ( $match ) {
-                       $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
-               }
-               $wgOut->addHTML( '</div>' );
-
-               $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
-               $de = new DifferenceEngine( $this->mTitle );
-               $de->setText( $this->getContent(), $this->textbox2 );
-               $de->showDiff( wfMsg( "storedversion" ), wfMsgExt( 'yourtext', 'parseinline' ) );
-
-               $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
-               $this->showTextbox2();
-
-               $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
-       }
-
-
-       /**
-        * @private
-        * @todo document
-        *
-        * @parma $editText string
-        *
-        * @return bool
-        */
-       function mergeChangesInto( &$editText ){
-               wfProfileIn( __METHOD__ );
-
-               $db = wfGetDB( DB_MASTER );
-
-               // This is the revision the editor started from
-               $baseRevision = $this->getBaseRevision();
-               if ( is_null( $baseRevision ) ) {
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-               $baseText = $baseRevision->getText();
-
-               // The current state, we want to merge updates into it
-               $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
-               if ( is_null( $currentRevision ) ) {
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-               $currentText = $currentRevision->getText();
-
-               $result = '';
-               if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
-                       $editText = $result;
-                       wfProfileOut( __METHOD__ );
-                       return true;
-               } else {
-                       wfProfileOut( __METHOD__ );
-                       return false;
-               }
-       }
-
-       /**
-        * Check if the browser is on a blacklist of user-agents known to
-        * mangle UTF-8 data on form submission. Returns true if Unicode
-        * should make it through, false if it's known to be a problem.
-        * @return bool
-        * @private
-        */
-       function checkUnicodeCompliantBrowser() {
-               global $wgBrowserBlackList;
-               if ( empty( $_SERVER["HTTP_USER_AGENT"] ) ) {
-                       // No User-Agent header sent? Trust it by default...
-                       return true;
-               }
-               $currentbrowser = $_SERVER["HTTP_USER_AGENT"];
-               foreach ( $wgBrowserBlackList as $browser ) {
-                       if ( preg_match($browser, $currentbrowser) ) {
-                               return false;
-                       }
-               }
-               return true;
+               $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
+
+               wfProfileOut( __METHOD__ );
+               return $previewhead . $previewHTML . $this->previewTextAfterContent;
        }
 
        /**
-        * Format an anchor fragment as it would appear for a given section name
-        * @param $text String
-        * @return String
-        * @private
+        * @return Array
         */
-       function sectionAnchor( $text ) {
-               global $wgParser;
-               return $wgParser->guessSectionNameFromWikiText( $text );
+       function getTemplates() {
+               if ( $this->preview || $this->section != '' ) {
+                       $templates = array();
+                       if ( !isset( $this->mParserOutput ) ) {
+                               return $templates;
+                       }
+                       foreach( $this->mParserOutput->getTemplates() as $ns => $template) {
+                               foreach( array_keys( $template ) as $dbk ) {
+                                       $templates[] = Title::makeTitle($ns, $dbk);
+                               }
+                       }
+                       return $templates;
+               } else {
+                       return $this->mTitle->getTemplateLinksFrom();
+               }
        }
 
        /**
@@ -2506,7 +2784,7 @@ HTML
                        )
                );
 
-               $script = '';
+               $script = 'mw.loader.using("mediawiki.action.edit", function() {';
                foreach ( $toolarray as $tool ) {
                        if ( !$tool ) {
                                continue;
@@ -2527,6 +2805,14 @@ HTML
 
                        $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
                }
+               
+               // This used to be called on DOMReady from mediawiki.action.edit, which
+               // ended up causing race conditions with the setup code above.
+               $script .= "\n" .
+                       "// Create button bar\n" .
+                       "$(function() { mw.toolbar.init(); } );\n";
+
+               $script .= '});';
                $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
 
                $toolbar = '<div id="toolbar"></div>';
@@ -2668,50 +2954,138 @@ HTML
        }
 
        /**
-        * @return string
+        * Call the stock "user is blocked" page
+        *
+        * @deprecated in 1.19; throw an exception directly instead
         */
-       public function getCancelLink() {
-               $cancelParams = array();
-               if ( !$this->isConflict && $this->mArticle->getOldID() > 0 ) {
-                       $cancelParams['oldid'] = $this->mArticle->getOldID();
-               }
+       function blockedPage() {
+               wfDeprecated( __METHOD__, '1.19' );
+               global $wgUser;
 
-               return Linker::linkKnown(
-                       $this->getContextTitle(),
-                       wfMsgExt( 'cancel', array( 'parseinline' ) ),
-                       array( 'id' => 'mw-editform-cancel' ),
-                       $cancelParams
-               );
+               throw new UserBlockedError( $wgUser->mBlock );
        }
 
        /**
-        * Get a diff between the current contents of the edit box and the
-        * version of the page we're editing from.
+        * Produce the stock "please login to edit pages" page
         *
-        * If this is a section edit, we'll replace the section as for final
-        * save and then make a comparison.
+        * @deprecated in 1.19; throw an exception directly instead
         */
-       function showDiff() {
-               $oldtext = $this->mArticle->fetchContent();
-               $newtext = $this->mArticle->replaceSection(
-                       $this->section, $this->textbox1, $this->summary, $this->edittime );
+       function userNotLoggedInPage() {
+               wfDeprecated( __METHOD__, '1.19' );
+               throw new PermissionsError( 'edit' );
+       }
 
-               wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+       /**
+        * Show an error page saying to the user that he has insufficient permissions
+        * to create a new page
+        *
+        * @deprecated in 1.19; throw an exception directly instead
+        */
+       function noCreatePermission() {
+               wfDeprecated( __METHOD__, '1.19' );
+               $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
+               throw new PermissionsError( $permission );
+       }
 
-               $newtext = $this->mArticle->preSaveTransform( $newtext );
-               $oldtitle = wfMsgExt( 'currentrev', array( 'parseinline' ) );
-               $newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) );
-               if ( $oldtext !== false  || $newtext != '' ) {
-                       $de = new DifferenceEngine( $this->mTitle );
-                       $de->setText( $oldtext, $newtext );
-                       $difftext = $de->getDiff( $oldtitle, $newtitle );
-                       $de->showDiffStyle();
-               } else {
-                       $difftext = '';
+       /**
+        * Creates a basic error page which informs the user that
+        * they have attempted to edit a nonexistent section.
+        */
+       function noSuchSectionPage() {
+               global $wgOut;
+
+               $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
+
+               $res = wfMsgExt( 'nosuchsectiontext', 'parse', $this->section );
+               wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
+               $wgOut->addHTML( $res );
+
+               $wgOut->returnToMain( false, $this->mTitle );
+       }
+
+       /**
+        * Produce the stock "your edit contains spam" page
+        *
+        * @param $match Text which triggered one or more filters
+        * @deprecated since 1.17 Use method spamPageWithContent() instead
+        */
+       static function spamPage( $match = false ) {
+               wfDeprecated( __METHOD__, '1.17' );
+               
+               global $wgOut, $wgTitle;
+
+               $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
+
+               $wgOut->addHTML( '<div id="spamprotected">' );
+               $wgOut->addWikiMsg( 'spamprotectiontext' );
+               if ( $match ) {
+                       $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
                }
+               $wgOut->addHTML( '</div>' );
+
+               $wgOut->returnToMain( false, $wgTitle );
+       }
 
+       /**
+        * Show "your edit contains spam" page with your diff and text
+        *
+        * @param $match Text which triggered one or more filters
+        */
+       public function spamPageWithContent( $match = false ) {
                global $wgOut;
-               $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
+               $this->textbox2 = $this->textbox1;
+
+               $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
+
+               $wgOut->addHTML( '<div id="spamprotected">' );
+               $wgOut->addWikiMsg( 'spamprotectiontext' );
+               if ( $match ) {
+                       $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
+               }
+               $wgOut->addHTML( '</div>' );
+
+               $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
+               $de = new DifferenceEngine( $this->mArticle->getContext() );
+               $de->setText( $this->getCurrentText(), $this->textbox2 );
+               $de->showDiff( wfMsg( "storedversion" ), wfMsgExt( 'yourtext', 'parseinline' ) );
+
+               $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
+               $this->showTextbox2();
+
+               $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
+       }
+
+       /**
+        * Format an anchor fragment as it would appear for a given section name
+        * @param $text String
+        * @return String
+        * @private
+        */
+       function sectionAnchor( $text ) {
+               global $wgParser;
+               return $wgParser->guessSectionNameFromWikiText( $text );
+       }
+
+       /**
+        * Check if the browser is on a blacklist of user-agents known to
+        * mangle UTF-8 data on form submission. Returns true if Unicode
+        * should make it through, false if it's known to be a problem.
+        * @return bool
+        * @private
+        */
+       function checkUnicodeCompliantBrowser() {
+               global $wgBrowserBlackList;
+               if ( empty( $_SERVER["HTTP_USER_AGENT"] ) ) {
+                       // No User-Agent header sent? Trust it by default...
+                       return true;
+               }
+               $currentbrowser = $_SERVER["HTTP_USER_AGENT"];
+               foreach ( $wgBrowserBlackList as $browser ) {
+                       if ( preg_match($browser, $currentbrowser) ) {
+                               return false;
+                       }
+               }
+               return true;
        }
 
        /**
@@ -2840,117 +3214,4 @@ HTML
                // reverse the transform that we made for reversability reasons.
                return strtr( $result, array( "&#x0" => "&#x" ) );
        }
-
-       function noCreatePermission() {
-               global $wgOut;
-               $wgOut->setPageTitle( wfMsg( 'nocreatetitle' ) );
-               $wgOut->addWikiMsg( 'nocreatetext' );
-       }
-
-       /**
-        * Attempt submission
-        * @return bool false if output is done, true if the rest of the form should be displayed
-        */
-       function attemptSave() {
-               global $wgUser, $wgOut;
-
-               $resultDetails = false;
-               # Allow bots to exempt some edits from bot flagging
-               $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
-               $status = $this->internalAttemptSave( $resultDetails, $bot );
-               // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
-
-               if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
-                       $this->didSave = true;
-               }
-
-               switch ( $status->value ) {
-                       case self::AS_HOOK_ERROR_EXPECTED:
-                       case self::AS_CONTENT_TOO_BIG:
-                       case self::AS_ARTICLE_WAS_DELETED:
-                       case self::AS_CONFLICT_DETECTED:
-                       case self::AS_SUMMARY_NEEDED:
-                       case self::AS_TEXTBOX_EMPTY:
-                       case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
-                       case self::AS_END:
-                               return true;
-
-                       case self::AS_HOOK_ERROR:
-                       case self::AS_FILTERING:
-                               return false;
-
-                       case self::AS_SUCCESS_NEW_ARTICLE:
-                               $query = $resultDetails['redirect'] ? 'redirect=no' : '';
-                               $wgOut->redirect( $this->mTitle->getFullURL( $query ) );
-                               return false;
-
-                       case self::AS_SUCCESS_UPDATE:
-                               $extraQuery = '';
-                               $sectionanchor = $resultDetails['sectionanchor'];
-
-                               // Give extensions a chance to modify URL query on update
-                               wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
-
-                               if ( $resultDetails['redirect'] ) {
-                                       if ( $extraQuery == '' ) {
-                                               $extraQuery = 'redirect=no';
-                                       } else {
-                                               $extraQuery = 'redirect=no&' . $extraQuery;
-                                       }
-                               }
-                               $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
-                               return false;
-
-                       case self::AS_SPAM_ERROR:
-                               $this->spamPageWithContent( $resultDetails['spam'] );
-                               return false;
-
-                       case self::AS_BLOCKED_PAGE_FOR_USER:
-                               $this->blockedPage();
-                               return false;
-
-                       case self::AS_IMAGE_REDIRECT_ANON:
-                               $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
-                               return false;
-
-                       case self::AS_READ_ONLY_PAGE_ANON:
-                               $this->userNotLoggedInPage();
-                               return false;
-
-                       case self::AS_READ_ONLY_PAGE_LOGGED:
-                       case self::AS_READ_ONLY_PAGE:
-                               $wgOut->readOnlyPage();
-                               return false;
-
-                       case self::AS_RATE_LIMITED:
-                               $wgOut->rateLimited();
-                               return false;
-
-                       case self::AS_NO_CREATE_PERMISSION:
-                               $this->noCreatePermission();
-                               return false;
-
-                       case self::AS_BLANK_ARTICLE:
-                               $wgOut->redirect( $this->getContextTitle()->getFullURL() );
-                               return false;
-
-                       case self::AS_IMAGE_REDIRECT_LOGGED:
-                               $wgOut->permissionRequired( 'upload' );
-                               return false;
-               }
-       }
-
-       /**
-        * @return Revision
-        */
-       function getBaseRevision() {
-               if ( !$this->mBaseRevision ) {
-                       $db = wfGetDB( DB_MASTER );
-                       $baseRevision = Revision::loadFromTimestamp(
-                               $db, $this->mTitle, $this->edittime );
-                       return $this->mBaseRevision = $baseRevision;
-               } else {
-                       return $this->mBaseRevision;
-               }
-       }
 }