merged master some more
authordaniel <daniel.kinzler@wikimedia.de>
Wed, 29 Aug 2012 13:36:13 +0000 (15:36 +0200)
committerdaniel <daniel.kinzler@wikimedia.de>
Wed, 29 Aug 2012 13:37:17 +0000 (15:37 +0200)
Change-Id: I53f349e42336ce0426ea1aff939853b3cd728aeb

1  2 
RELEASE-NOTES-1.20
includes/AutoLoader.php
includes/EditPage.php
includes/GlobalFunctions.php
includes/Title.php
includes/diff/DifferenceEngine.php
includes/job/RefreshLinksJob.php
includes/parser/Parser.php
includes/specials/SpecialUndelete.php
languages/messages/MessagesEn.php
tests/phpunit/includes/filerepo/FileBackendTest.php

diff --combined RELEASE-NOTES-1.20
@@@ -65,7 -65,7 +65,7 @@@ upgrade PHP if you have not done so pri
  * (bug 35685) api.php URL and other entry point URLs are now listed on
    Special:Version
  * Edit notices can now be translated.
- * jQuery upgraded to 1.8.
+ * (bug 35680) jQuery upgraded to 1.7.2.
  * jQuery UI upgraded to 1.8.22.
  * (bug 35705) QUnit upgraded from v1.2.0 to v1.8.0.
  * (bug 37604) jquery.cookie upgraded to 2011 version.
@@@ -87,8 -87,6 +87,8 @@@
  * Added new function getDomain to AuthPlugin for getting a user's domain
  * (bug 23427) New magic word {{PAGEID}} which gives the current page ID.
    Will be null on previewing a page being created.
 +* Added new hook AfterFinalPageOutput to allow modifications to buffered page output before sent
 +  to the client.
  * (bug 37627) UserNotLoggedIn() exception to show a generic error page whenever
    a user is not logged in.
  * Watched status in changes lists are no longer indicated by <strong></strong>
  * (bug 39376) jquery.form upgraded to 3.14
  * SVG files will now show the actual width in the SVG's specified units
    in the metadata box.
+ * Added ResourceLoader module "jquery.jStorage".
  
  === Bug fixes in 1.20 ===
  * (bug 30245) Use the correct way to construct a log page title.
diff --combined includes/AutoLoader.php
@@@ -252,7 -252,6 +252,7 @@@ $wgAutoloadLocalClasses = array
        'UnlistedSpecialPage' => 'includes/SpecialPage.php',
        'UploadSourceAdapter' => 'includes/Import.php',
        'UppercaseCollation' => 'includes/Collation.php',
 +      'Uri' => 'includes/Uri.php',
        'User' => 'includes/User.php',
        'UserArray' => 'includes/UserArray.php',
        'UserArrayFromResult' => 'includes/UserArray.php',
        'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
        'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php',
  
 +      # content handler
 +      'Content' => 'includes/Content.php',
 +      'AbstractContent' => 'includes/Content.php',
 +      'ContentHandler' => 'includes/ContentHandler.php',
 +      'CssContent' => 'includes/Content.php',
 +      'TextContentHandler' => 'includes/ContentHandler.php',
 +      'CssContentHandler' => 'includes/ContentHandler.php',
 +      'JavaScriptContent' => 'includes/Content.php',
 +      'JavaScriptContentHandler' => 'includes/ContentHandler.php',
 +      'MessageContent' => 'includes/Content.php',
 +      'TextContent' => 'includes/Content.php',
 +      'WikitextContent' => 'includes/Content.php',
 +      'WikitextContentHandler' => 'includes/ContentHandler.php',
 +
        # includes/actions
        'CachedAction' => 'includes/actions/CachedAction.php',
        'CreditsAction' => 'includes/actions/CreditsAction.php',
        'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
        'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
        'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
 +      'ApiFormatNone' => 'includes/api/ApiFormatNone.php',
        'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
        'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php',
        'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
        'TestFileIterator' => 'tests/testHelpers.inc',
        'TestRecorder' => 'tests/testHelpers.inc',
  
-       
 +      # tests/phpunit
 +      'RevisionStorageTest' => 'tests/phpunit/includes/RevisionStorageTest.php',
 +      'WikiPageTest' => 'tests/phpunit/includes/WikiPageTest.php',
 +      'WikitextContentTest' => 'tests/phpunit/includes/WikitextContentTest.php',
 +      'JavascriptContentTest' => 'tests/phpunit/includes/JavascriptContentTest.php',
 +      'DummyContentHandlerForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
 +      'DummyContentForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
        # tests/phpunit/includes
        'GenericArrayObjectTest' => 'tests/phpunit/includes/libs/GenericArrayObjectTest.php',
  
diff --combined includes/EditPage.php
@@@ -155,11 -155,6 +155,11 @@@ class EditPage 
         */
        const AS_IMAGE_REDIRECT_LOGGED     = 234;
  
 +      /**
 +       * Status: can't parse content
 +       */
 +      const AS_PARSE_ERROR                = 240;
 +
        /**
         * HTML id and name for the beginning of the edit form.
         */
        var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
        var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
        var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
 +      var $content_model = null, $content_format = null;
  
        # Placeholders for text injection by hooks (must be HTML)
        # extensions should take care to _append_ to the present value
        public $editFormTextBottom = '';
        public $editFormTextAfterContent = '';
        public $previewTextAfterContent = '';
 -      public $mPreloadText = '';
 +      public $mPreloadContent = null;
  
        /* $didSave should be set to true whenever an article was succesfully altered. */
        public $didSave = false;
        public function __construct( Article $article ) {
                $this->mArticle = $article;
                $this->mTitle = $article->getTitle();
 +
 +              $this->content_model = $this->mTitle->getContentModel();
 +
 +              $handler = ContentHandler::getForModelID( $this->content_model );
 +              $this->content_format = $handler->getDefaultFormat(); #NOTE: should be overridden by format of actual revision
        }
  
        /**
                        return;
                }
  
 -              $content = $this->getContent();
 +              $content = $this->getContentObject();
  
                # Use the normal message if there's nothing to display
 -              if ( $this->firsttime && $content === '' ) {
 +              if ( $this->firsttime && $content->isEmpty() ) {
                        $action = $this->mTitle->exists() ? 'edit' :
                                ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
                        throw new PermissionsError( $action, $permErrors );
                # 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;
 +                      $text = $this->textbox1;
                        $wgOut->addWikiMsg( 'viewyourtext' );
                } else {
 +                      $text = $content->serialize( $this->content_format );
                        $wgOut->addWikiMsg( 'viewsourcetext' );
                }
  
 -              $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
 +              $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
  
                $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
                        Linker::formatTemplates( $this->getTemplates() ) ) );
                        }
                }
  
 +              $this->oldid = $request->getInt( 'oldid' );
 +
                $this->bot = $request->getBool( 'bot', true );
                $this->nosummary = $request->getBool( 'nosummary' );
  
 -              $this->oldid = $request->getInt( 'oldid' );
 +              $content_handler = ContentHandler::getForTitle( $this->mTitle );
 +              $this->content_model = $request->getText( 'model', $content_handler->getModelID() ); #may be overridden by revision
 +              $this->content_format = $request->getText( 'format', $content_handler->getDefaultFormat() ); #may be overridden by revision
 +
 +              #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
 +              #TODO: check if the desired content model supports the given content format!
  
                $this->live = $request->getCheck( 'live' );
                $this->editintro = $request->getText( 'editintro',
        function initialiseForm() {
                global $wgUser;
                $this->edittime = $this->mArticle->getTimestamp();
 -              $this->textbox1 = $this->getContent( false );
 +
 +              $content = $this->getContentObject( false ); #TODO: track content object?!
 +              $this->textbox1 = $content->serialize( $this->content_format );
 +
                // activate checkboxes if user wants them to be always active
                # Sort out the "watch" checkbox
                if ( $wgUser->getOption( 'watchdefault' ) ) {
         * @param $def_text string
         * @return mixed string on success, $def_text for invalid sections
         * @private
 +       * @deprecated since 1.WD
 +       * @todo: deprecated, replace usage everywhere
         */
 -      function getContent( $def_text = '' ) {
 -              global $wgOut, $wgRequest, $wgParser;
 +      function getContent( $def_text = false ) {
 +              wfDeprecated( __METHOD__, '1.WD' );
 +
 +              if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
 +                      $def_content = ContentHandler::makeContent( $def_text, $this->getTitle() );
 +              } else {
 +                      $def_content = false;
 +              }
 +
 +              $content = $this->getContentObject( $def_content );
 +
 +              // Note: EditPage should only be used with text based content anyway.
 +              return $content->serialize( $this->content_format );
 +      }
 +
 +      private function getContentObject( $def_content = null ) {
 +              global $wgOut, $wgRequest;
  
                wfProfileIn( __METHOD__ );
  
 -              $text = false;
 +              $content = false;
  
                // 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();
 +                              $msg = $this->mTitle->getDefaultMessageText();
 +
 +                              $content = ContentHandler::makeContent( $msg, $this->mTitle );
                        }
 -                      if ( $text === false ) {
 +                      if ( $content === 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 );
 +
 +                              $content = $this->getPreloadedContent( $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 );
 +                              $orig = $this->getOriginalContent();
 +                              $content = $orig ? $orig->getSection( $this->section ) : null;
 +
 +                              if ( !$content ) $content = $def_content;
                        } else {
                                $undoafter = $wgRequest->getInt( 'undoafter' );
                                $undo = $wgRequest->getInt( 'undo' );
  
                                        # Sanity check, make sure it's the right page,
                                        # the revisions exist and they were not deleted.
 -                                      # Otherwise, $text will be left as-is.
 +                                      # Otherwise, $content 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 ) {
 +                                              $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
 +
 +                                              if ( $content === false ) {
                                                        # Warn the user that something went wrong
                                                        $undoMsg = 'failure';
                                                } else {
                                                wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
                                }
  
 -                              if ( $text === false ) {
 -                                      $text = $this->getOriginalContent();
 +                              if ( $content === false ) {
 +                                      $content = $this->getOriginalContent();
                                }
                        }
                }
  
                wfProfileOut( __METHOD__ );
 -              return $text;
 +              return $content;
        }
  
        /**
         */
        private function getOriginalContent() {
                if ( $this->section == 'new' ) {
 -                      return $this->getCurrentText();
 +                      return $this->getCurrentContent();
                }
                $revision = $this->mArticle->getRevisionFetched();
                if ( $revision === null ) {
 -                      return '';
 +                      if ( !$this->content_model ) $this->content_model = $this->getTitle()->getContentModel();
 +                      $handler = ContentHandler::getForModelID( $this->content_model );
 +
 +                      return $handler->makeEmptyContent();
                }
 -              return $this->mArticle->getContent();
 +              $content = $revision->getContent();
 +              return $content;
        }
  
        /**
 -       * 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.
 +       * Get the current content of the page. This is basically similar to
 +       * WikiPage::getContent( Revision::RAW ) except that when the page doesn't exist an empty
 +       * content object is returned instead of null.
         *
 -       * @since 1.19
 +       * @since 1.WD
         * @return string
         */
 -      private function getCurrentText() {
 -              $text = $this->mArticle->getRawText();
 -              if ( $text === false ) {
 -                      return '';
 +      private function getCurrentContent() {
 +              $rev = $this->mArticle->getRevision();
 +              $content = $rev ? $rev->getContent( Revision::RAW ) : null;
 +
 +              if ( $content  === false || $content === null ) {
 +                      if ( !$this->content_model ) $this->content_model = $this->getTitle()->getContentModel();
 +                      $handler = ContentHandler::getForModelID( $this->content_model );
 +
 +                      return $handler->makeEmptyContent();
                } else {
 -                      return $text;
 +                      # nasty side-effect, but needed for consistency
 +                      $this->content_model = $rev->getContentModel();
 +                      $this->content_format = $rev->getContentFormat();
 +
 +                      return $content;
                }
        }
  
 +
        /**
         * Use this method before edit() to preload some text into the edit box
         *
         * @param $text string
 +       * @deprecated since 1.WD
         */
        public function setPreloadedText( $text ) {
 -              $this->mPreloadText = $text;
 +              wfDeprecated( __METHOD__, "1.WD" );
 +
 +              $content = ContentHandler::makeContent( $text, $this->getTitle() );
 +
 +              $this->setPreloadedContent( $content );
 +      }
 +
 +      /**
 +       * Use this method before edit() to preload some content into the edit box
 +       *
 +       * @param $content Content
 +       *
 +       * @since 1.WD
 +       */
 +      public function setPreloadedContent( Content $content ) {
 +              $this->mPreloadedContent = $content;
        }
  
        /**
         * an earlier setPreloadText() or by loading the given page.
         *
         * @param $preload String: representing the title to preload from.
 +       *
         * @return String
 +       *
 +       * @deprecated since 1.WD, use getPreloadedContent() instead
 +       */
 +      protected function getPreloadedText( $preload ) { #NOTE: B/C only, replace usage!
 +              wfDeprecated( __METHOD__, "1.WD" );
 +
 +              $content = $this->getPreloadedContent( $preload );
 +              $text = $content->serialize( $this->content_format );
 +
 +              return $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 Content
 +       *
 +       * @since 1.WD
         */
 -      protected function getPreloadedText( $preload ) {
 -              global $wgUser, $wgParser;
 +      protected function getPreloadedContent( $preload ) { #@todo: use this!
 +              global $wgUser;
  
 -              if ( !empty( $this->mPreloadText ) ) {
 -                      return $this->mPreloadText;
 +              if ( !empty( $this->mPreloadContent ) ) {
 +                      return $this->mPreloadContent;
                }
  
 +              $handler = ContentHandler::getForTitle( $this->getTitle() );
 +
                if ( $preload === '' ) {
 -                      return '';
 +                      return $handler->makeEmptyContent();
                }
  
                $title = Title::newFromText( $preload );
                # Check for existence to avoid getting MediaWiki:Noarticletext
                if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
 -                      return '';
 +                      return $handler->makeEmptyContent();
                }
  
                $page = WikiPage::factory( $title );
                        $title = $page->getRedirectTarget();
                        # Same as before
                        if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
 -                              return '';
 +                              return $handler->makeEmptyContent();
                        }
                        $page = WikiPage::factory( $title );
                }
  
                $parserOptions = ParserOptions::newFromUser( $wgUser );
 -              return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
 +              $content = $page->getContent( Revision::RAW );
 +
 +              return $content->preloadTransform( $title, $parserOptions );
        }
  
        /**
                        case self::AS_HOOK_ERROR:
                                return false;
  
 +                      case self::AS_PARSE_ERROR:
 +                              $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>');
 +                              return true;
 +
                        case self::AS_SUCCESS_NEW_ARTICLE:
                                $query = $resultDetails['redirect'] ? 'redirect=no' : '';
                                $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
                        return $status;
                }
  
 +              try {
 +                      # Construct Content object
 +                      $textbox_content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(),
 +                                                                                                                      $this->content_model, $this->content_format );
 +              } catch (MWContentSerializationException $ex) {
 +                      $status->fatal( 'content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
 +                      $status->value = self::AS_PARSE_ERROR;
 +                      wfProfileOut( __METHOD__ );
 +                      return $status;
 +              }
 +
                # Check image redirect
                if ( $this->mTitle->getNamespace() == NS_FILE &&
 -                      Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
 +                      $textbox_content->isRedirect() &&
                        !$wgUser->isAllowed( 'upload' ) ) {
                                $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
                                $status->setResult( false, $code );
                                return $status;
                        }
  
 -                      $text = $this->textbox1;
 +                      $content = $textbox_content;
 +
                        $result['sectionanchor'] = '';
                        if ( $this->section == 'new' ) {
                                if ( $this->sectiontitle !== '' ) {
                                        // Insert the section title above the content.
 -                                      $text = wfMessage( 'newsectionheaderdefaultlevel', $this->sectiontitle )
 -                                              ->inContentLanguage()->text() . "\n\n" . $text;
 +                                      $content = $content->addSectionHeader( $this->sectiontitle );
  
                                        // Jump to the new section
                                        $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
                                        if ( $this->summary === '' ) {
                                                $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
                                                $this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle )
 -                                                      ->inContentLanguage()->text();
 +                                                      ->inContentLanguage()->text() ;
                                        }
                                } elseif ( $this->summary !== '' ) {
                                        // Insert the section title above the content.
 -                                      $text = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )
 -                                              ->inContentLanguage()->text() . "\n\n" . $text;
 +                                      $content = $content->addSectionHeader( $this->sectiontitle );
  
                                        // 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 = wfMessage( 'newsectionsummary', $cleanSummary )->inContentLanguage()->text() ;
+                                       $this->summary = wfMessage( 'newsectionsummary', $cleanSummary )
+                                               ->inContentLanguage()->text();
                                }
                        }
  
                        $status->value = self::AS_SUCCESS_NEW_ARTICLE;
  
 -              } else {
 +              } else { # not $new
  
                        # Article exists. Check for edit conflict.
 +
 +                      $this->mArticle->clear(); # Force reload of dates, etc.
                        $timestamp = $this->mArticle->getTimestamp();
 +
                        wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
  
                        if ( $timestamp != $this->edittime ) {
                                $sectionTitle = $this->summary;
                        }
  
 +                      $content = null;
 +
                        if ( $this->isConflict ) {
 -                              wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
 -                              $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
 +                              wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
 +                                              . " (article time '{$timestamp}')\n" );
 +
 +                              $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content,
 +                                                                                                                                      $sectionTitle, $this->edittime );
                        } else {
 -                              wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
 -                              $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
 +                              wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
 +                              $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
                        }
 -                      if ( is_null( $text ) ) {
 +
 +                      if ( is_null( $content ) ) {
                                wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
                                $this->isConflict = true;
 -                              $text = $this->textbox1; // do not try to merge here!
 +                              $content = $textbox_content; // do not try to merge here!
                        } elseif ( $this->isConflict ) {
                                # Attempt merge
 -                              if ( $this->mergeChangesInto( $text ) ) {
 +                              if ( $this->mergeChangesIntoContent( $textbox_content ) ) {
                                        // Successful merge! Maybe we should tell the user the good news?
                                        $this->isConflict = false;
 +                                      $content = $textbox_content;
                                        wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
                                } else {
                                        $this->section = '';
 -                                      $this->textbox1 = $text;
 +                                      #$this->textbox1 = $text; #redundant, nothing to do here?
                                        wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
                                }
                        }
                        }
  
                        // Run post-section-merge edit filter
 -                      if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
 +                      $hook_args = array( $this, $content, &$this->hookError, $this->summary );
 +
 +                      if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged', $hook_args )
 +                              || !wfRunHooks( 'EditFilterMergedContent', $hook_args ) ) {
                                # Error messages etc. could be handled within the hook...
                                $status->fatal( 'hookaborted' );
                                $status->value = self::AS_HOOK_ERROR;
  
                        # Handle the user preference to force summaries here, but not for null edits
                        if ( $this->section != 'new' && !$this->allowBlankSummary
 -                              && $this->getOriginalContent() != $text
 -                              && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
 +                              && !$content->equals( $this->getOriginalContent() )
 +                              && !$content->isRedirect() ) # check if it's not a redirect
                        {
                                if ( md5( $this->summary ) == $this->autoSumm ) {
                                        $this->missingSummary = true;
                        // merged the section into full text. Clear the section field
                        // so that later submission of conflict forms won't try to
                        // replace that into a duplicated mess.
 -                      $this->textbox1 = $text;
 +                      $this->textbox1 = $content->serialize( $this->content_format );
                        $this->section = '';
  
                        $status->value = self::AS_SUCCESS_UPDATE;
                }
  
                // Check for length errors again now that the section is merged in
 -              $this->kblength = (int)( strlen( $text ) / 1024 );
 +                      $this->kblength = (int)( strlen( $content->serialize( $this->content_format ) ) / 1024 );
                if ( $this->kblength > $wgMaxArticleSize ) {
                        $this->tooBig = true;
                        $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
                        ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
                        ( $bot ? EDIT_FORCE_BOT : 0 );
  
 -              $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
 +                      $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
 +                                                                                                                      false, null, $this->content_format );
  
                if ( $doEditStatus->isOK() ) {
 -                      $result['redirect'] = Title::newFromRedirect( $text ) !== null;
 +                              $result['redirect'] = $content->isRedirect();
                        $this->commitWatch();
                        wfProfileOut( __METHOD__ );
                        return $status;
         * @param $editText string
         *
         * @return bool
 +       * @deprecated since 1.WD, use mergeChangesIntoContent() instead
 +       */
 +      function mergeChangesInto( &$editText ){
 +              wfDebug( __METHOD__, "1.WD" );
 +
 +              $editContent = ContentHandler::makeContent( $editText, $this->getTitle(),
 +                                                                                                      $this->content_model, $this->content_format );
 +
 +              $ok = $this->mergeChangesIntoContent( $editContent );
 +
 +              if ( $ok ) {
 +                      $editText = $editContent->serialize( $this->content_format );
 +                      return true;
 +              } else {
 +                      return false;
 +              }
 +      }
 +
 +      /**
 +       * @private
 +       * @todo document
 +       *
 +       * @parma $editText string
 +       *
 +       * @return bool
 +       * @since since 1.WD
         */
 -      function mergeChangesInto( &$editText ) {
 +      private function mergeChangesIntoContent( &$editContent ){
                wfProfileIn( __METHOD__ );
  
                $db = wfGetDB( DB_MASTER );
                        wfProfileOut( __METHOD__ );
                        return false;
                }
 -              $baseText = $baseRevision->getText();
 +              $baseContent = $baseRevision->getContent();
  
                // The current state, we want to merge updates into it
                $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
                        wfProfileOut( __METHOD__ );
                        return false;
                }
 -              $currentText = $currentRevision->getText();
 +              $currentContent = $currentRevision->getContent();
  
 -              $result = '';
 -              if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
 -                      $editText = $result;
 +              $handler = ContentHandler::getForModelID( $baseContent->getModel() );
 +
 +              $result = $handler->merge3( $baseContent, $editContent, $currentContent );
 +
 +              if ( $result ) {
 +                      $editContent = $result;
                        wfProfileOut( __METHOD__ );
                        return true;
                } else {
                        }
                }
  
 +              //@todo: add EditForm plugin interface and use it here!
 +              //       search for textarea1 and textares2, and allow EditForm to override all uses.
                $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
                        'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
                        'enctype' => 'multipart/form-data' ) ) );
  
                $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
  
 +              $wgOut->addHTML( Html::hidden( 'format', $this->content_format ) );
 +              $wgOut->addHTML( Html::hidden( 'model', $this->content_model ) );
 +
                if ( $this->section == 'new' ) {
                        $this->showSummaryInput( true, $this->summary );
                        $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
                        // resolved between page source edits and custom ui edits using the
                        // custom edit ui.
                        $this->textbox2 = $this->textbox1;
 -                      $this->textbox1 = $this->getCurrentText();
 +
 +                      $content = $this->getCurrentContent();
 +                      $this->textbox1 = $content->serialize( $this->content_format );
  
                        $this->showTextbox1();
                } else {
                        Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
  
                if ( $this->isConflict ) {
 -                      $this->showConflict();
 +                      try {
 +                              $this->showConflict();
 +                      } catch ( MWContentSerializationException $ex ) {
 +                              // this can't really happen, but be nice if it does.
 +                              $msg = wfMessage( 'content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
 +                              $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
 +                      }
                }
  
                $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
  
                        if ( $this->section != '' && $this->section != 'new' ) {
                                if ( !$this->summary && !$this->preview && !$this->diff ) {
 -                                      $sectionTitle = self::extractSectionTitle( $this->textbox1 );
 +                                      $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
                                        if ( $sectionTitle !== false ) {
                                                $this->summary = "/* $sectionTitle */ ";
                                        }
                                );
                        }
                }
+               # Add header copyright warning
+               $this->showHeaderCopyrightWarning();
        }
  
        /**
         * Standard summary input and label (wgSummary), abstracted so EditPage
         * subclasses may reorganize the form.
                $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
        }
  
 -      protected function showTextbox( $content, $name, $customAttribs = array() ) {
 +      protected function showTextbox( $text, $name, $customAttribs = array() ) {
                global $wgOut, $wgUser;
  
 -              $wikitext = $this->safeUnicodeOutput( $content );
 +              $wikitext = $this->safeUnicodeOutput( $text );
                if ( strval( $wikitext ) !== '' ) {
                        // Ensure there's a newline at the end, otherwise adding lines
                        // is awkward.
                $wgOut->addHTML( '</div>' );
  
                if ( $this->formtype == 'diff' ) {
 -                      $this->showDiff();
 +                      try {
 +                              $this->showDiff();
 +                      } catch ( MWContentSerializationException $ex ) {
 +                              $msg = wfMessage( 'content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
 +                              $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
 +                      }
                }
        }
  
                        $oldtext = $this->mTitle->getDefaultMessageText();
                        if( $oldtext !== false ) {
                                $oldtitlemsg = 'defaultmessagetext';
 +                              $oldContent = ContentHandler::makeContent( $oldtext, $this->mTitle );
 +                      } else {
 +                              $oldContent = null;
                        }
                } else {
 -                      $oldtext = $this->mArticle->getRawText();
 +                      $oldContent = $this->getOriginalContent();
                }
 -              $newtext = $this->mArticle->replaceSection(
 -                      $this->section, $this->textbox1, $this->summary, $this->edittime );
  
 -              wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
 +              $textboxContent = ContentHandler::makeContent( $this->textbox1, $this->getTitle(),
 +                                                                                                              $this->content_model, $this->content_format );
 +
 +              $newContent = $this->mArticle->replaceSectionContent(
 +                                                                                      $this->section, $textboxContent,
 +                                                                                      $this->summary, $this->edittime );
 +
 +              ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
 +              wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
  
                $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
 -              $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
 +              $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
  
 -              if ( $oldtext !== false  || $newtext != '' ) {
 +              if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
                        $oldtitle = wfMessage( $oldtitlemsg )->parse();
                        $newtitle = wfMessage( 'yourtext' )->parse();
  
 -                      $de = new DifferenceEngine( $this->mArticle->getContext() );
 -                      $de->setText( $oldtext, $newtext );
 +                      $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
 +                      $de->setContent( $oldContent, $newContent );
 +
                        $difftext = $de->getDiff( $oldtitle, $newtitle );
                        $de->showDiffStyle();
                } else {
                $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
        }
  
+       /**
+        * Show the header copyright warning.
+        */
+       protected function showHeaderCopyrightWarning() {
+               $msg = 'editpage-head-copy-warn';
+               if ( !wfMessage( $msg )->isDisabled() ) {
+                       global $wgOut;
+                       $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
+                               'editpage-head-copy-warn' );
+               }
+       }
        /**
         * Give a chance for site and per-namespace customizations of
         * terms of service summary link that might exist separately
                // Allow for site and per-namespace customization of contribution/copyright notice.
                wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
  
 +              $msg = call_user_func_array( "wfMessage", $copywarnMsg );
                return "<div id=\"editpage-copywarn\">\n" .
 -                      call_user_func_array( "wfMsgNoTrans", $copywarnMsg ) . "\n</div>";
 +                      $msg->plain() . "\n</div>";
        }
  
        protected function showStandardInputs( &$tabindex = 2 ) {
                if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
  
 -                      $de = new DifferenceEngine( $this->mArticle->getContext() );
 -                      $de->setText( $this->textbox2, $this->textbox1 );
 -                      $de->showDiff(
 +                      $content1 = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
 +                      $content2 = ContentHandler::makeContent( $this->textbox2, $this->getTitle(), $this->content_model, $this->content_format );
 +
 +                      $handler = ContentHandler::getForModelID( $this->content_model );
 +                      $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
 +                      $de->setContent( $content2, $content1 );
 +                      $de->showDiff( 
                                wfMessage( 'yourtext' )->parse(),
                                wfMessage( 'storedversion' )->text()
                        );
                        return $parsedNote;
                }
  
 -              if ( $this->mTriedSave && !$this->mTokenOk ) {
 -                      if ( $this->mTokenOkExceptSuffix ) {
 -                              $note = wfMessage( 'token_suffix_mismatch' )->plain();
 -                      } else {
 -                              $note = wfMessage( 'session_fail_preview' )->plain();
 -                      }
 -              } elseif ( $this->incompleteForm ) {
 -                      $note = wfMessage( 'edit_form_incomplete' )->plain();
 -              } else {
 -                      $note = wfMessage( 'previewnote' )->plain() .
 -                              ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
 -              }
 +              $note = '';
  
 -              $parserOptions = ParserOptions::newFromUser( $wgUser );
 -              $parserOptions->setEditSection( false );
 -              $parserOptions->setTidy( true );
 -              $parserOptions->setIsPreview( true );
 -              $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
 -
 -              # don't parse non-wikitext pages, show message about preview
 -              if ( $this->mTitle->isCssJsSubpage() || !$this->mTitle->isWikitextPage() ) {
 -                      if ( $this->mTitle->isCssJsSubpage() ) {
 -                              $level = 'user';
 -                      } elseif ( $this->mTitle->isCssOrJsPage() ) {
 -                              $level = 'site';
 -                      } else {
 -                              $level = false;
 -                      }
 -
 -                      # Used messages to make sure grep find them:
 -                      # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
 -                      $class = 'mw-code';
 -                      if ( $level ) {
 -                              if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
 -                                      $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMessage( "{$level}csspreview" )->text() . "\n</div>";
 -                                      $class .= " mw-css";
 -                              } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
 -                                      $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMessage( "{$level}jspreview" )->text() . "\n</div>";
 -                                      $class .= " mw-js";
 +              try {
 +                      $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(),
 +                                                                                                      $this->content_model, $this->content_format );
 +
 +                      if ( $this->mTriedSave && !$this->mTokenOk ) {
 +                              if ( $this->mTokenOkExceptSuffix ) {
 +                                      $note = wfMessage( 'token_suffix_mismatch' )->plain() ;
                                } else {
 -                                      throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
 +                                      $note = wfMessage( 'session_fail_preview' )->plain() ;
                                }
 -                              $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
 -                              $previewHTML = $parserOutput->getText();
 +                      } elseif ( $this->incompleteForm ) {
 +                              $note = wfMessage( 'edit_form_incomplete' )->plain() ;
                        } else {
 -                              $previewHTML = '';
 -                      }
 +                              $note = wfMessage( 'previewnote' )->plain() .
 +                                      ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
 +                      }
 +
 +                      $parserOptions = ParserOptions::newFromUser( $wgUser );
 +                      $parserOptions->setEditSection( false );
 +                      $parserOptions->setTidy( true );
 +                      $parserOptions->setIsPreview( true );
 +                      $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
 +
 +                      # don't parse non-wikitext pages, show message about preview
 +                      if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
 +                              if( $this->mTitle->isCssJsSubpage() ) {
 +                                      $level = 'user';
 +                              } elseif( $this->mTitle->isCssOrJsPage() ) {
 +                                      $level = 'site';
 +                              } else {
 +                                      $level = false;
 +                              }
  
 -                      $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
 -              } else {
 -                      $toparse = $this->textbox1;
 +                              if ( $content->getModel() == CONTENT_MODEL_CSS ) {
 +                                      $format = 'css';
 +                              } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
 +                                      $format = 'js';
 +                              } else {
 +                                      $format = false;
 +                              }
  
 -                      # If we're adding a comment, we need to show the
 -                      # summary as the headline
 -                      if ( $this->section == "new" && $this->summary != "" ) {
 -                              $toparse = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )->inContentLanguage()->text() . "\n\n" . $toparse;
 +                              # Used messages to make sure grep find them:
 +                              # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
 +                              if( $level && $format ) {
 +                                      $note = "<div id='mw-{$level}{$format}preview'>" . wfMessage( "{$level}{$format}preview" )->text()  . "</div>";
 +                              } else {
 +                                      $note = wfMessage( 'previewnote' )->text() ;
 +                              }
 +                      } else {
 +                              $note = wfMessage( 'previewnote' )->text() ;
                        }
  
 -                      wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
 +                      $rt = $content->getRedirectChain();
  
 -                      $parserOptions->enableLimitReport();
 -
 -                      $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
 -                      $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
 -
 -                      $rt = Title::newFromRedirectArray( $this->textbox1 );
                        if ( $rt ) {
                                $previewHTML = $this->mArticle->viewRedirect( $rt, false );
                        } else {
 -                              $previewHTML = $parserOutput->getText();
 -                      }
  
 -                      $this->mParserOutput = $parserOutput;
 -                      $wgOut->addParserOutputNoText( $parserOutput );
 +                              # If we're adding a comment, we need to show the
 +                              # summary as the headline
 +                              if ( $this->section == "new" && $this->summary != "" ) {
 +                                      $content = $content->addSectionHeader( $this->summary );
 +                              }
 +
 +                              $hook_args = array( $this, &$content );
 +                              ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
 +                              wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
 +
 +                              $parserOptions->enableLimitReport();
 +
 +                              # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
 +                              # But it's now deprecated, so never mind
  
 -                      if ( count( $parserOutput->getWarnings() ) ) {
 -                              $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
 +                              $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
 +                              $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
 +
 +                              $previewHTML = $parserOutput->getText();
 +                              $this->mParserOutput = $parserOutput;
 +                              $wgOut->addParserOutputNoText( $parserOutput );
 +
 +                              if ( count( $parserOutput->getWarnings() ) ) {
 +                                      $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
 +                              }
                        }
 +              } catch (MWContentSerializationException $ex) {
 +                      $m = wfMessage('content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
 +                      $note .= "\n\n" . $m->parse();
 +                      $previewHTML = '';
                }
  
                if ( $this->isConflict ) {
@@@ -391,7 -391,7 +391,7 @@@ function wfArrayToCgi( $array1, $array
  
        $cgi = '';
        foreach ( $array1 as $key => $value ) {
 -              if ( !is_null($value) && $value !== false ) {
 +              if ( $value !== false ) {
                        if ( $cgi != '' ) {
                                $cgi .= '&';
                        }
                        } else {
                                if ( is_object( $value ) ) {
                                        $value = $value->__toString();
 +                              } elseif( !is_null( $value ) ) {
 +                                      $cgi .= urlencode( $key ) . '=' . urlencode( $value );
 +                              } else {
 +                                      $cgi .= urlencode( $key );
                                }
 -                              $cgi .= urlencode( $key ) . '=' . urlencode( $value );
                        }
                }
        }
@@@ -443,15 -440,14 +443,15 @@@ function wfCgiToArray( $query ) 
                        continue;
                }
                if ( strpos( $bit, '=' ) === false ) {
 -                      // Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
 -                      $key = $bit;
 -                      $value = '';
 +                      // Pieces like &qwerty become 'qwerty' => null
 +                      $key = urldecode( $bit );
 +                      $value = null;
                } else {
                        list( $key, $value ) = explode( '=', $bit );
 +                      $key = urldecode( $key );
 +                      $value = urldecode( $value );
                }
 -              $key = urldecode( $key );
 -              $value = urldecode( $value );
 +
                if ( strpos( $key, '[' ) !== false ) {
                        $keys = array_reverse( explode( '[', $key ) );
                        $key = array_pop( $keys );
   * Append a query string to an existing URL, which may or may not already
   * have query string parameters already. If so, they will be combined.
   *
 + * @deprecated in 1.20. Use Uri class.
   * @param $url String
   * @param $query Mixed: string or associative array
   * @return string
   */
  function wfAppendQuery( $url, $query ) {
 -      if ( is_array( $query ) ) {
 -              $query = wfArrayToCgi( $query );
 -      }
 -      if( $query != '' ) {
 -              if( false === strpos( $url, '?' ) ) {
 -                      $url .= '?';
 -              } else {
 -                      $url .= '&';
 -              }
 -              $url .= $query;
 -      }
 -      return $url;
 +      $obj = new Uri( $url );
 +      $obj->extendQuery( $query );
 +      return $obj->toString();
  }
  
  /**
@@@ -572,13 -576,49 +572,13 @@@ function wfExpandUrl( $url, $defaultPro
   * @todo Need to integrate this into wfExpandUrl (bug 32168)
   *
   * @since 1.19
 + * @deprecated
   * @param $urlParts Array URL parts, as output from wfParseUrl
   * @return string URL assembled from its component parts
   */
  function wfAssembleUrl( $urlParts ) {
 -      $result = '';
 -
 -      if ( isset( $urlParts['delimiter'] ) ) {
 -              if ( isset( $urlParts['scheme'] ) ) {
 -                      $result .= $urlParts['scheme'];
 -              }
 -
 -              $result .= $urlParts['delimiter'];
 -      }
 -
 -      if ( isset( $urlParts['host'] ) ) {
 -              if ( isset( $urlParts['user'] ) ) {
 -                      $result .= $urlParts['user'];
 -                      if ( isset( $urlParts['pass'] ) ) {
 -                              $result .= ':' . $urlParts['pass'];
 -                      }
 -                      $result .= '@';
 -              }
 -
 -              $result .= $urlParts['host'];
 -
 -              if ( isset( $urlParts['port'] ) ) {
 -                      $result .= ':' . $urlParts['port'];
 -              }
 -      }
 -
 -      if ( isset( $urlParts['path'] ) ) {
 -              $result .= $urlParts['path'];
 -      }
 -
 -      if ( isset( $urlParts['query'] ) ) {
 -              $result .= '?' . $urlParts['query'];
 -      }
 -
 -      if ( isset( $urlParts['fragment'] ) ) {
 -              $result .= '#' . $urlParts['fragment'];
 -      }
 -
 -      return $result;
 +      $obj = new Uri( $urlParts );
 +      return $obj->toString();
  }
  
  /**
@@@ -725,13 -765,58 +725,13 @@@ function wfUrlProtocolsWithoutProtRel(
   * 2) Handles protocols that don't use :// (e.g., mailto: and news: , as well as protocol-relative URLs) correctly
   * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2))
   *
 + * @deprecated
   * @param $url String: a URL to parse
   * @return Array: bits of the URL in an associative array, per PHP docs
   */
  function wfParseUrl( $url ) {
 -      global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
 -
 -      // Protocol-relative URLs are handled really badly by parse_url(). It's so bad that the easiest
 -      // way to handle them is to just prepend 'http:' and strip the protocol out later
 -      $wasRelative = substr( $url, 0, 2 ) == '//';
 -      if ( $wasRelative ) {
 -              $url = "http:$url";
 -      }
 -      wfSuppressWarnings();
 -      $bits = parse_url( $url );
 -      wfRestoreWarnings();
 -      // parse_url() returns an array without scheme for some invalid URLs, e.g.
 -      // parse_url("%0Ahttp://example.com") == array( 'host' => '%0Ahttp', 'path' => 'example.com' )
 -      if ( !$bits || !isset( $bits['scheme'] ) ) {
 -              return false;
 -      }
 -
 -      // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
 -      if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
 -              $bits['delimiter'] = '://';
 -      } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
 -              $bits['delimiter'] = ':';
 -              // parse_url detects for news: and mailto: the host part of an url as path
 -              // We have to correct this wrong detection
 -              if ( isset( $bits['path'] ) ) {
 -                      $bits['host'] = $bits['path'];
 -                      $bits['path'] = '';
 -              }
 -      } else {
 -              return false;
 -      }
 -
 -      /* Provide an empty host for eg. file:/// urls (see bug 28627) */
 -      if ( !isset( $bits['host'] ) ) {
 -              $bits['host'] = '';
 -
 -              /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
 -              if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
 -                      $bits['path'] = '/' . $bits['path'];
 -              }
 -      }
 -
 -      // If the URL was protocol-relative, fix scheme and delimiter
 -      if ( $wasRelative ) {
 -              $bits['scheme'] = '';
 -              $bits['delimiter'] = '//';
 -      }
 -      return $bits;
 +      $obj = new Uri( $url );
 +      return $obj->getComponents();
  }
  
  /**
@@@ -976,8 -1061,9 +976,10 @@@ function wfLogDBError( $text ) 
                } else {
                        $d = date_create( "now", $logDBErrorTimeZoneObject );
                }
 +              $date = $d->format( 'D M j G:i:s T Y' );
  
+               $date = $d->format( 'D M j G:i:s T Y' );
                $text = "$date\t$host\t$wiki\t$text";
                wfErrorLog( $text, $wgDBerrorLog );
        }
   * @return null
   */
  function wfDeprecated( $function, $version = false, $component = false, $callerOffset = 2 ) {
-       static $functionsWarned = array();
-       MWDebug::deprecated( $function, $version, $component );
-       if ( !isset( $functionsWarned[$function] ) ) {
-               $functionsWarned[$function] = true;
-               if ( $version ) {
-                       global $wgDeprecationReleaseLimit;
-                       if ( $wgDeprecationReleaseLimit && $component === false ) {
-                               # Strip -* off the end of $version so that branches can use the
-                               # format #.##-branchname to avoid issues if the branch is merged into
-                               # a version of MediaWiki later than what it was branched from
-                               $comparableVersion = preg_replace( '/-.*$/', '', $version );
-                               # If the comparableVersion is larger than our release limit then
-                               # skip the warning message for the deprecation
-                               if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
-                                       return;
-                               }
-                       }
-                       $component = $component === false ? 'MediaWiki' : $component;
-                       wfWarn( "Use of $function was deprecated in $component $version.", $callerOffset );
-               } else {
-                       wfWarn( "Use of $function is deprecated.", $callerOffset );
-               }
-       }
+       MWDebug::deprecated( $function, $version, $component, $callerOffset + 1 );
  }
  
  /**
   *        is true
   */
  function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
-       global $wgDevelopmentWarnings;
-       MWDebug::warning( $msg, $callerOffset + 2 );
-       $callers = wfDebugBacktrace();
-       if ( isset( $callers[$callerOffset + 1] ) ) {
-               $callerfunc = $callers[$callerOffset + 1];
-               $callerfile = $callers[$callerOffset];
-               if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
-                       $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
-               } else {
-                       $file = '(internal function)';
-               }
-               $func = '';
-               if ( isset( $callerfunc['class'] ) ) {
-                       $func .= $callerfunc['class'] . '::';
-               }
-               if ( isset( $callerfunc['function'] ) ) {
-                       $func .= $callerfunc['function'];
-               }
-               $msg .= " [Called from $func in $file]";
-       }
-       if ( $wgDevelopmentWarnings ) {
-               trigger_error( $msg, $level );
-       } else {
-               wfDebug( "$msg\n" );
-       }
+       MWDebug::warning( $msg, $callerOffset + 1, $level );
  }
  
  /**
@@@ -2297,11 -2328,7 +2244,7 @@@ function wfSuppressWarnings( $end = fal
                }
        } else {
                if ( !$suppressCount ) {
-                       // E_DEPRECATED is undefined in PHP 5.2
-                       if( !defined( 'E_DEPRECATED' ) ) {
-                               define( 'E_DEPRECATED', 8192 );
-                       }
-                       $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED ) );
+                       $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED ) );
                }
                ++$suppressCount;
        }
diff --combined includes/Title.php
@@@ -65,7 -65,6 +65,7 @@@ class Title 
        var $mFragment;                   // /< Title fragment (i.e. the bit after the #)
        var $mArticleID = -1;             // /< Article ID, fetched from the link cache on demand
        var $mLatestID = false;           // /< ID of most recent revision
 +      var $mContentModel = false;       // /< ID of the page's content model, i.e. one of the CONTENT_MODEL_XXX constants
        private $mEstimateRevisions;      // /< Estimated number of revisions; null of not loaded
        var $mRestrictions = array();     // /< Array of groups allowed to edit this article
        var $mOldRestrictions = false;
                }
        }
  
 +      /**
 +       * Returns a list of fields that are to be selected for initializing Title objects or LinkCache entries.
 +       * Uses $wgContentHandlerUseDB to determine whether to include page_content_model.
 +       *
 +       * @return array
 +       */
 +      protected static function getSelectFields() {
 +              global $wgContentHandlerUseDB;
 +
 +              $fields = array(
 +                      'page_namespace', 'page_title', 'page_id',
 +                      'page_len', 'page_is_redirect', 'page_latest',
 +              );
 +
 +              if ( $wgContentHandlerUseDB ) {
 +                      $fields[] = 'page_content_model';
 +              }
 +
 +              return $fields;
 +      }
 +
        /**
         * Create a new Title from an article ID
         *
                $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                $row = $db->selectRow(
                        'page',
 -                      array(
 -                              'page_namespace', 'page_title', 'page_id',
 -                              'page_len', 'page_is_redirect', 'page_latest',
 -                      ),
 +                      self::getSelectFields(),
                        array( 'page_id' => $id ),
                        __METHOD__
                );
  
                $res = $dbr->select(
                        'page',
 -                      array(
 -                              'page_namespace', 'page_title', 'page_id',
 -                              'page_len', 'page_is_redirect', 'page_latest',
 -                      ),
 +                      self::getSelectFields(),
                        array( 'page_id' => $ids ),
                        __METHOD__
                );
                                $this->mRedirect = (bool)$row->page_is_redirect;
                        if ( isset( $row->page_latest ) )
                                $this->mLatestID = (int)$row->page_latest;
 +                      if ( isset( $row->page_content_model ) )
 +                              $this->mContentModel = strval( $row->page_content_model );
 +                      else
 +                              $this->mContentModel = false; # initialized lazily in getContentModel()
                } else { // page not found
                        $this->mArticleID = 0;
                        $this->mLength = 0;
                        $this->mRedirect = false;
                        $this->mLatestID = 0;
 +                      $this->mContentModel = false; # initialized lazily in getContentModel()
                }
        }
  
                $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
                $t->mUrlform = wfUrlencode( $t->mDbkeyform );
                $t->mTextform = str_replace( '_', ' ', $title );
 +              $t->mContentModel = false; # initialized lazily in getContentModel()
                return $t;
        }
  
         *
         * @param $text String: Text with possible redirect
         * @return Title: The corresponding Title
 +       * @deprecated since 1.WD, use Content::getRedirectTarget instead.
         */
        public static function newFromRedirect( $text ) {
 -              return self::newFromRedirectInternal( $text );
 +              $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
 +              return $content->getRedirectTarget();
        }
  
        /**
         *
         * @param $text String Text with possible redirect
         * @return Title
 +       * @deprecated since 1.WD, use Content::getUltimateRedirectTarget instead.
         */
        public static function newFromRedirectRecurse( $text ) {
 -              $titles = self::newFromRedirectArray( $text );
 -              return $titles ? array_pop( $titles ) : null;
 +              $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
 +              return $content->getUltimateRedirectTarget();
        }
  
        /**
         *
         * @param $text String Text with possible redirect
         * @return Array of Titles, with the destination last
 +       * @deprecated since 1.WD, use Content::getRedirectChain instead.
         */
        public static function newFromRedirectArray( $text ) {
 -              global $wgMaxRedirects;
 -              $title = self::newFromRedirectInternal( $text );
 -              if ( is_null( $title ) ) {
 -                      return null;
 -              }
 -              // recursive check to follow double redirects
 -              $recurse = $wgMaxRedirects;
 -              $titles = array( $title );
 -              while ( --$recurse > 0 ) {
 -                      if ( $title->isRedirect() ) {
 -                              $page = WikiPage::factory( $title );
 -                              $newtitle = $page->getRedirectTarget();
 -                      } else {
 -                              break;
 -                      }
 -                      // Redirects to some special pages are not permitted
 -                      if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
 -                              // the new title passes the checks, so make that our current title so that further recursion can be checked
 -                              $title = $newtitle;
 -                              $titles[] = $newtitle;
 -                      } else {
 -                              break;
 -                      }
 -              }
 -              return $titles;
 -      }
 -
 -      /**
 -       * Really extract the redirect destination
 -       * Do not call this function directly, use one of the newFromRedirect* functions above
 -       *
 -       * @param $text String Text with possible redirect
 -       * @return Title
 -       */
 -      protected static function newFromRedirectInternal( $text ) {
 -              global $wgMaxRedirects;
 -              if ( $wgMaxRedirects < 1 ) {
 -                      //redirects are disabled, so quit early
 -                      return null;
 -              }
 -              $redir = MagicWord::get( 'redirect' );
 -              $text = trim( $text );
 -              if ( $redir->matchStartAndRemove( $text ) ) {
 -                      // Extract the first link and see if it's usable
 -                      // Ensure that it really does come directly after #REDIRECT
 -                      // Some older redirects included a colon, so don't freak about that!
 -                      $m = array();
 -                      if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
 -                              // Strip preceding colon used to "escape" categories, etc.
 -                              // and URL-decode links
 -                              if ( strpos( $m[1], '%' ) !== false ) {
 -                                      // Match behavior of inline link parsing here;
 -                                      $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
 -                              }
 -                              $title = Title::newFromText( $m[1] );
 -                              // If the title is a redirect to bad special pages or is invalid, return null
 -                              if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
 -                                      return null;
 -                              }
 -                              return $title;
 -                      }
 -              }
 -              return null;
 +              $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
 +              return $content->getRedirectChain();
        }
  
        /**
                return $this->mNamespace;
        }
  
 +      /**
 +       * Get the page's content model id, see the CONTENT_MODEL_XXX constants.
 +       *
 +       * @return String: Content model id
 +       */
 +      public function getContentModel() {
 +              if ( !$this->mContentModel ) {
 +                      $linkCache = LinkCache::singleton();
 +                      $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
 +              }
 +
 +              if ( !$this->mContentModel ) {
 +                      $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
 +              }
 +
 +              if( !$this->mContentModel ) {
 +                      throw new MWException( "failed to determin content model!" );
 +              }
 +
 +              return $this->mContentModel;
 +      }
 +
 +      /**
 +       * Convenience method for checking a title's content model name
 +       *
 +       * @param int $id
 +       * @return Boolean true if $this->getContentModel() == $id
 +       */
 +      public function hasContentModel( $id ) {
 +              return $this->getContentModel() == $id;
 +      }
 +
        /**
         * Get the namespace text
         *
         * @return Bool
         */
        public function isWikitextPage() {
 -              $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
 -              wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
 -              return $retval;
 +              return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
        }
  
        /**
 -       * Could this page contain custom CSS or JavaScript, based
 -       * on the title?
 +       * Could this page contain custom CSS or JavaScript for the global UI.
 +       * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS
 +       * or CONTENT_MODEL_JAVASCRIPT.
 +       *
 +       * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage() for that!
 +       *
 +       * Note that this method should not return true for pages that contain and show "inactive" CSS or JS.
         *
         * @return Bool
         */
        public function isCssOrJsPage() {
 -              $retval = $this->mNamespace == NS_MEDIAWIKI
 -                      && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
 -              wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
 -              return $retval;
 +              $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
 +                      && ( $this->hasContentModel( CONTENT_MODEL_CSS )
 +                              || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
 +
 +              #NOTE: this hook is also called in ContentHandler::getDefaultModel. It's called here again to make sure
 +              #      hook funktions can force this method to return true even outside the mediawiki namespace.
 +
 +              wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
 +
 +              return $isCssOrJsPage;
        }
  
        /**
         * @return Bool
         */
        public function isCssJsSubpage() {
 -              return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
 +              return ( NS_USER == $this->mNamespace && $this->isSubpage()
 +                              && ( $this->hasContentModel( CONTENT_MODEL_CSS )
 +                                      || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
        }
  
        /**
         * @return Bool
         */
        public function isCssSubpage() {
 -              return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
 +              return ( NS_USER == $this->mNamespace && $this->isSubpage()
 +                      && $this->hasContentModel( CONTENT_MODEL_CSS ) );
        }
  
        /**
         * @return Bool
         */
        public function isJsSubpage() {
 -              return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
 +              return ( NS_USER == $this->mNamespace && $this->isSubpage()
 +                      && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
        }
  
        /**
                if ( !$this->getArticleID( $flags ) ) {
                        return $this->mRedirect = false;
                }
 +
                $linkCache = LinkCache::singleton();
 -              $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
 +              $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
 +              if ( $cached === null ) { # check the assumption that the cache actually knows about this title
 +                      # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
 +                      #      as a stop gap, perhaps log this, but don't throw an exception?
 +                      throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
 +              }
 +
 +              $this->mRedirect = (bool)$cached;
  
                return $this->mRedirect;
        }
                        return $this->mLength = 0;
                }
                $linkCache = LinkCache::singleton();
 -              $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
 +              $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
 +              if ( $cached === null ) { # check the assumption that the cache actually knows about this title
 +                      # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
 +                      #      as a stop gap, perhaps log this, but don't throw an exception?
 +                      throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
 +              }
 +
 +              $this->mLength = intval( $cached );
  
                return $this->mLength;
        }
                        return $this->mLatestID = 0;
                }
                $linkCache = LinkCache::singleton();
 -              $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
 +              $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
 +              if ( $cached === null ) { # check the assumption that the cache actually knows about this title
 +                      # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
 +                      #      as a stop gap, perhaps log this, but don't throw an exception?
 +                      throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
 +              }
 +
 +              $this->mLatestID = intval( $cached );
  
                return $this->mLatestID;
        }
                $this->mRedirect = null;
                $this->mLength = -1;
                $this->mLatestID = false;
 +              $this->mContentModel = false;
                $this->mEstimateRevisions = null;
        }
  
  
                $res = $db->select(
                        array( 'page', $table ),
 -                      array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
 +                      self::getSelectFields(),
                        array(
                                "{$prefix}_from=page_id",
                                "{$prefix}_namespace" => $this->getNamespace(),
         * @return Array of Title objects linking here
         */
        public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
 +              global $wgContentHandlerUseDB;
 +
                $id = $this->getArticleID();
  
                # If the page doesn't exist; there can't be any link from this page
                $namespaceFiled = "{$prefix}_namespace";
                $titleField = "{$prefix}_title";
  
 +              $fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
 +              if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
 +
                $res = $db->select(
                        array( $table, 'page' ),
 -                      array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
 +                      $fields,
                        array( "{$prefix}_from" => $id ),
                        __METHOD__,
                        $options,
         * @return Bool
         */
        public function isSingleRevRedirect() {
 +              global $wgContentHandlerUseDB;
 +
                $dbw = wfGetDB( DB_MASTER );
 +
                # Is it a redirect?
 +              $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
 +              if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
 +
                $row = $dbw->selectRow( 'page',
 -                      array( 'page_is_redirect', 'page_latest', 'page_id' ),
 +                      $fields,
                        $this->pageCond(),
                        __METHOD__,
                        array( 'FOR UPDATE' )
                $this->mArticleID = $row ? intval( $row->page_id ) : 0;
                $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
                $this->mLatestID = $row ? intval( $row->page_latest ) : false;
 +              $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
                if ( !$this->mRedirect ) {
                        return false;
                }
                if( !is_object( $rev ) ){
                        return false;
                }
 -              $text = $rev->getText();
 +              $content = $rev->getContent();
                # Does the redirect point to the source?
                # Or is it a broken self-redirect, usually caused by namespace collisions?
 -              $m = array();
 -              if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
 -                      $redirTitle = Title::newFromText( $m[1] );
 -                      if ( !is_object( $redirTitle ) ||
 -                              ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
 -                              $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
 +              $redirTitle = $content->getRedirectTarget();
 +
 +              if ( $redirTitle ) {
 +                      if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
 +                              $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
                                wfDebug( __METHOD__ . ": redirect points to other page\n" );
                                return false;
 +                      } else {
 +                              return true;
                        }
                } else {
 -                      # Fail safe
 -                      wfDebug( __METHOD__ . ": failsafe\n" );
 +                      # Fail safe (not a redirect after all. strange.)
 +                      wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
 +                                              " is a redirect, but it doesn't contain a valid redirect.\n" );
                        return false;
                }
 -              return true;
        }
  
        /**
                                return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
                        }
                        return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2;
-               }
+               }\r
                $dbr = wfGetDB( DB_SLAVE );
                $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
                        array(
                if ( $this->isSpecialPage() ) {
                        // special pages are in the user language
                        return $wgLang;
 -              } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
 -                      // css/js should always be LTR and is, in fact, English
 -                      return wfGetLangObj( 'en' );
                } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
                        // Parse mediawiki messages with correct target language
                        list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
                        return wfGetLangObj( $lang );
                }
 -              global $wgContLang;
 -              // If nothing special, it should be in the wiki content language
 -              $pageLang = $wgContLang;
 +
 +              //TODO: use the LinkCache to cache this!
 +              //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
 +              $contentHandler = ContentHandler::getForTitle( $this );
 +              $pageLang = $contentHandler->getPageLanguage( $this );
 +
                // Hook at the end because we don't want to override the above stuff
                wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
                return wfGetLangObj( $pageLang );
@@@ -38,7 -38,7 +38,7 @@@ class DifferenceEngine extends ContextS
         * @private
         */
        var $mOldid, $mNewid;
 -      var $mOldtext, $mNewtext;
 +      var $mOldContent, $mNewContent;
        protected $mDiffLang;
  
        /**
                # we'll use the application/x-external-editor interface to call
                # an external diff tool like kompare, kdiff3, etc.
                if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) {
 +                      //TODO: come up with a good solution for non-text content here.
 +                      //      at least, the content format needs to be passed to the client somehow.
 +                      //      Currently, action=raw will just fail for non-text content.
 +
                        $urls = array(
                                'File' => array( 'Extension' => 'wiki', 'URL' =>
                                        # This should be mOldPage, but it may not be set, see below.
                        $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
                        $out->setArticleFlag( true );
  
 +                      // NOTE: only needed for B/C: custom rendering of JS/CSS via hook
                        if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
                                // Stolen from Article::view --AG 2007-10-11
                                // Give hooks a chance to customise the output
                                // @TODO: standardize this crap into one function
 -                              if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
 -                                      // Wrap the whole lot in a <pre> and don't parse
 -                                      $m = array();
 -                                      preg_match( '!\.(css|js)$!u', $this->mNewPage->getText(), $m );
 -                                      $out->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
 -                                      $out->addHTML( htmlspecialchars( $this->mNewtext ) );
 -                                      $out->addHTML( "\n</pre>\n" );
 +                              if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
 +                                      // NOTE: deprecated hook, B/C only
 +                                      // use the content object's own rendering
 +                                      $po = $this->mContentObject->getParserOutput();
 +                                      $out->addHTML( $po->getText() );
                                }
 -                      } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
 +                      } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
 +                              // Handled by extension
 +                      } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
 +                              // NOTE: deprecated hook, B/C only
                                // Handled by extension
                        } else {
                                // Normal page
                                        $wikiPage = WikiPage::factory( $this->mNewPage );
                                }
  
 -                              $parserOptions = ParserOptions::newFromContext( $this->getContext() );
 -                              $parserOptions->enableLimitReport();
 -                              $parserOptions->setTidy( true );
 -
 -                              if ( !$this->mNewRev->isCurrent() ) {
 -                                      $parserOptions->setEditSection( false );
 -                              }
 -
 -                              $parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
 +                              $parserOutput = $this->getParserOutput( $wikiPage, $this->mNewRev );
  
                                # WikiPage::getParserOutput() should not return false, but just in case
                                if( $parserOutput ) {
                wfProfileOut( __METHOD__ );
        }
  
 +      protected function getParserOutput( WikiPage $page, Revision $rev ) {
 +              $parserOptions = ParserOptions::newFromContext( $this->getContext() );
 +              $parserOptions->enableLimitReport();
 +              $parserOptions->setTidy( true );
 +
 +              if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( "edit" ) ) {
 +                      $parserOptions->setEditSection( false );
 +              }
 +
 +              $parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
 +              return $parserOutput;
 +      }
 +
        /**
         * Get the diff text, send it to the OutputPage object
         * Returns false if the diff could not be generated, otherwise returns true
                        return false;
                }
  
 -              $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
 +              $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
  
                // Save to cache for 7 days
                if ( !wfRunHooks( 'AbortDiffCache', array( &$this ) ) ) {
                }
        }
  
 +      /**
 +       * Generate a diff, no caching.
 +       *
 +       * Subclasses may override this to provide a
 +       *
 +       * @param $old Content: old content
 +       * @param $new Content: new content
 +       *
 +       * @since 1.WD
 +       */
 +      function generateContentDiffBody( Content $old, Content $new ) {
 +              if ( !( $old instanceof TextContent ) ) {
 +                      throw new MWException( "Diff not implemented for " . get_class( $old ) . "; "
 +                                                              . "override generateContentDiffBody to fix this." );
 +              }
 +
 +              if ( !( $new instanceof TextContent ) ) {
 +                      throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
 +                              . "override generateContentDiffBody to fix this." );
 +              }
 +
 +              $otext = $old->serialize();
 +              $ntext = $new->serialize();
 +
 +              return $this->generateTextDiffBody( $otext, $ntext );
 +      }
 +
        /**
         * Generate a diff, no caching
         *
         * @param $otext String: old text, must be already segmented
         * @param $ntext String: new text, must be already segmented
 -       * @return bool|string
 +       * @deprecated since 1.WD, use generateContentDiffBody() instead!
         */
        function generateDiffBody( $otext, $ntext ) {
 +              wfDeprecated( __METHOD__, "1.WD" );
 +
 +              return $this->generateTextDiffBody( $otext, $ntext );
 +      }
 +
 +      /**
 +       * Generate a diff, no caching
 +       *
 +       * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
 +       *
 +       * @param $otext String: old text, must be already segmented
 +       * @param $ntext String: new text, must be already segmented
 +       * @return bool|string
 +       */
 +      function generateTextDiffBody( $otext, $ntext ) {
                global $wgExternalDiffEngine, $wgContLang;
  
                wfProfileIn( __METHOD__ );
         *        the visibility of the revision and a link to edit the page.
         * @return String HTML fragment
         */
 -      private function getRevisionHeader( Revision $rev, $complete = '' ) {
 +      protected function getRevisionHeader( Revision $rev, $complete = '' ) {
                $lang = $this->getLanguage();
                $user = $this->getUser();
                $revtimestamp = $rev->getTimestamp();
  
                if ( !$diff && !$otitle ) {
                        $header .= "
-                       <tr valign='top'>
+                       <tr style='vertical-align: top;'>
                        <td class='diff-ntitle'>{$ntitle}</td>
                        </tr>";
                        $multiColspan = 1;
                                $multiColspan = 2;
                        }
                        $header .= "
-                       <tr valign='top'>
+                       <tr style='vertical-align: top;'>
                        <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
                        <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
                        </tr>";
                }
  
                if ( $multi != '' ) {
-                       $header .= "<tr><td colspan='{$multiColspan}' align='center' class='diff-multi'>{$multi}</td></tr>";
+                       $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;' class='diff-multi'>{$multi}</td></tr>";
                }
                if ( $notice != '' ) {
-                       $header .= "<tr><td colspan='{$multiColspan}' align='center'>{$notice}</td></tr>";
+                       $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;'>{$notice}</td></tr>";
                }
  
                return $header . $diff . "</table>";
  
        /**
         * Use specified text instead of loading from the database
 +       * @deprecated since 1.WD, use setContent() instead.
         */
        function setText( $oldText, $newText ) {
 -              $this->mOldtext = $oldText;
 -              $this->mNewtext = $newText;
 +              wfDeprecated( __METHOD__, "1.WD" );
 +
 +              $oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
 +              $newContent = ContentHandler::makeContent( $newText, $this->getTitle() );
 +
 +              $this->setContent( $oldContent, $newContent );
 +      }
 +
 +      /**
 +       * Use specified text instead of loading from the database
 +       * @since 1.WD
 +       */
 +      function setContent( Content $oldContent, Content $newContent ) {
 +              $this->mOldContent = $oldContent;
 +              $this->mNewContent = $newContent;
 +
                $this->mTextLoaded = 2;
                $this->mRevisionsLoaded = true;
        }
                        return false;
                }
                if ( $this->mOldRev ) {
 -                      $this->mOldtext = $this->mOldRev->getText( Revision::FOR_THIS_USER );
 -                      if ( $this->mOldtext === false ) {
 +                      $this->mOldContent = $this->mOldRev->getContent( Revision::FOR_THIS_USER );
 +                      if ( $this->mOldContent === false ) {
                                return false;
                        }
                }
                if ( $this->mNewRev ) {
 -                      $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
 -                      if ( $this->mNewtext === false ) {
 +                      $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER );
 +                      if ( $this->mNewContent === false ) {
                                return false;
                        }
                }
                if ( !$this->loadRevisionData() ) {
                        return false;
                }
 -              $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER );
 +              $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER );
                return true;
        }
  }
@@@ -55,7 -55,7 +55,7 @@@ class RefreshLinksJob extends Job 
                        wfGetLB()->waitFor( $this->params['masterPos'] );
                }
  
-               $revision = Revision::newFromTitle( $this->title, 0, Revision::READ_NORMAL );
+               $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
                if ( !$revision ) {
                        $this->error = 'refreshLinks: Article not found "' .
                                $this->title->getPrefixedDBkey() . '"';
        }
  
        public static function runForTitleInternal( Title $title, Revision $revision, $fname ) {
 -              global $wgParser, $wgContLang;
 +              global $wgContLang;
  
                wfProfileIn( $fname . '-parse' );
                $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
 -              $parserOutput = $wgParser->parse(
 -                      $revision->getText(), $title, $options, true, true, $revision->getId() );
 +              $content = $revision->getContent();
 +              $parserOutput = $content->getParserOutput( $title, $revision->getId(), $options, false );
                wfProfileOut( $fname . '-parse' );
  
                wfProfileIn( $fname . '-update' );
 -              $updates = $parserOutput->getSecondaryDataUpdates( $title, false );
 +              $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput  );
                DataUpdate::runUpdates( $updates );
                wfProfileOut( $fname . '-update' );
        }
@@@ -185,7 -185,7 +185,7 @@@ class RefreshLinksJob2 extends Job 
                        }
                        # Re-parse each page that transcludes this page and update their tracking links...
                        foreach ( $titles as $title ) {
-                               $revision = Revision::newFromTitle( $title, 0, Revision::READ_NORMAL );
+                               $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
                                if ( !$revision ) {
                                        $this->error = 'refreshLinks: Article not found "' .
                                                $title->getPrefixedDBkey() . '"';
@@@ -1946,7 -1946,6 +1946,7 @@@ class Parser 
                                # Interwikis
                                wfProfileIn( __METHOD__."-interwiki" );
                                if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
 +                                      // FIXME: the above check prevents links to sites with identifiers that are not language codes
                                        $this->mOutput->addLanguageLink( $nt->getFullText() );
                                        $s = rtrim( $s . $prefix );
                                        $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
                        # Get the revision
                        $rev = $id
                                ? Revision::newFromId( $id )
-                               : Revision::newFromTitle( $title, 0, Revision::READ_NORMAL );
+                               : Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
                        $rev_id = $rev ? $rev->getId() : 0;
                        # If there is no current revision, there is no page
                        if ( $id === false && !$rev ) {
                        }
  
                        if ( $rev ) {
 -                              $text = $rev->getText();
 +                              $content = $rev->getContent();
 +                              $text = $content->getWikitextForTransclusion();
 +
 +                              if ( $text === false || $text === null ) {
 +                                      $text = false;
 +                                      break;
 +                              }
                        } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
                                global $wgContLang;
                                $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
                                        $text = false;
                                        break;
                                }
 +                              $content = $message->content();
                                $text = $message->plain();
                        } else {
                                break;
                        }
 -                      if ( $text === false ) {
 +                      if ( !$content ) {
                                break;
                        }
                        # Redirect?
                        $finalTitle = $title;
 -                      $title = Title::newFromRedirect( $text );
 +                      $title = $content->getRedirectTarget();
                }
                return array(
                        'text' => $text,
@@@ -32,16 -32,7 +32,16 @@@ class PageArchive 
         * @var Title
         */
        protected $title;
 -      var $fileStatus;
 +
 +      /**
 +       * @var Status
 +       */
 +      protected $fileStatus;
 +
 +      /**
 +       * @var Status
 +       */
 +      protected $revisionStatus;
  
        function __construct( $title ) {
                if( is_null( $title ) ) {
         * @return ResultWrapper
         */
        function listRevisions() {
 +              global $wgContentHandlerNoDB;
 +
                $dbr = wfGetDB( DB_SLAVE );
 +
 +              $fields = array(
 +                      'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
 +                      'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
 +              );
 +
 +              if ( !$wgContentHandlerNoDB ) {
 +                      $fields[] = 'ar_content_format';
 +                      $fields[] = 'ar_content_model';
 +              }
 +
                $res = $dbr->select( 'archive',
 -                      array(
 -                              'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
 -                              'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
 -                      ),
 +                      $fields,
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                   'ar_title' => $this->title->getDBkey() ),
                        __METHOD__,
         * @return Revision
         */
        function getRevision( $timestamp ) {
 +              global $wgContentHandlerNoDB;
 +
                $dbr = wfGetDB( DB_SLAVE );
 +
 +              $fields = array(
 +                      'ar_rev_id',
 +                      'ar_text',
 +                      'ar_comment',
 +                      'ar_user',
 +                      'ar_user_text',
 +                      'ar_timestamp',
 +                      'ar_minor_edit',
 +                      'ar_flags',
 +                      'ar_text_id',
 +                      'ar_deleted',
 +                      'ar_len',
 +                      'ar_sha1',
 +              );
 +
 +              if ( !$wgContentHandlerNoDB ) {
 +                      $fields[] = 'ar_content_format';
 +                      $fields[] = 'ar_content_model';
 +              }
 +
                $row = $dbr->selectRow( 'archive',
 -                      array(
 -                              'ar_rev_id',
 -                              'ar_text',
 -                              'ar_comment',
 -                              'ar_user',
 -                              'ar_user_text',
 -                              'ar_timestamp',
 -                              'ar_minor_edit',
 -                              'ar_flags',
 -                              'ar_text_id',
 -                              'ar_deleted',
 -                              'ar_len',
 -                              'ar_sha1',
 -                      ),
 +                      $fields,
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                        'ar_title' => $this->title->getDBkey(),
                                        'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
                if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
                        $img = wfLocalFile( $this->title );
                        $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
 -                      if ( !$this->fileStatus->isOk() ) {
 +                      if ( !$this->fileStatus->isOK() ) {
                                return false;
                        }
                        $filesRestored = $this->fileStatus->successCount;
                }
  
                if( $restoreText ) {
 -                      $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
 -                      if( $textRestored === false ) { // It must be one of UNDELETE_*
 +                      $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
 +                      if( !$this->revisionStatus->isOK() ) {
                                return false;
                        }
 +
 +                      $textRestored = $this->revisionStatus->getValue();
                } else {
                        $textRestored = 0;
                }
         * @param $comment String
         * @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs
         *
 -       * @return Mixed: number of revisions restored or false on failure
 +       * @return Status, containing the number of revisions restored on success
         */
        private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
 +              global $wgContentHandlerNoDB;
 +
                if ( wfReadOnly() ) {
 -                      return false;
 +                      throw new ReadOnlyError();
                }
                $restoreAll = empty( $timestamps );
  
                        $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
                                array( 'rev_id' => $previousRevId ),
                                __METHOD__ );
 +
                        if( $previousTimestamp === false ) {
                                wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
 -                              return 0;
 +
 +                              $status = Status::newGood( 0 );
 +                              $status->warning( 'undeleterevision-missing' );
 +
 +                              return $status;
                        }
                } else {
                        # Have to create a new article...
                        $oldones = "ar_timestamp IN ( {$oldts} )";
                }
  
 +              $fields = array(
 +                      'ar_rev_id',
 +                      'ar_text',
 +                      'ar_comment',
 +                      'ar_user',
 +                      'ar_user_text',
 +                      'ar_timestamp',
 +                      'ar_minor_edit',
 +                      'ar_flags',
 +                      'ar_text_id',
 +                      'ar_deleted',
 +                      'ar_page_id',
 +                      'ar_len',
 +                      'ar_sha1');
 +
 +              if ( !$wgContentHandlerNoDB ) {
 +                      $fields[] = 'ar_content_format';
 +                      $fields[] = 'ar_content_model';
 +              }
 +
                /**
                 * Select each archived revision...
                 */
                $result = $dbw->select( 'archive',
 -                      /* fields */ array(
 -                              'ar_rev_id',
 -                              'ar_text',
 -                              'ar_comment',
 -                              'ar_user',
 -                              'ar_user_text',
 -                              'ar_timestamp',
 -                              'ar_minor_edit',
 -                              'ar_flags',
 -                              'ar_text_id',
 -                              'ar_deleted',
 -                              'ar_page_id',
 -                              'ar_len',
 -                              'ar_sha1' ),
 +                      $fields,
                        /* WHERE */ array(
                                'ar_namespace' => $this->title->getNamespace(),
                                'ar_title'     => $this->title->getDBkey(),
                $rev_count = $dbw->numRows( $result );
                if( !$rev_count ) {
                        wfDebug( __METHOD__ . ": no revisions to restore\n" );
 -                      return false; // ???
 +
 +                      $status = Status::newGood( 0 );
 +                      $status->warning( "undelete-no-results" );
 +                      return $status;
                }
  
                $ret->seek( $rev_count - 1 ); // move to last
                $row = $ret->fetchObject(); // get newest archived rev
                $ret->seek( 0 ); // move back
  
 +              // grab the content to check consistency with global state before restoring the page.
 +              $revision = Revision::newFromArchiveRow( $row,
 +                      array(
 +                              'title' => $article->getTitle(), // used to derive default content model
 +                      ) );
 +
 +              $m = $revision->getContentModel();
 +
 +              $user = User::newFromName( $revision->getRawUserText(), false );
 +              $content = $revision->getContent( Revision::RAW );
 +
 +              //NOTE: article ID may not be known yet. prepareSave() should not modify the database.
 +              $status = $content->prepareSave( $article, 0, -1, $user );
 +
 +              if ( !$status->isOK() ) {
 +                      return $status;
 +              }
 +
                if( $makepage ) {
                        // Check the state of the newest to-be version...
                        if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
 -                              return false; // we can't leave the current revision like this!
 +                              return Status::newFatal( "undeleterevdel" );
                        }
                        // Safe to insert now...
                        $newid  = $article->insertOn( $dbw );
                        if( $row->ar_timestamp > $previousTimestamp ) {
                                // Check the state of the newest to-be version...
                                if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
 -                                      return false; // we can't leave the current revision like this!
 +                                      return Status::newFatal( "undeleterevdel" );
                                }
                        }
                }
  
                // Was anything restored at all?
                if ( $restored == 0 ) {
 -                      return 0;
 +                      return Status::newGood( 0 );
                }
  
                $created = (bool)$newid;
                        $update->doUpdate();
                }
  
 -              return $restored;
 +              return Status::newGood( $restored );
        }
  
        /**
         * @return Status
         */
        function getFileStatus() { return $this->fileStatus; }
 +
 +      /**
 +       * @return Status
 +       */
 +      function getRevisionStatus() { return $this->revisionStatus; }
  }
  
  /**
@@@ -926,15 -855,12 +926,15 @@@ class SpecialUndelete extends SpecialPa
  
                if( $this->mPreview ) {
                        // Hide [edit]s
 +                      //FIXME: ContentHandler will have to provide some specialized magic to do this
                        $popts = $out->parserOptions();
                        $popts->setEditSection( false );
                        $out->parserOptions( $popts );
                        $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER, $user ), $this->mTargetObj, true );
                }
  
 +              //FIXME: ContentHandler will have to provide some specialized magic for reviewing content before undeletion
 +
                $out->addHTML(
                        Xml::element( 'textarea', array(
                                        'readonly' => 'readonly',
         * @return String: HTML
         */
        function showDiff( $previousRev, $currentRev ) {
 -              $diffEngine = new DifferenceEngine( $this->getContext() );
 +              $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $this->getContext() );
                $diffEngine->showDiffStyle();
                $this->getOutput()->addHTML(
                        "<div>" .
                        "<col class='diff-marker' />" .
                        "<col class='diff-content' />" .
                        "<tr>" .
-                               "<td colspan='2' width='50%' align='center' class='diff-otitle'>" .
+                               "<td colspan='2' width='50%' style='text-align: center' class='diff-otitle'>" .
                                $this->diffHeader( $previousRev, 'o' ) .
                                "</td>\n" .
-                               "<td colspan='2' width='50%' align='center' class='diff-ntitle'>" .
+                               "<td colspan='2' width='50%' style='text-align: center' class='diff-ntitle'>" .
                                $this->diffHeader( $currentRev, 'n' ) .
                                "</td>\n" .
                        "</tr>" .
 -                      $diffEngine->generateDiffBody(
 -                              $previousRev->getText( Revision::FOR_THIS_USER, $this->getUser() ),
 -                              $currentRev->getText( Revision::FOR_THIS_USER, $this->getUser() ) ) .
 +                      $diffEngine->generateContentDiffBody(
 +                              $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
 +                              $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) ) .
                        "</table>" .
                        "</div>\n"
                );
                        $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
                } else {
                        $out->setPageTitle( $this->msg( 'undelete-error' ) );
 -                      $out->addWikiMsg( 'cannotundelete' );
 -                      $out->addWikiMsg( 'undeleterevdel' );
                }
  
 -              // Show file deletion warnings and errors
 +              // Show revision undeletion warnings and errors
 +              $status = $archive->getRevisionStatus();
 +              if( $status && !$status->isGood() ) {
 +                      $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' );
 +              }
 +
 +              // Show file undeletion warnings and errors
                $status = $archive->getFileStatus();
                if( $status && !$status->isGood() ) {
                        $out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
@@@ -895,7 -895,6 +895,7 @@@ $1'
  'portal-url'           => 'Project:Community portal',
  'privacy'              => 'Privacy policy',
  'privacypage'          => 'Project:Privacy policy',
 +'content-failed-to-parse' => "Failed to parse $2 content for $1 model: $3",
  
  'badaccess'        => 'Permission error',
  'badaccess-group0' => 'You are not allowed to execute the action you have requested.',
@@@ -1430,12 -1429,13 +1430,13 @@@ If you save it, any changes made since 
  'yourdiff'                         => 'Differences',
  'copyrightwarning'                 => "Please note that all contributions to {{SITENAME}} are considered to be released under the $2 (see $1 for details).
  If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.<br />
 -You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
 +You are also promising us that you wrote this yourself, or copied editpageit from a public domain or similar free resource.
  '''Do not submit copyrighted work without permission!'''",
  'copyrightwarning2'                => "Please note that all contributions to {{SITENAME}} may be edited, altered, or removed by other contributors.
  If you do not want your writing to be edited mercilessly, then do not submit it here.<br />
  You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see $1 for details).
  '''Do not submit copyrighted work without permission!'''",
+ 'editpage-head-copy-warn'          => '-', # do not translate or duplicate this message to other languages
  'editpage-tos-summary'             => '-', # do not translate or duplicate this message to other languages
  'longpage-hint'                    => '-', # do not translate or duplicate this message to other languages
  'longpageerror'                    => "'''Error: The text you have submitted is {{PLURAL:$1|one kilobyte|$1 kilobytes}} long, which is longer than the maximum of {{PLURAL:$2|one kilobyte|$2 kilobytes}}.'''
@@@ -1486,7 -1486,6 +1487,7 @@@ It already exists.'
  'addsection-preload'               => '', # do not translate or duplicate this message to other languages
  'addsection-editintro'             => '', # do not translate or duplicate this message to other languages
  'defaultmessagetext'               => 'Default message text',
 +'invalid-content-data'             => 'Invalid content data',
  
  # Parser/template warnings
  'expensive-parserfunction-warning'        => "'''Warning:''' This page contains too many expensive parser function calls.
@@@ -3069,8 -3068,8 +3070,8 @@@ You may have a bad link, or the revisio
  'undeletedrevisions'           => '{{PLURAL:$1|1 revision|$1 revisions}} restored',
  'undeletedrevisions-files'     => '{{PLURAL:$1|1 revision|$1 revisions}} and {{PLURAL:$2|1 file|$2 files}} restored',
  'undeletedfiles'               => '{{PLURAL:$1|1 file|$1 files}} restored',
 -'cannotundelete'               => 'Undelete failed;
 -someone else may have undeleted the page first.',
 +'cannotundelete'               => 'Undelete failed:
 +$1',
  'undeletedpage'                => "'''$1 has been restored'''
  
  Consult the [[Special:Log/delete|deletion log]] for a record of recent deletions and restorations.",
@@@ -4941,10 -4940,4 +4942,10 @@@ Otherwise, you can use the easy form be
  'duration-centuries' => '$1 {{PLURAL:$1|century|centuries}}',
  'duration-millennia' => '$1 {{PLURAL:$1|millennium|millennia}}',
  
 +# Content model IDs for the ContentHandler facility; used by ContentHandler::getContentModel()
 +'content-model-wikitext' => 'wikitext',
 +'content-model-javascript' => 'JavaScript',
 +'content-model-css' => 'CSS',
 +'content-model-text' => 'plain text',
 +
  );
@@@ -1,9 -1,6 +1,9 @@@
  <?php
  
  /**
 + * @group medium
 + * ^---- causes phpunit to use a higher timeout threshold
 + * 
   * @group FileRepo
   * @group FileBackend
   */
@@@ -885,6 -882,20 +885,20 @@@ class FileBackendTest extends MediaWiki
                                "Correct file size of '$path'" );
                        $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 10,
                                "Correct file timestamp of '$path'" );
+                       $this->backend->clearCache( array( $path ) );
+                       $size = $this->backend->getFileSize( array( 'src' => $path ) );
+                       $this->assertEquals( strlen( $content ), $size,
+                               "Correct file size of '$path'" );
+                       $this->backend->preloadCache( array( $path ) );
+                       $size = $this->backend->getFileSize( array( 'src' => $path ) );
+                       $this->assertEquals( strlen( $content ), $size,
+                               "Correct file size of '$path'" );
                } else {
                        $size = $this->backend->getFileSize( array( 'src' => $path ) );
                        $time = $this->backend->getFileTimestamp( array( 'src' => $path ) );