Merge branch 'master' into Wikidata
authorJens Ohlig <jens.ohlig@wikimedia.de>
Wed, 11 Apr 2012 12:24:29 +0000 (14:24 +0200)
committerJens Ohlig <jens.ohlig@wikimedia.de>
Wed, 11 Apr 2012 12:24:29 +0000 (14:24 +0200)
Conflicts:
.gitreview
includes/Article.php
includes/AutoLoader.php
includes/EditPage.php
includes/LinksUpdate.php
includes/WikiPage.php
includes/installer/Ibm_db2Updater.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/SqliteUpdater.php
maintenance/refreshLinks.php

29 files changed:
1  2 
includes/Article.php
includes/AutoLoader.php
includes/DefaultSettings.php
includes/Defines.php
includes/EditPage.php
includes/FeedUtils.php
includes/ImagePage.php
includes/LinksUpdate.php
includes/Revision.php
includes/Title.php
includes/WikiPage.php
includes/actions/RawAction.php
includes/actions/RollbackAction.php
includes/api/ApiComparePages.php
includes/api/ApiDelete.php
includes/api/ApiEditPage.php
includes/api/ApiParse.php
includes/diff/DairikiDiff.php
includes/diff/DifferenceEngine.php
includes/installer/Ibm_db2Updater.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/SqliteUpdater.php
includes/parser/ParserOutput.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/specials/SpecialUndelete.php
languages/messages/MessagesEn.php
maintenance/refreshLinks.php
maintenance/tables.sql

diff --combined includes/Article.php
@@@ -37,13 -37,7 +37,13 @@@ class Article extends Page 
         */
        public $mParserOptions;
  
 -      var $mContent;                    // !<
 +      var $mContent;                    // !< #BC cruft
 +
 +      /**
 +       * @var Content
 +       */
 +      var $mContentObject;
 +
        var $mContentLoaded = false;      // !<
        var $mOldId;                      // !<
  
                if ( !$page ) {
                        switch( $title->getNamespace() ) {
                                case NS_FILE:
 -                                      $page = new ImagePage( $title );
 +                                      $page = new ImagePage( $title ); #FIXME: teach ImagePage to use ContentHandler
                                        break;
                                case NS_CATEGORY:
 -                                      $page = new CategoryPage( $title );
 +                                      $page = new CategoryPage( $title ); #FIXME: teach ImagePage to use ContentHandler
                                        break;
                                default:
 -                                      $page = new Article( $title );
 +                                      $handler = ContentHandler::getForTitle( $title );
 +                                      $page = $handler->createArticle( $title );
                        }
                }
                $page->setContext( $context );
         * This function has side effects! Do not use this function if you
         * only want the real revision text if any.
         *
 -       * @return string Return the text of this revision
 +       * @deprecated in 1.20; use getContentObject() instead
 +       *
 +       * @return string The text of this revision
         */
        public function getContent() {
 +              wfDeprecated( __METHOD__, '1.20' );
 +              $content = $this->getContentObject();
 +              return ContentHandler::getContentText( $content );
 +      }
 +
 +      /**
 +       * Note that getContent/loadContent do not follow redirects anymore.
 +       * If you need to fetch redirectable content easily, try
 +       * the shortcut in WikiPage::getRedirectTarget()
 +       *
 +       * This function has side effects! Do not use this function if you
 +       * only want the real revision text if any.
 +       *
 +       * @return Content
 +       */
 +   public function getContentObject() {
                global $wgUser;
  
                wfProfileIn( __METHOD__ );
                                if ( $text === false ) {
                                        $text = '';
                                }
 +
 +                              $content = ContentHandler::makeContent( $text, $this->getTitle() );
                        } else {
 -                              $text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' );
 +                              $content = new MessageContent( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', null, 'parsemag' );
                        }
                        wfProfileOut( __METHOD__ );
  
 -                      return $text;
 +                      return $content;
                } else {
 -                      $this->fetchContent();
 +                      $this->fetchContentObject();
                        wfProfileOut( __METHOD__ );
  
 -                      return $this->mContent;
 +                      return $this->mContentObject;
                }
        }
  
                if ( $oldid !== 0 ) {
                        # Load the given revision and check whether the page is another one.
                        # In that case, update this instance to reflect the change.
-                       $this->mRevision = Revision::newFromId( $oldid );
-                       if ( $this->mRevision !== null ) {
-                               // Revision title doesn't match the page title given?
-                               if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
-                                       $function = array( get_class( $this->mPage ), 'newFromID' );
-                                       $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
+                       if ( $oldid === $this->mPage->getLatest() ) {
+                               $this->mRevision = $this->mPage->getRevision();
+                       } else {
+                               $this->mRevision = Revision::newFromId( $oldid );
+                               if ( $this->mRevision !== null ) {
+                                       // Revision title doesn't match the page title given?
+                                       if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
+                                               $function = array( get_class( $this->mPage ), 'newFromID' );
+                                               $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
+                                       }
                                }
                        }
                }
         * Does *NOT* follow redirects.
         *
         * @return mixed string containing article contents, or false if null
 +       * @deprecated in 1.20, use getContentObject() instead
         */
 -      function fetchContent() {
 -              if ( $this->mContentLoaded ) {
 +      protected function fetchContent() { #BC cruft!
 +              wfDeprecated( __METHOD__, '1.20' );
 +
 +              if ( $this->mContentLoaded && $this->mContent ) {
                        return $this->mContent;
                }
  
                wfProfileIn( __METHOD__ );
  
 +              $content = $this->fetchContentObject();
 +
 +              $this->mContent = ContentHandler::getContentText( $content ); #FIXME: get rid of mContent everywhere!
 +              wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); #BC cruft!
 +
 +              wfProfileOut( __METHOD__ );
 +
 +              return $this->mContent;
 +      }
 +
 +
 +      /**
 +       * Get text content object
 +       * Does *NOT* follow redirects.
 +       * TODO: when is this null?
 +       *
 +       * @return Content|null
 +       */
 +      protected function fetchContentObject() {
 +              if ( $this->mContentLoaded ) {
 +                      return $this->mContentObject;
 +              }
 +
 +              wfProfileIn( __METHOD__ );
 +
                $this->mContentLoaded = true;
 +              $this->mContent = null;
  
                $oldid = $this->getOldID();
  
                # fails we'll have something telling us what we intended.
                $t = $this->getTitle()->getPrefixedText();
                $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
 -              $this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
 +              $this->mContentObject = new MessageContent( 'missing-article', array($t, $d), array() ) ;
  
                if ( $oldid ) {
                        # $this->mRevision might already be fetched by getOldIDFromRequest()
                        }
  
                        $this->mRevision = $this->mPage->getRevision();
 +
                        if ( !$this->mRevision ) {
                                wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
                                wfProfileOut( __METHOD__ );
  
                // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
                // We should instead work with the Revision object when we need it...
 -              $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
 +              $this->mContentObject = $this->mRevision->getContent( Revision::FOR_THIS_USER ); // Loads if user is allowed
                $this->mRevIdFetched = $this->mRevision->getId();
  
 -              wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
 +              wfRunHooks( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) ); #FIXME: register new hook
  
                wfProfileOut( __METHOD__ );
  
 -              return $this->mContent;
 +              return $this->mContentObject;
        }
  
        /**
         * @return Revision|null
         */
        public function getRevisionFetched() {
 -              $this->fetchContent();
 +              $this->fetchContentObject();
  
                return $this->mRevision;
        }
                if ( $wgOut->isPrintable() ) {
                        $parserOptions->setIsPrintable( true );
                        $parserOptions->setEditSection( false );
-               } elseif ( !$this->getTitle()->quickUserCan( 'edit' ) ) {
+               } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
                        $parserOptions->setEditSection( false );
                }
  
                                        break;
                                case 3:
                                        # This will set $this->mRevision if needed
 -                                      $this->fetchContent();
 +                                      $this->fetchContentObject();
  
                                        # Are we looking at an old revision
                                        if ( $oldid && $this->mRevision ) {
                                                wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
                                                $this->showCssOrJsPage();
                                                $outputDone = true;
 -                                      } elseif( !wfRunHooks( 'ArticleViewCustom', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
 +                                      } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->fetchContentObject(), $this->getTitle(), $wgOut ) ) ) { #FIXME: document new hook!
 +                                              # Allow extensions do their own custom view for certain pages
 +                                              $outputDone = true;
 +                                      } elseif( Hooks::isRegistered( 'ArticleViewCustom' ) && !wfRunHooks( 'ArticleViewCustom', array( $this->fetchContent(), $this->getTitle(), $wgOut ) ) ) { #FIXME: fetchContent() is deprecated! #FIXME: deprecate hook!
                                                # Allow extensions do their own custom view for certain pages
                                                $outputDone = true;
                                        } else {
 -                                              $text = $this->getContent();
 -                                              $rt = Title::newFromRedirectArray( $text );
 +                                              $content = $this->getContentObject();
 +                                              $rt = $content->getRedirectChain();
                                                if ( $rt ) {
                                                        wfDebug( __METHOD__ . ": showing redirect=no page\n" );
                                                        # Viewing a redirect page (e.g. with parameter redirect=no)
                                                        $wgOut->addHTML( $this->viewRedirect( $rt ) );
                                                        # Parse just to get categories, displaytitle, etc.
 -                                                      $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
 +                                                      $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions );
                                                        $wgOut->addParserOutputNoText( $this->mParserOutput );
                                                        $outputDone = true;
                                                }
                                        wfDebug( __METHOD__ . ": doing uncached parse\n" );
  
                                        $poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
 -                                              $this->getRevIdFetched(), $useParserCache, $this->getContent() );
 +                                              $this->getRevIdFetched(), $useParserCache, $this->getContentObject() );
  
                                        if ( !$poolArticleView->execute() ) {
                                                $error = $poolArticleView->getError();
                $unhide = $wgRequest->getInt( 'unhide' ) == 1;
                $oldid = $this->getOldID();
  
 -              $de = new DifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
 +              $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
 +              $de = $contentHandler->getDifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
 +
                // DifferenceEngine directly fetched the revision:
                $this->mRevIdFetched = $de->mNewid;
                $de->showDiffPage( $diffOnly );
         * This is hooked by SyntaxHighlight_GeSHi to do syntax highlighting of these
         * page views.
         */
 -      protected function showCssOrJsPage() {
 +      protected function showCssOrJsPage( $showCacheHint = true ) {
                global $wgOut;
  
 -              $dir = $this->getContext()->getLanguage()->getDir();
 -              $lang = $this->getContext()->getLanguage()->getCode();
 +              if ( $showCacheHint ) {
 +                      $dir = $this->getContext()->getLanguage()->getDir();
 +                      $lang = $this->getContext()->getLanguage()->getCode();
  
 -              $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
 -                      'clearyourcache' );
 +                      $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
 +                              'clearyourcache' );
 +              }
  
                // Give hooks a chance to customise the output
 -              if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
 -                      // Wrap the whole lot in a <pre> and don't parse
 -                      $m = array();
 -                      preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
 -                      $wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
 -                      $wgOut->addHTML( htmlspecialchars( $this->mContent ) );
 -                      $wgOut->addHTML( "\n</pre>\n" );
 +              if ( !Hooks::isRegistered('ShowRawCssJs') || wfRunHooks( 'ShowRawCssJs', array( $this->fetchContent(), $this->getTitle(), $wgOut ) ) ) { #FIXME: fetchContent() is deprecated #FIXME: hook is deprecated
 +                      $po = $this->mContentObject->getParserOutput();
 +                      $wgOut->addHTML( $po->getText() );
                }
        }
  
                                'msgKey' => array( 'moveddeleted-notice' ) )
                );
  
+               if ( !$this->mPage->hasViewableContent() && $wgSend404Code ) {
+                       // If there's no backing content, send a 404 Not Found
+                       // for better machine handling of broken links.
+                       $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
+               }
+               $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
+               if ( ! $hookResult ) {
+                       return;
+               }
                # Show error message
                $oldid = $this->getOldID();
                if ( $oldid ) {
                }
                $text = "<div class='noarticletext'>\n$text\n</div>";
  
-               if ( !$this->mPage->hasViewableContent() && $wgSend404Code ) {
-                       // If there's no backing content, send a 404 Not Found
-                       // for better machine handling of broken links.
-                       $wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
-               }
                $wgOut->addWikiText( $text );
        }
  
  
                # Cascade unhide param in links for easy deletion browsing
                $extraParams = array();
-               if ( $wgRequest->getVal( 'unhide' ) ) {
+               if ( $unhide ) {
                        $extraParams['unhide'] = 1;
                }
  
-               $revision = Revision::newFromId( $oldid );
+               if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
+                       $revision = $this->mRevision;
+               } else {
+                       $revision = Revision::newFromId( $oldid );
+               }
                $timestamp = $revision->getTimestamp();
  
                $current = ( $oldid == $this->mPage->getLatest() );
                // Generate deletion reason
                $hasHistory = false;
                if ( !$reason ) {
 -                      $reason = $this->generateReason( $hasHistory );
 +                      try {
 +                              $reason = $this->generateReason( $hasHistory );
 +                      } catch (MWException $e) {
 +                              # if a page is horribly broken, we still want to be able to delete it. so be lenient about errors here.
 +                              wfDebug("Error while building auto delete summary: $e");
 +                              $reason = '';
 +                      }
                }
  
                // If the page has a history, insert a warning
  
        /**
         * Get parser options suitable for rendering the primary article wikitext
-        * @return ParserOptions|false
+        * @return ParserOptions
         */
        public function getParserOptions() {
                global $wgUser;
         *
         * @param $fname String Name of called method
         * @param $args Array Arguments to the method
+        * @return mixed
         */
        public function __call( $fname, $args ) {
                if ( is_callable( array( $this->mPage, $fname ) ) ) {
         * @return mixed
         */
        public function generateReason( &$hasHistory ) {
 -              return $this->mPage->getAutoDeleteReason( $hasHistory );
 +              $title = $this->mPage->getTitle();
 +              $handler = ContentHandler::getForTitle( $title );
 +              return $handler->getAutoDeleteReason( $title, $hasHistory );
        }
  
        // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
         * @param $newtext
         * @param $flags
         * @return string
 +       * @deprecated since 1.20, use ContentHandler::getAutosummary() instead
         */
        public static function getAutosummary( $oldtext, $newtext, $flags ) {
                return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
diff --combined includes/AutoLoader.php
@@@ -49,9 -49,13 +49,13 @@@ $wgAutoloadLocalClasses = array
        'ConfEditorToken' => 'includes/ConfEditor.php',
        'Cookie' => 'includes/Cookie.php',
        'CookieJar' => 'includes/Cookie.php',
+       'MWCryptRand' => 'includes/CryptRand.php',
        'CurlHttpRequest' => 'includes/HttpFunctions.php',
+ //    'DBDataObject' => 'includes/DBDataObject.php',
+ //    'DBTable' => 'includes/DBTable.php',
        'DeferrableUpdate' => 'includes/DeferredUpdates.php',
        'DeferredUpdates' => 'includes/DeferredUpdates.php',
+       'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php',
        'DerivativeRequest' => 'includes/WebRequest.php',
        'DiffHistoryBlob' => 'includes/HistoryBlob.php',
  
@@@ -91,6 -95,7 +95,7 @@@
        'FormAction' => 'includes/Action.php',
        'FormOptions' => 'includes/FormOptions.php',
        'FormSpecialPage' => 'includes/SpecialPage.php',
+       'GitInfo' => 'includes/GitInfo.php',
        'HashtableReplacer' => 'includes/StringUtils.php',
        'HistoryBlob' => 'includes/HistoryBlob.php',
        'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
        'Pager' => 'includes/Pager.php',
        'PasswordError' => 'includes/User.php',
        'PathRouter' => 'includes/PathRouter.php',
+       'PathRouterPatternReplacer' => 'includes/PathRouter.php',
        'PermissionsError' => 'includes/Exception.php',
        'PhpHttpRequest' => 'includes/HttpFunctions.php',
        'PoolCounter' => 'includes/PoolCounter.php',
        'RevisionList' => 'includes/RevisionList.php',
        'RSSFeed' => 'includes/Feed.php',
        'Sanitizer' => 'includes/Sanitizer.php',
 +    'SecondaryDataUpdate' => 'includes/SecondaryDataUpdate.php',
 +    'SecondaryDBDataUpdate' => 'includes/SecondaryDBDataUpdate.php',
+       'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php',
        'SiteConfiguration' => 'includes/SiteConfiguration.php',
        'SiteStats' => 'includes/SiteStats.php',
        'SiteStatsInit' => 'includes/SiteStats.php',
        'ZhClient' => 'includes/ZhClient.php',
        'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
  
 +    # content handler
 +    'Content' => 'includes/Content.php',
 +    'ContentHandler' => 'includes/ContentHandler.php',
 +    'CssContent' => 'includes/Content.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
        'CreditsAction' => 'includes/actions/CreditsAction.php',
        'DeleteAction' => 'includes/actions/DeleteAction.php',
        'ApiResult' => 'includes/api/ApiResult.php',
        'ApiRollback' => 'includes/api/ApiRollback.php',
        'ApiRsd' => 'includes/api/ApiRsd.php',
+       'ApiTokens' => 'includes/api/ApiTokens.php',
        'ApiUnblock' => 'includes/api/ApiUnblock.php',
        'ApiUndelete' => 'includes/api/ApiUndelete.php',
        'ApiUpload' => 'includes/api/ApiUpload.php',
        # includes/filerepo/backend
        'FileBackendGroup' => 'includes/filerepo/backend/FileBackendGroup.php',
        'FileBackend' => 'includes/filerepo/backend/FileBackend.php',
-       'FileBackendStore' => 'includes/filerepo/backend/FileBackend.php',
+       'FileBackendStore' => 'includes/filerepo/backend/FileBackendStore.php',
+       'FileBackendStoreShardListIterator' => 'includes/filerepo/backend/FileBackendStore.php',
        'FileBackendMultiWrite' => 'includes/filerepo/backend/FileBackendMultiWrite.php',
-       'FileBackendStoreShardListIterator' => 'includes/filerepo/backend/FileBackend.php',
        'FSFileBackend' => 'includes/filerepo/backend/FSFileBackend.php',
        'FSFileBackendFileList' => 'includes/filerepo/backend/FSFileBackend.php',
        'SwiftFileBackend' => 'includes/filerepo/backend/SwiftFileBackend.php',
        'SwiftFileBackendFileList' => 'includes/filerepo/backend/SwiftFileBackend.php',
+       'FileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
+       'DBFileJournal' => 'includes/filerepo/backend/filejournal/DBFileJournal.php',
+       'NullFileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
        'LockManagerGroup' => 'includes/filerepo/backend/lockmanager/LockManagerGroup.php',
        'LockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
        'ScopedLock' => 'includes/filerepo/backend/lockmanager/LockManager.php',
        'MySqlLockManager'=> 'includes/filerepo/backend/lockmanager/DBLockManager.php',
        'NullLockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
        'FileOp' => 'includes/filerepo/backend/FileOp.php',
-       'FileOpScopedPHPTimeout' => 'includes/filerepo/backend/FileOp.php',
        'StoreFileOp' => 'includes/filerepo/backend/FileOp.php',
        'CopyFileOp' => 'includes/filerepo/backend/FileOp.php',
        'MoveFileOp' => 'includes/filerepo/backend/FileOp.php',
        'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php',
        'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
        'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
+       'ResourceLoaderUserCSSPrefsModule' => 'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
        'ResourceLoaderUserGroupsModule' => 'includes/resourceloader/ResourceLoaderUserGroupsModule.php',
        'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php',
        'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php',
        'ResourceLoaderUserTokensModule' => 'includes/resourceloader/ResourceLoaderUserTokensModule.php',
+       'ResourceLoaderLanguageDataModule' => 'includes/resourceloader/ResourceLoaderLanguageDataModule.php',
        'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php',
  
        # includes/revisiondelete
@@@ -33,7 -33,7 +33,7 @@@ $wgConf = new SiteConfiguration
  /** @endcond */
  
  /** MediaWiki version number */
- $wgVersion = '1.19alpha';
+ $wgVersion = '1.20alpha';
  
  /** Name of the site. It must be changed in LocalSettings.php */
  $wgSitename = 'MediaWiki';
@@@ -453,6 -453,10 +453,10 @@@ $wgAllowCopyUploads = false
   * This feature is experimental and broken as of r81612.
   */
  $wgAllowAsyncCopyUploads = false;
+ /**
+  * A list of domains copy uploads can come from
+  */
+ $wgCopyUploadsDomains = array();
  
  /**
   * Max size for uploads, in bytes. If not set to an array, applies to all
@@@ -636,17 -640,6 +640,17 @@@ $wgMediaHandlers = array
        'image/x-djvu' => 'DjVuHandler', // compat
  );
  
 +/**
 + * Plugins for page content model handling.
 + * Each entry in the array maps a model name type to a class name
 + */
 +$wgContentHandlers = array(
 +    CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', // the usual case
 +    CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // dumb version, no syntax highlighting
 +    CONTENT_MODEL_CSS => 'CssContentHandler', // dumb version, no syntax highlighting
 +    CONTENT_MODEL_TEXT => 'TextContentHandler', // dumb plain text in <pre>
 +);
 +
  /**
   * Resizing can be done using PHP's internal image libraries or using
   * ImageMagick or another third-party converter, e.g. GraphicMagick.
@@@ -1562,12 -1555,21 +1566,21 @@@ $wgMessageCacheType = CACHE_ANYTHING
   */
  $wgParserCacheType = CACHE_ANYTHING;
  
+ /**
+  * The cache type for storing language conversion tables,
+  * which are used when parsing certain text and interface messages.
+  *
+  * For available types see $wgMainCacheType.
+  */
+ $wgLanguageConverterCacheType = CACHE_ANYTHING;
  /**
   * Advanced object cache configuration.
   *
   * Use this to define the class names and constructor parameters which are used
   * for the various cache types. Custom cache types may be defined here and
-  * referenced from $wgMainCacheType, $wgMessageCacheType or $wgParserCacheType.
+  * referenced from $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType,
+  * or $wgLanguageConverterCacheType.
   *
   * The format is an associative array where the key is a cache identifier, and
   * the value is an associative array of parameters. The "class" parameter is the
@@@ -1708,6 -1710,8 +1721,8 @@@ $wgStyleVersion = '303'
   * This will cache static pages for non-logged-in users to reduce
   * database traffic on public sites.
   * Must set $wgShowIPinHeader = false
+  * ResourceLoader requests to default language and skins are cached
+  * as well as single module requests.
   */
  $wgUseFileCache = false;
  
@@@ -1761,8 -1765,6 +1776,6 @@@ $wgSidebarCacheExpiry = 86400
  /**
   * When using the file cache, we can store the cached HTML gzipped to save disk
   * space. Pages will then also be served compressed to clients that support it.
-  * THIS IS NOT COMPATIBLE with ob_gzhandler which is now enabled if supported in
-  * the default LocalSettings.php! If you enable this, remove that setting first.
   *
   * Requires zlib support enabled in PHP.
   */
@@@ -1868,23 -1870,58 +1881,58 @@@ $wgSquidServersNoPurge = array()
  /** Maximum number of titles to purge in any one client operation */
  $wgMaxSquidPurgeTitles = 400;
  
+ /**
+  * Routing configuration for HTCP multicast purging. Add elements here to
+  * enable HTCP and determine which purges are sent where. If set to an empty
+  * array, HTCP is disabled.
+  * 
+  * Each key in this array is a regular expression to match against the purged
+  * URL, or an empty string to match all URLs. The purged URL is matched against
+  * the regexes in the order specified, and the first rule whose regex matches
+  * is used.
+  * 
+  * Example configuration to send purges for upload.wikimedia.org to one
+  * multicast group and all other purges to another:
+  * $wgHTCPMulticastRouting = array(
+  *         '|^https?://upload\.wikimedia\.org|' => array(
+  *                 'host' => '239.128.0.113',
+  *                 'port' => 4827,
+  *         ),
+  *         '' => array(
+  *                 'host' => '239.128.0.112',
+  *                 'port' => 4827,
+  *         ),
+  * );
+  * 
+  * @see $wgHTCPMulticastTTL
+  */
+ $wgHTCPMulticastRouting = array();
  /**
   * HTCP multicast address. Set this to a multicast IP address to enable HTCP.
   *
   * Note that MediaWiki uses the old non-RFC compliant HTCP format, which was
   * present in the earliest Squid implementations of the protocol.
+  * 
+  * This setting is DEPRECATED in favor of $wgHTCPMulticastRouting , and kept
+  * for backwards compatibility only. If $wgHTCPMulticastRouting is set, this
+  * setting is ignored. If $wgHTCPMulticastRouting is not set and this setting
+  * is, it is used to populate $wgHTCPMulticastRouting.
+  * 
+  * @deprecated in favor of $wgHTCPMulticastRouting
   */
  $wgHTCPMulticastAddress = false;
  
  /**
   * HTCP multicast port.
+  * @deprecated in favor of $wgHTCPMulticastRouting
   * @see $wgHTCPMulticastAddress
   */
  $wgHTCPPort = 4827;
  
  /**
   * HTCP multicast TTL.
-  * @see $wgHTCPMulticastAddress
+  * @see $wgHTCPMulticastRouting
   */
  $wgHTCPMulticastTTL = 1;
  
@@@ -2185,6 -2222,17 +2233,17 @@@ $wgLocaltimezone = null
   */
  $wgLocalTZoffset = null;
  
+ /**
+  * If set to true, this will roll back a few bug fixes introduced in 1.19,
+  * emulating the 1.18 behaviour, to avoid introducing bug 34832. In 1.19,
+  * language variant conversion is disabled in interface messages. Setting this
+  * to true re-enables it.
+  *
+  * This variable should be removed (implicitly false) in 1.20 or earlier.
+  */
+ $wgBug34832TransitionalRollback = true;
  /** @} */ # End of language/charset settings
  
  /*************************************************************************//**
@@@ -2291,6 -2339,7 +2350,7 @@@ $wgXhtmlNamespaces = array()
  /**
   * Show IP address, for non-logged in users. It's necessary to switch this off
   * for some forms of caching.
+  * Will disable file cache.
   */
  $wgShowIPinHeader = true;
  
@@@ -2576,13 -2625,6 +2636,6 @@@ $wgResourceLoaderMaxage = array
        ),
  );
  
- /**
-  * Whether to embed private modules inline with HTML output or to bypass
-  * caching and check the user parameter against $wgUser to prevent
-  * unauthorized access to private modules.
-  */
- $wgResourceLoaderInlinePrivateModules = true;
  /**
   * The default debug mode (on/off) for of ResourceLoader requests. This will still
   * be overridden when the debug URL parameter is used.
@@@ -3237,7 -3279,6 +3290,6 @@@ $wgDefaultUserOptions = array
        'gender'                  => 'unknown',
        'hideminor'               => 0,
        'hidepatrolled'           => 0,
-       'highlightbroken'         => 1,
        'imagesize'               => 2,
        'justify'                 => 0,
        'math'                    => 1,
@@@ -4055,6 -4096,11 +4107,11 @@@ $wgDebugRawPage = false
   */
  $wgDebugComments = false;
  
+ /**
+  * Extensive database transaction state debugging
+  */
+ $wgDebugDBTransactions = false;
  /**
   * Write SQL queries to the debug log
   */
@@@ -4127,7 -4173,7 +4184,7 @@@ $wgDevelopmentWarnings = false
   * development warnings will not be generated for deprecations added in releases
   * after the limit.
   */
- $wgDeprecationReleaseLimit = '1.17';
+ $wgDeprecationReleaseLimit = false;
  
  /** Only record profiling info for pages that took longer than this */
  $wgProfileLimit = 0.0;
@@@ -4220,7 -4266,7 +4277,7 @@@ $wgParserTestFiles = array
   * );
   */
  $wgParserTestRemote = false;
-  
  /**
   * Allow running of javascript test suites via [[Special:JavaScriptTest]] (such as QUnit).
   */
@@@ -4231,7 -4277,17 +4288,17 @@@ $wgEnableJavaScriptTest = false
   */
  $wgJavaScriptTestConfig = array(
        'qunit' => array(
+               // Page where documentation can be found relevant to the QUnit test suite being ran.
+               // Used in the intro paragraph on [[Special:JavaScriptTest/qunit]] for the
+               // documentation link in the "javascripttest-qunit-intro" message.
                'documentation' => '//www.mediawiki.org/wiki/Manual:JavaScript_unit_testing',
+               // If you are submitting the QUnit test suite to a TestSwarm instance,
+               // point this to the "inject.js" script of that instance. This is was registers
+               // the QUnit hooks to extract the test results and push them back up into the
+               // TestSwarm database.
+               // @example 'http://localhost/testswarm/js/inject.js'
+               // @example '//integration.mediawiki.org/testswarm/js/inject.js'
+               'testswarm-injectjs' => false,
        ),
  );
  
@@@ -4245,6 -4301,7 +4312,7 @@@ $wgCachePrefix = false
  /**
   * Display the new debugging toolbar. This also enables profiling on database
   * queries and other useful output.
+  * Will disable file cache.
   *
   * @since 1.19
   */
@@@ -5750,22 -5807,6 +5818,22 @@@ $wgSeleniumConfigFile = null
  $wgDBtestuser = ''; //db user that has permission to create and drop the test databases only
  $wgDBtestpassword = '';
  
 +/**
 + * Associative array mapping namespace IDs to the name of the content model pages in that namespace should have by
 + * default (use the CONTENT_MODEL_XXX constants). If no special content type is defined for a given namespace,
 + * pages in that namespace will  use the CONTENT_MODEL_WIKITEXT (except for the special case of JS and CS pages).
 + */
 +$wgNamespaceContentModels = array();
 +
 +/**
 + * How to react if a plain text version of a non-text Content object is requested using ContentHandler::getContentText():
 + *
 + * * 'ignore': return null
 + * * 'fail': throw an MWException
 + * * 'serialize': serialize to default format
 + */
 +$wgContentHandlerTextFallback = 'ignore';
 +
  /**
   * For really cool vim folding this needs to be at the end:
   * vim: foldmarker=@{,@} foldmethod=marker
diff --combined includes/Defines.php
@@@ -9,6 -9,10 +9,10 @@@
   * @file
   */
  
+ /**
+  * @defgroup Constants
+  */
  /**
   * Version constants for the benefit of extensions
   */
@@@ -249,12 -253,3 +253,12 @@@ define( 'PROTO_RELATIVE', '//' )
  define( 'PROTO_CURRENT', null );
  define( 'PROTO_CANONICAL', 1 );
  define( 'PROTO_INTERNAL', 2 );
 +
 +/**
 + * Content model names, used by Content and ContentHandler
 + */
 +define('CONTENT_MODEL_WIKITEXT', 'wikitext');
 +define('CONTENT_MODEL_JAVASCRIPT', 'javascript');
 +define('CONTENT_MODEL_CSS', 'css');
 +define('CONTENT_MODEL_TEXT', 'text');
 +
diff --combined includes/EditPage.php
@@@ -37,7 -37,7 +37,7 @@@ class EditPage 
        const AS_HOOK_ERROR                = 210;
  
        /**
-        * Status: The filter function set in $wgFilterCallback returned true (= block it) 
+        * Status: The filter function set in $wgFilterCallback returned true (= block it)
         */
        const AS_FILTERING                 = 211;
  
         */
        const AS_IMAGE_REDIRECT_LOGGED     = 234;
  
 +      /**
 +       * Status: can't parse content
 +       */
 +      const AS_PARSE_ERROR                = 240;
 +
        /**
         * @var Article
         */
         */
        var $mParserOutput;
  
+       /**
+        * Has a summary been preset using GET parameter &summary= ?
+        * @var Bool
+        */
+       var $hasPresetSummary = false;
        var $mBaseRevision = false;
        var $mShowSummaryField = true;
  
        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->getContentModelName();
 +
 +              $handler = ContentHandler::getForModelName( $this->content_model );
 +              $this->content_format = $handler->getDefaultFormat(); #NOTE: should be overridden by format of actual revision
        }
  
        /**
                }
  
                wfProfileIn( __METHOD__ );
-               wfDebug( __METHOD__.": enter\n" );
+               wfDebug( __METHOD__ . ": enter\n" );
  
                // If they used redlink=1 and the page exists, redirect to the main article
                if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
                        return;
                }
  
-               wfProfileIn( __METHOD__."-business-end" );
+               wfProfileIn( __METHOD__ . "-business-end" );
  
                $this->isConflict = false;
                // css / js subpages of user pages get a special treatment
  
                if ( 'save' == $this->formtype ) {
                        if ( !$this->attemptSave() ) {
-                               wfProfileOut( __METHOD__."-business-end" );
+                               wfProfileOut( __METHOD__ . "-business-end" );
                                wfProfileOut( __METHOD__ );
                                return;
                        }
                if ( 'initial' == $this->formtype || $this->firsttime ) {
                        if ( $this->initialiseForm() === false ) {
                                $this->noSuchSectionPage();
-                               wfProfileOut( __METHOD__."-business-end" );
+                               wfProfileOut( __METHOD__ . "-business-end" );
                                wfProfileOut( __METHOD__ );
                                return;
                        }
-                       if ( !$this->mTitle->getArticleId() )
+                       if ( !$this->mTitle->getArticleID() )
                                wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
                        else
                                wfRunHooks( 'EditFormInitialText', array( $this ) );
                }
  
                $this->showEditForm();
-               wfProfileOut( __METHOD__."-business-end" );
+               wfProfileOut( __METHOD__ . "-business-end" );
                wfProfileOut( __METHOD__ );
        }
  
                }
                # Ignore some permissions errors when a user is just previewing/viewing diffs
                $remove = array();
-               foreach( $permErrors as $error ) {
+               foreach ( $permErrors as $error ) {
                        if ( ( $this->preview || $this->diff ) &&
                                ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' ) )
                        {
                        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() ) ) );
                        // Standard preference behaviour
                        return true;
                } elseif ( !$this->mTitle->exists() &&
-                 isset($wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]) &&
+                 isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) &&
                  $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
                {
                        // Categories are special
         * @return bool
         */
        protected function isWrongCaseCssJsPage() {
-               if( $this->mTitle->isCssJsSubpage() ) {
+               if ( $this->mTitle->isCssJsSubpage() ) {
                        $name = $this->mTitle->getSkinFromCssJsSubpage();
                        $skins = array_merge(
                                array_keys( Skin::getSkinNames() ),
                        # Also remove trailing whitespace, but don't remove _initial_
                        # whitespace from the text boxes. This may be significant formatting.
                        $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
-                       if ( !$request->getCheck('wpTextbox2') ) {
+                       if ( !$request->getCheck( 'wpTextbox2' ) ) {
                                // Skip this if wpTextbox2 has input, it indicates that we came
                                // from a conflict page with raw page text, not a custom form
                                // modified by subclasses
 -                              wfProfileIn( get_class( $this ) . "::importContentFormData" );
 -                              $textbox1 = $this->importContentFormData( $request );
 -                              if ( isset( $textbox1 ) )
 +                              wfProfileIn( get_class($this)."::importContentFormData" );
 +                              $textbox1 = $this->importContentFormData( $request ); #FIXME: what should this return??
 +                              if ( isset($textbox1) )
                                        $this->textbox1 = $textbox1;
-                               wfProfileOut( get_class($this)."::importContentFormData" );
+                               wfProfileOut( get_class( $this ) . "::importContentFormData" );
                        }
  
                        # Truncate for whole multibyte characters. +5 bytes for ellipsis
                        {
                                $this->allowBlankSummary = true;
                        } else {
-                               $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary');
+                               $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary' );
                        }
  
                        $this->autoSumm = $request->getText( 'wpAutoSummary' );
                } else {
                        # Not a posted form? Start with nothing.
                        wfDebug( __METHOD__ . ": Not a posted form.\n" );
 -                      $this->textbox1     = '';
 +                      $this->textbox1     = ''; #FIXME: track content object
                        $this->summary      = '';
                        $this->sectiontitle = '';
                        $this->edittime     = '';
                        }
                        elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
                                $this->summary = $request->getText( 'summary' );
+                               if ( $this->summary !== '' ) {
+                                       $this->hasPresetSummary = true;
+                               }
                        }
  
                        if ( $request->getVal( 'minor' ) ) {
                        }
                }
  
 +              $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->getModelName() ); #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.20
         */
 -      function getContent( $def_text = '' ) {
 -              global $wgOut, $wgRequest, $wgParser;
 +      function getContent( $def_text = false ) { #FIXME: deprecated, replace usage!
 +              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 );
 +
 +              return $content->serialize( $this->content_format ); #XXX: really use serialized form? use ContentHandler::getContentText() instead?
 +      }
 +
 +      private function getContentObject( $def_content = null ) { #FIXME: use this!
 +              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 = new WikitextContent($msg); //XXX: really hardcode wikitext here?
                        }
 -                      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->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 {
                                                $undoMsg = 'norev';
                                        }
  
-                                       $this->editFormPageTop .= $wgOut->parse( "<div class=\"error mw-undo-{$undoMsg}\">" .
+                                       $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
+                                       $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
                                                wfMsgNoTrans( 'undo-' . $undoMsg ) . '</div>', true, /* interface */true );
                                }
  
 -                              if ( $text === false ) {
 -                                      $text = $this->getOriginalContent();
 +                              if ( $content === false ) {
 +                                      $content = $this->getOriginalContent();
                                }
                        }
                }
  
                wfProfileOut( __METHOD__ );
 -              return $text;
 +              return $content;
        }
  
        /**
         * @since 1.19
         * @return string
         */
 -      private function getOriginalContent() {
 +      private function getOriginalContent() { #FIXME: use Content! set content_model and content_format!
                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()->getContentModelName();
 +                      $handler = ContentHandler::getForModelName( $this->content_model );
 +
 +                      return $handler->emptyContent();
                }
 -              return $this->mArticle->getContent();
 +
 +              $content = $this->mArticle->getContentObject();
 +              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.20
         * @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()->getContentModelName();
 +                      $handler = ContentHandler::getForModelName( $this->content_model );
 +
 +                      return $handler->emptyContent();
                } else {
 -                      return $text;
 +                      #FIXME: nasty side-effect!
 +                      $this->content_model = $rev->getContentModelName();
 +                      $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.20
 +       */
 +      public function setPreloadedText( $text ) { #FIXME: deprecated, use setPreloadedContent()
 +              wfDeprecated( __METHOD__, "1.20" );
 +
 +              $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
         */
 -      public function setPreloadedText( $text ) {
 -              $this->mPreloadText = $text;
 +      public function setPreloadedContent( Content $content ) { #FIXME: use this!
 +              $this->mPreloadedContent = $content;
        }
  
        /**
         *
         * @param $preload String: representing the title to preload from.
         * @return String
 +       * @deprecated since 1.20
         */
 -      protected function getPreloadedText( $preload ) {
 -              global $wgUser, $wgParser;
 +      protected function getPreloadedText( $preload ) { #FIXME: B/C only, replace usage!
 +              wfDeprecated( __METHOD__, "1.20" );
  
 -              if ( !empty( $this->mPreloadText ) ) {
 -                      return $this->mPreloadText;
 +              $content = $this->getPreloadedContent( $preload );
 +              $text = $content->serialize( $this->content_format ); #XXX: really use serialized form? use ContentHandler::getContentText() instead?!
 +
 +              return $text;
 +      }
 +
 +      protected function getPreloadedContent( $preload ) { #FIXME: use this!
 +              global $wgUser;
 +
 +              if ( !empty( $this->mPreloadContent ) ) {
 +                      return $this->mPreloadContent;
                }
  
 +              $handler = ContentHandler::getForTitle( $this->getTitle() );
 +
                if ( $preload === '' ) {
 -                      return '';
 +                      return $handler->emptyContent();
                }
  
                $title = Title::newFromText( $preload );
                # Check for existence to avoid getting MediaWiki:Noarticletext
                if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
 -                      return '';
 +                      return $handler->emptyContent();
                }
  
                $page = WikiPage::factory( $title );
                        $title = $page->getRedirectTarget();
                        # Same as before
                        if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
 -                              return '';
 +                              return $handler->emptyContent();
                        }
                        $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_FILTERING:
                                return false;
  
 +                      case self::AS_PARSE_ERROR:
 +                              $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>');
 +                              #FIXME: cause editform to be shown again, not just an error!
 +                              return false;
 +
                        case self::AS_SUCCESS_NEW_ARTICLE:
                                $query = $resultDetails['redirect'] ? 'redirect=no' : '';
                                $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
                                return false;
  
                        case self::AS_BLOCKED_PAGE_FOR_USER:
-                               throw new UserBlockedError( $wgUser->mBlock );
+                               throw new UserBlockedError( $wgUser->getBlock() );
  
                        case self::AS_IMAGE_REDIRECT_ANON:
                        case self::AS_IMAGE_REDIRECT_LOGGED:
  
                # Check image redirect
                if ( $this->mTitle->getNamespace() == NS_FILE &&
 -                      Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
 +                      Title::newFromRedirect( $this->textbox1 ) instanceof Title && #FIXME: use content handler to check for redirect
                        !$wgUser->isAllowed( 'upload' ) ) {
                                $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
                                $status->setResult( false, $code );
                $aid = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
                $new = ( $aid == 0 );
  
 -              if ( $new ) {
 -                      // Late check for create permission, just in case *PARANOIA*
 -                      if ( !$this->mTitle->userCan( 'create' ) ) {
 -                              $status->fatal( 'nocreatetext' );
 -                              $status->value = self::AS_NO_CREATE_PERMISSION;
 -                              wfDebug( __METHOD__ . ": no create permission\n" );
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +              try {
 +                      if ( $new ) {
 +                              // Late check for create permission, just in case *PARANOIA*
 +                              if ( !$this->mTitle->userCan( 'create' ) ) {
 +                                      $status->fatal( 'nocreatetext' );
 +                                      $status->value = self::AS_NO_CREATE_PERMISSION;
 +                                      wfDebug( __METHOD__ . ": no create permission\n" );
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              }
  
 -                      # Don't save a new article if it's blank.
 -                      if ( $this->textbox1 == '' ) {
 -                              $status->setResult( false, self::AS_BLANK_ARTICLE );
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +                              # Don't save a new article if it's blank.
 +                              if ( $this->textbox1 == '' ) {
 +                                      $status->setResult( false, self::AS_BLANK_ARTICLE );
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              }
  
 -                      // Run post-section-merge edit filter
 -                      if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
 -                              # Error messages etc. could be handled within the hook...
 -                              $status->fatal( 'hookaborted' );
 -                              $status->value = self::AS_HOOK_ERROR;
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      } elseif ( $this->hookError != '' ) {
 -                              # ...or the hook could be expecting us to produce an error
 -                              $status->fatal( 'hookaborted' );
 -                              $status->value = self::AS_HOOK_ERROR_EXPECTED;
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
++<<<<<<< HEAD
 +                              // Run post-section-merge edit filter
 +                              if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
 +                                      # Error messages etc. could be handled within the hook...
 +                                      $status->fatal( 'hookaborted' );
 +                                      $status->value = self::AS_HOOK_ERROR;
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              } elseif ( $this->hookError != '' ) {
 +                                      # ...or the hook could be expecting us to produce an error
 +                                      $status->fatal( 'hookaborted' );
 +                                      $status->value = self::AS_HOOK_ERROR_EXPECTED;
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              }
 +
 +                $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
  
 +                              # Handle the user preference to force summaries here. Check if it's not a redirect.
 +                              if ( !$this->allowBlankSummary && !$content->isRedirect() ) {
 +                                      if ( md5( $this->summary ) == $this->autoSumm ) {
 +                                              $this->missingSummary = true;
 +                                              $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
 +                                              $status->value = self::AS_SUMMARY_NEEDED;
 +                                              wfProfileOut( __METHOD__ );
 +                                              return $status;
 +                                      }
++=======
+                       $text = $this->textbox1;
+                       $result['sectionanchor'] = '';
+                       if ( $this->section == 'new' ) {
+                               if ( $this->sectiontitle !== '' ) {
+                                       // Insert the section title above the content.
+                                       $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $text;
+                                       // Jump to the new section
+                                       $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+                                       // If no edit summary was specified, create one automatically from the section
+                                       // title and have it link to the new section. Otherwise, respect the summary as
+                                       // passed.
+                                       if ( $this->summary === '' ) {
+                                               $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+                                               $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+                                       }
+                               } elseif ( $this->summary !== '' ) {
+                                       // Insert the section title above the content.
+                                       $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
+                                       // Jump to the new section
+                                       $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+                                       // Create a link to the new section from the edit summary.
+                                       $cleanSummary = $wgParser->stripSectionName( $this->summary );
+                                       $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
++>>>>>>> master
                                }
 -                      }
  
 -                      $status->value = self::AS_SUCCESS_NEW_ARTICLE;
 +                              $result['sectionanchor'] = '';
 +                              if ( $this->section == 'new' ) {
 +                                      if ( $this->sectiontitle !== '' ) {
 +                                              // Insert the section title above the content.
 +                                              $content = $content->addSectionHeader( $this->sectiontitle );
 +
 +                                              // Jump to the new section
 +                                              $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
 +
 +                                              // If no edit summary was specified, create one automatically from the section
 +                                              // title and have it link to the new section. Otherwise, respect the summary as
 +                                              // passed.
 +                                              if ( $this->summary === '' ) {
 +                                                      $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
 +                                                      $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
 +                                              }
 +                                      } elseif ( $this->summary !== '' ) {
 +                                              // Insert the section title above the content.
 +                                              $content = $content->addSectionHeader( $this->sectiontitle );
 +
 +                                              // Jump to the new section
 +                                              $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
  
 -              } else {
 +                                              // Create a link to the new section from the edit summary.
 +                                              $cleanSummary = $wgParser->stripSectionName( $this->summary );
 +                                              $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
 +                                      }
 +                              }
  
 -                      # Article exists. Check for edit conflict.
 +                              $status->value = self::AS_SUCCESS_NEW_ARTICLE;
  
 -                      $this->mArticle->clear(); # Force reload of dates, etc.
 -                      $timestamp = $this->mArticle->getTimestamp();
 +                      } else {
  
 -                      wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
 +                              # Article exists. Check for edit conflict.
  
 -                      if ( $timestamp != $this->edittime ) {
 -                              $this->isConflict = true;
 -                              if ( $this->section == 'new' ) {
 -                                      if ( $this->mArticle->getUserText() == $wgUser->getName() &&
 -                                              $this->mArticle->getComment() == $this->summary ) {
 -                                              // Probably a duplicate submission of a new comment.
 -                                              // This can happen when squid resends a request after
 -                                              // a timeout but the first one actually went through.
 -                                              wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
 -                                      } else {
 -                                              // New comment; suppress conflict.
 +                              $this->mArticle->clear(); # Force reload of dates, etc.
 +                              $timestamp = $this->mArticle->getTimestamp();
 +
 +                              wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
 +
 +                              if ( $timestamp != $this->edittime ) {
 +                                      $this->isConflict = true;
 +                                      if ( $this->section == 'new' ) {
 +                                              if ( $this->mArticle->getUserText() == $wgUser->getName() &&
 +                                                      $this->mArticle->getComment() == $this->summary ) {
 +                                                      // Probably a duplicate submission of a new comment.
 +                                                      // This can happen when squid resends a request after
 +                                                      // a timeout but the first one actually went through.
 +                                                      wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
 +                                              } else {
 +                                                      // New comment; suppress conflict.
 +                                                      $this->isConflict = false;
 +                                                      wfDebug( __METHOD__ .": conflict suppressed; new section\n" );
 +                                              }
 +                                      } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
 +                                              # Suppress edit conflict with self, except for section edits where merging is required.
 +                                              wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
                                                $this->isConflict = false;
++<<<<<<< HEAD
++=======
+                                               wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
++>>>>>>> master
                                        }
 -                              } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
 -                                      # Suppress edit conflict with self, except for section edits where merging is required.
 -                                      wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
 -                                      $this->isConflict = false;
                                }
++<<<<<<< HEAD
 +
 +                              // If sectiontitle is set, use it, otherwise use the summary as the section title (for
 +                              // backwards compatibility with old forms/bots).
 +                              if ( $this->sectiontitle !== '' ) {
 +                                      $sectionTitle = $this->sectiontitle;
++=======
+                       }
+                       // If sectiontitle is set, use it, otherwise use the summary as the section title (for
+                       // backwards compatibility with old forms/bots).
+                       if ( $this->sectiontitle !== '' ) {
+                               $sectionTitle = $this->sectiontitle;
+                       } else {
+                               $sectionTitle = $this->summary;
+                       }
+                       if ( $this->isConflict ) {
+                               wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
+                               $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
+                       } else {
+                               wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
+                               $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+                       }
+                       if ( is_null( $text ) ) {
+                               wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
+                               $this->isConflict = true;
+                               $text = $this->textbox1; // do not try to merge here!
+                       } elseif ( $this->isConflict ) {
+                               # Attempt merge
+                               if ( $this->mergeChangesInto( $text ) ) {
+                                       // Successful merge! Maybe we should tell the user the good news?
+                                       $this->isConflict = false;
+                                       wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
++>>>>>>> master
                                } else {
 -                                      $this->section = '';
 -                                      $this->textbox1 = $text;
 -                                      wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
 +                                      $sectionTitle = $this->summary;
                                }
 -                      }
  
 -                      if ( $this->isConflict ) {
 -                              $status->setResult( false, self::AS_CONFLICT_DETECTED );
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +                              $textbox_content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
 +                              $content = false;
  
 -                      // Run post-section-merge edit filter
 -                      if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
 -                              # Error messages etc. could be handled within the hook...
 -                              $status->fatal( 'hookaborted' );
 -                              $status->value = self::AS_HOOK_ERROR;
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      } elseif ( $this->hookError != '' ) {
 -                              # ...or the hook could be expecting us to produce an error
 -                              $status->fatal( 'hookaborted' );
 -                              $status->value = self::AS_HOOK_ERROR_EXPECTED;
 -                              wfProfileOut( __METHOD__ );
 -                              return $status;
 -                      }
 +                              if ( $this->isConflict ) {
 +                                      wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
  
 -                      # 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
 -                      {
 -                              if ( md5( $this->summary ) == $this->autoSumm ) {
 -                                      $this->missingSummary = true;
 -                                      $status->fatal( 'missingsummary' );
 -                                      $status->value = self::AS_SUMMARY_NEEDED;
 -                                      wfProfileOut( __METHOD__ );
 -                                      return $status;
 +                                      $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
 +                              } else {
 +                                      wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
 +
 +                                      $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
                                }
 -                      }
  
 -                      # And a similar thing for new sections
 -                      if ( $this->section == 'new' && !$this->allowBlankSummary ) {
 -                              if ( trim( $this->summary ) == '' ) {
 -                                      $this->missingSummary = true;
 -                                      $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
 -                                      $status->value = self::AS_SUMMARY_NEEDED;
 +                              if ( is_null( $content ) ) {
 +                                      wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
 +                                      $this->isConflict = true;
 +                                      $content = $textbox_content; // do not try to merge here!
 +                              } elseif ( $this->isConflict ) {
 +                                      # Attempt merge
 +                                      if ( $this->mergeChangesIntoContent( $textbox_content ) ) {
 +                                              // Successful merge! Maybe we should tell the user the good news?
 +                                              $content = $textbox_content;
 +                                              $this->isConflict = false;
 +                                              wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
 +                                      } else {
 +                                              $this->section = '';
 +                                              #$this->textbox1 = $text; #redundant, nothing to do here?
 +                                              wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
 +                                      }
 +                              }
 +
 +                              if ( $this->isConflict ) {
 +                                      $status->setResult( false, self::AS_CONFLICT_DETECTED );
                                        wfProfileOut( __METHOD__ );
                                        return $status;
                                }
 -                      }
  
 -                      # All's well
 -                      wfProfileIn( __METHOD__ . '-sectionanchor' );
 -                      $sectionanchor = '';
 -                      if ( $this->section == 'new' ) {
 -                              if ( $this->textbox1 == '' ) {
 -                                      $this->missingComment = true;
 -                                      $status->fatal( 'missingcommenttext' );
 -                                      $status->value = self::AS_TEXTBOX_EMPTY;
 -                                      wfProfileOut( __METHOD__ . '-sectionanchor' );
 +                              // Run post-section-merge edit filter
 +                              if ( !wfRunHooks( 'EditFilterMerged', array( $this, $content->serialize( $this->content_format ), &$this->hookError, $this->summary ) )
 +                                      || !wfRunHooks( 'EditFilterMergedContent', array( $this, $content, &$this->hookError, $this->summary ) ) ) { #FIXME: document new hook
 +                                      # Error messages etc. could be handled within the hook...
 +                                      $status->fatal( 'hookaborted' );
 +                                      $status->value = self::AS_HOOK_ERROR;
 +                                      wfProfileOut( __METHOD__ );
 +                                      return $status;
 +                              } elseif ( $this->hookError != '' ) {
 +                                      # ...or the hook could be expecting us to produce an error
 +                                      $status->fatal( 'hookaborted' );
 +                                      $status->value = self::AS_HOOK_ERROR_EXPECTED;
                                        wfProfileOut( __METHOD__ );
                                        return $status;
                                }
 -                              if ( $this->sectiontitle !== '' ) {
 -                                      $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
 -                                      // If no edit summary was specified, create one automatically from the section
 -                                      // title and have it link to the new section. Otherwise, respect the summary as
 -                                      // passed.
 -                                      if ( $this->summary === '' ) {
 -                                              $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
 -                                              $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
 +
 +                              # Handle the user preference to force summaries here, but not for null edits
 +                              if ( $this->section != 'new' && !$this->allowBlankSummary
 +                                      && !$content->equals( $this->getOriginalContent() )
 +                                      && !$content->isRedirect() ) # check if it's not a redirect
 +                              {
 +                                      if ( md5( $this->summary ) == $this->autoSumm ) {
 +                                              $this->missingSummary = true;
 +                                              $status->fatal( 'missingsummary' );
 +                                              $status->value = self::AS_SUMMARY_NEEDED;
 +                                              wfProfileOut( __METHOD__ );
 +                                              return $status;
                                        }
 -                              } elseif ( $this->summary !== '' ) {
 -                                      $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
 -                                      # This is a new section, so create a link to the new section
 -                                      # in the revision summary.
 -                                      $cleanSummary = $wgParser->stripSectionName( $this->summary );
 -                                      $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
                                }
 -                      } elseif ( $this->section != '' ) {
 -                              # Try to get a section anchor from the section source, redirect to edited section if header found
 -                              # XXX: might be better to integrate this into Article::replaceSection
 -                              # for duplicate heading checking and maybe parsing
 -                              $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
 -                              # we can't deal with anchors, includes, html etc in the header for now,
 -                              # headline would need to be parsed to improve this
 -                              if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
 -                                      $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
 +
 +                              # And a similar thing for new sections
 +                              if ( $this->section == 'new' && !$this->allowBlankSummary ) {
 +                                      if ( trim( $this->summary ) == '' ) {
 +                                              $this->missingSummary = true;
 +                                              $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
 +                                              $status->value = self::AS_SUMMARY_NEEDED;
 +                                              wfProfileOut( __METHOD__ );
 +                                              return $status;
 +                                      }
                                }
 -                      }
 -                      $result['sectionanchor'] = $sectionanchor;
 -                      wfProfileOut( __METHOD__ . '-sectionanchor' );
  
 -                      // Save errors may fall down to the edit form, but we've now
 -                      // 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->section = '';
 +                              # All's well
 +                              wfProfileIn( __METHOD__ . '-sectionanchor' );
 +                              $sectionanchor = '';
 +                              if ( $this->section == 'new' ) {
 +                                      if ( $this->textbox1 == '' ) {
 +                                              $this->missingComment = true;
 +                                              $status->fatal( 'missingcommenttext' );
 +                                              $status->value = self::AS_TEXTBOX_EMPTY;
 +                                              wfProfileOut( __METHOD__ . '-sectionanchor' );
 +                                              wfProfileOut( __METHOD__ );
 +                                              return $status;
 +                                      }
 +                                      if ( $this->sectiontitle !== '' ) {
 +                                              $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
 +                                              // If no edit summary was specified, create one automatically from the section
 +                                              // title and have it link to the new section. Otherwise, respect the summary as
 +                                              // passed.
 +                                              if ( $this->summary === '' ) {
 +                                                      $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
 +                                                      $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
 +                                              }
 +                                      } elseif ( $this->summary !== '' ) {
 +                                              $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
 +                                              # This is a new section, so create a link to the new section
 +                                              # in the revision summary.
 +                                              $cleanSummary = $wgParser->stripSectionName( $this->summary );
 +                                              $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
 +                                      }
 +                              } elseif ( $this->section != '' ) {
 +                                      # Try to get a section anchor from the section source, redirect to edited section if header found
 +                                      # XXX: might be better to integrate this into Article::replaceSection
 +                                      # for duplicate heading checking and maybe parsing
 +                                      $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
 +                                      # we can't deal with anchors, includes, html etc in the header for now,
 +                                      # headline would need to be parsed to improve this
 +                                      if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
 +                                              $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
 +                                      }
 +                              }
 +                              $result['sectionanchor'] = $sectionanchor;
 +                              wfProfileOut( __METHOD__ . '-sectionanchor' );
  
 -                      $status->value = self::AS_SUCCESS_UPDATE;
 -              }
 +                              // Save errors may fall down to the edit form, but we've now
 +                              // 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 = $content->serialize( $this->content_format );
 +                              $this->section = '';
  
 -              // Check for length errors again now that the section is merged in
 -              $this->kblength = (int)( strlen( $text ) / 1024 );
 -              if ( $this->kblength > $wgMaxArticleSize ) {
 -                      $this->tooBig = true;
 -                      $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
 -                      wfProfileOut( __METHOD__ );
 -                      return $status;
 -              }
 +                              $status->value = self::AS_SUCCESS_UPDATE;
 +                      }
 +
 +                      // Check for length errors again now that the section is merged in
 +                      $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 );
 +                              wfProfileOut( __METHOD__ );
 +                              return $status;
 +                      }
  
 -              $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
 -                      ( $new ? EDIT_NEW : EDIT_UPDATE ) |
 -                      ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
 -                      ( $bot ? EDIT_FORCE_BOT : 0 );
 +                      $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
 +                              ( $new ? EDIT_NEW : EDIT_UPDATE ) |
 +                              ( ( $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;
 -                      $this->commitWatch();
 +                      if ( $doEditStatus->isOK() ) {
 +                              $result['redirect'] = $content->isRedirect();
 +                              $this->commitWatch();
 +                              wfProfileOut( __METHOD__ );
 +                              return $status;
 +                      } else {
 +                              $this->isConflict = true;
 +                              $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares
 +                              wfProfileOut( __METHOD__ );
 +                              return $doEditStatus;
 +                      }
 +              } 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;
 -              } else {
 -                      $this->isConflict = true;
 -                      $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares
 -                      wfProfileOut( __METHOD__ );
 -                      return $doEditStatus;
                }
        }
  
                global $wgUser;
                if ( $this->watchthis xor $this->mTitle->userIsWatching() ) {
                        $dbw = wfGetDB( DB_MASTER );
-                       $dbw->begin();
+                       $dbw->begin( __METHOD__ );
                        if ( $this->watchthis ) {
                                WatchAction::doWatch( $this->mTitle, $wgUser );
                        } else {
                                WatchAction::doUnwatch( $this->mTitle, $wgUser );
                        }
-                       $dbw->commit();
+                       $dbw->commit( __METHOD__ );
                }
        }
  
         * @return bool
         */
        protected function userWasLastToEdit( $id, $edittime ) {
-               if( !$id ) return false;
+               if ( !$id ) return false;
                $dbw = wfGetDB( DB_MASTER );
                $res = $dbw->select( 'revision',
                        'rev_user',
                        array(
-                               'rev_page' => $this->mTitle->getArticleId(),
-                               'rev_timestamp > '.$dbw->addQuotes( $dbw->timestamp($edittime) )
+                               'rev_page' => $this->mTitle->getArticleID(),
+                               'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $edittime ) )
                        ),
                        __METHOD__,
                        array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ) );
                foreach ( $res as $row ) {
-                       if( $row->rev_user != $id ) {
+                       if ( $row->rev_user != $id ) {
                                return false;
                        }
                }
         * @parma $editText string
         *
         * @return bool
 +       * @deprecated since 1.20
 +       */
 +      function mergeChangesInto( &$editText ){
 +              wfDebug( __METHOD__, "1.20" );
 +
 +              $editContent = ContentHandler::makeContent( $editText, $this->getTitle(), $this->content_model, $this->content_format );
 +
 +              $ok = $this->mergeChangesIntoContent( $editContent );
 +
 +              if ( $ok ) {
 +                      $editText = $editContent->serialize( $this->content_format ); #XXX: really serialize?!
 +                      return true;
 +              } else {
 +                      return false;
 +              }
 +      }
 +
 +      /**
 +       * @private
 +       * @todo document
 +       *
 +       * @parma $editText string
 +       *
 +       * @return bool
 +       * @since since 1.20
         */
 -      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::getForModelName( $baseContent->getModelName() );
 +
 +              $result = $handler->merge3( $baseContent, $editContent, $currentContent );
 +
 +              if ( $result ) {
 +                      $editContent = $result;
                        wfProfileOut( __METHOD__ );
                        return true;
                } else {
         *
         * @param $text string
         *
-        * @return string|false matching string or false
+        * @return string|bool matching string or false
         */
        public static function matchSpamRegex( $text ) {
                global $wgSpamRegex;
         *
         * @parma $text string
         *
-        * @return string|false  matching string or false
+        * @return string|bool  matching string or false
         */
        public static function matchSummarySpamRegex( $text ) {
                global $wgSummarySpamRegex;
         * @return bool|string
         */
        protected static function matchSpamRegexInternal( $text, $regexes ) {
-               foreach( $regexes as $regex ) {
+               foreach ( $regexes as $regex ) {
                        $matches = array();
-                       if( preg_match( $regex, $text, $matches ) ) {
+                       if ( preg_match( $regex, $text, $matches ) ) {
                                return $matches[0];
                        }
                }
                # Enabled article-related sidebar, toplinks, etc.
                $wgOut->setArticleRelated( true );
  
+               $contextTitle = $this->getContextTitle();
                if ( $this->isConflict ) {
-                       $wgOut->setPageTitle( wfMessage( 'editconflict', $this->getContextTitle()->getPrefixedText() ) );
-               } elseif ( $this->section != '' ) {
+                       $msg = 'editconflict';
+               } elseif ( $contextTitle->exists() && $this->section != '' ) {
                        $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
-                       $wgOut->setPageTitle( wfMessage( $msg, $this->getContextTitle()->getPrefixedText() ) );
                } else {
-                       # Use the title defined by DISPLAYTITLE magic word when present
-                       if ( isset( $this->mParserOutput )
-                        && ( $dt = $this->mParserOutput->getDisplayTitle() ) !== false ) {
-                               $title = $dt;
-                       } else {
-                               $title = $this->getContextTitle()->getPrefixedText();
-                       }
-                       $wgOut->setPageTitle( wfMessage( 'editing', $title ) );
 -                      $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?\r
 -                              'editing' : 'creating';\r
++                      $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?
++                              'editing' : 'creating';
+               }
+               # Use the title defined by DISPLAYTITLE magic word when present
 -              $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;\r
 -              if ( $displayTitle === false ) {\r
 -                      $displayTitle = $contextTitle->getPrefixedText();\r
++              $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
++              if ( $displayTitle === false ) {
++                      $displayTitle = $contextTitle->getPrefixedText();
                }
+               $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
        }
  
        /**
                if ( $namespace == NS_MEDIAWIKI ) {
                        # Show a warning if editing an interface message
                        $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
+               } else if( $namespace == NS_FILE ) {
+                       # Show a hint to shared repo
+                       $file = wfFindFile( $this->mTitle );
+                       if( $file && !$file->isLocal() ) {
+                               $descUrl = $file->getDescriptionUrl();
+                               # there must be a description url to show a hint to shared repo
+                               if( $descUrl ) {
+                                       if( !$this->mTitle->exists() ) {
+                                               $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array (
+                                                                       'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
+                                               ) );
+                                       } else {
+                                               $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array(
+                                                                       'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
+                                               ) );
+                                       }
+                               }
+                       }
                }
  
                # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
                        $username = $parts[0];
                        $user = User::newFromName( $username, false /* allow IP users*/ );
                        $ip = User::isIP( $username );
-                       if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
+                       if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
                                $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
                                        array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
                        } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
                                '', array( 'lim' => 10,
                                           'conds' => array( "log_action != 'revision'" ),
                                           'showIfEmpty' => false,
-                                          'msgKey' => array( 'recreate-moveddeleted-warn') )
+                                          'msgKey' => array( 'recreate-moveddeleted-warn' ) )
                        );
                }
        }
  
                wfProfileIn( __METHOD__ );
  
-               #need to parse the preview early so that we know which templates are used,
-               #otherwise users with "show preview after edit box" will get a blank list
-               #we parse this near the beginning so that setHeaders can do the title
-               #setting work instead of leaving it in getPreviewText
+               # need to parse the preview early so that we know which templates are used,
+               # otherwise users with "show preview after edit box" will get a blank list
+               # we parse this near the beginning so that setHeaders can do the title
+               # setting work instead of leaving it in getPreviewText
                $previewOutput = '';
                if ( $this->formtype == 'preview' ) {
                        $previewOutput = $this->getPreviewText();
                }
  
-               wfRunHooks( 'EditPage::showEditForm:initial', array( &$this ) );
+               wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
  
                $this->setHeaders();
  
                        }
                }
  
 +              #FIXME: add EditForm plugin interface and use it here! #FIXME: search for textarea1 and textares2, and allow EditForm to override all uses.
                $wgOut->addHTML( Html::openElement( 'form', array( 'id' => 'editform', 'name' => 'editform',
                        'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
                        'enctype' => 'multipart/form-data' ) ) );
                        );
                }
  
+               # When the summary is hidden, also hide them on preview/show changes
+               if( $this->nosummary ) {
+                       $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
+               }
                # If a blank edit summary was previously provided, and the appropriate
                # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
                # user being bounced back more than once in the event that a summary
                        $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
                }
  
+               if ( $this->hasPresetSummary ) {
+                       // If a summary has been preset using &summary= we dont want to prompt for
+                       // a different summary. Only prompt for a summary if the summary is blanked.
+                       // (Bug 17416)
+                       $this->autoSumm = md5( '' );
+               }
                $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
                $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
  
                $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 {
         * @return Mixed|string or false
         */
        public static function extractSectionTitle( $text ) {
-               preg_match( "/^(=+)(.+)\\1(\n|$)/i", $text, $matches );
+               preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
                if ( !empty( $matches[2] ) ) {
                        global $wgParser;
-                       return $wgParser->stripSectionName(trim($matches[2]));
+                       return $wgParser->stripSectionName( trim( $matches[2] ) );
                } else {
                        return false;
                }
                }
  
                # Optional notices on a per-namespace and per-page basis
-               $editnotice_ns   = 'editnotice-'.$this->mTitle->getNamespace();
+               $editnotice_ns = 'editnotice-' . $this->mTitle->getNamespace();
                $editnotice_ns_message = wfMessage( $editnotice_ns )->inContentLanguage();
                if ( $editnotice_ns_message->exists() ) {
                        $wgOut->addWikiText( $editnotice_ns_message->plain() );
                        $parts = explode( '/', $this->mTitle->getDBkey() );
                        $editnotice_base = $editnotice_ns;
                        while ( count( $parts ) > 0 ) {
-                               $editnotice_base .= '-'.array_shift( $parts );
+                               $editnotice_base .= '-' . array_shift( $parts );
                                $editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
                                if ( $editnotice_base_msg->exists() ) {
-                                       $wgOut->addWikiText( $editnotice_base_msg->plain()  );
+                                       $wgOut->addWikiText( $editnotice_base_msg->plain() );
                                }
                        }
                } else {
                }
                if ( $this->mTitle->isCascadeProtected() ) {
                        # Is this page under cascading protection from some source pages?
-                       list($cascadeSources, /* $restrictions */) = $this->mTitle->getCascadeProtectionSources();
+                       list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
                        $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
                        $cascadeSourcesCount = count( $cascadeSources );
                        if ( $cascadeSourcesCount > 0 ) {
                                # Explain, and list the titles responsible
-                               foreach( $cascadeSources as $page ) {
+                               foreach ( $cascadeSources as $page ) {
                                        $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
                                }
                        }
                }
                if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
                        LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
-                               array(  'lim' => 1,
+                               array( 'lim' => 1,
                                        'showIfEmpty' => false,
                                        'msgKey' => array( 'titleprotectedwarning' ),
                                        'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
                        $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
                                array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
                } else {
-                       if( !wfMessage('longpage-hint')->isDisabled() ) {
+                       if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
                                $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
                                        array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
                                );
         *
         * @return array An array in the format array( $label, $input )
         */
-       function getSummaryInput($summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null) {
-               //Note: the maxlength is overriden in JS to 250 and to make it use UTF-8 bytes, not characters.
-               $inputAttrs = ( is_array($inputAttrs) ? $inputAttrs : array() ) + array(
+       function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
+               // Note: the maxlength is overriden in JS to 250 and to make it use UTF-8 bytes, not characters.
+               $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
                        'id' => 'wpSummary',
                        'maxlength' => '200',
                        'tabindex' => '1',
                        'spellcheck' => 'true',
                ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
  
-               $spanLabelAttrs = ( is_array($spanLabelAttrs) ? $spanLabelAttrs : array() ) + array(
+               $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
                        'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
                        'id' => "wpSummaryLabel"
                );
                }
                $summary = $wgContLang->recodeForEdit( $summary );
                $labelText = wfMsgExt( $isSubjectPreview ? 'subject' : 'summary', 'parseinline' );
-               list($label, $input) = $this->getSummaryInput($summary, $labelText, array( 'class' => $summaryClass ), array());
-               $wgOut->addHTML("{$label} {$input}");
+               list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
+               $wgOut->addHTML( "{$label} {$input}" );
        }
  
        /**
  HTML
                );
                if ( !$this->checkUnicodeCompliantBrowser() )
-                       $wgOut->addHTML(Html::hidden( 'safemode', '1' ));
+                       $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
        }
  
        protected function showFormAfterText() {
         * The $textoverride method can be used by subclasses overriding showContentForm
         * to pass back to this method.
         *
-        * @param $customAttribs An array of html attributes to use in the textarea
+        * @param $customAttribs array of html attributes to use in the textarea
         * @param $textoverride String: optional text to override $this->textarea1 with
         */
        protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
                $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( $text );
-               if ( strval($wikitext) !== '' ) {
+               $wikitext = $this->safeUnicodeOutput( $content );
+               if ( strval( $wikitext ) !== '' ) {
                        // Ensure there's a newline at the end, otherwise adding lines
                        // is awkward.
                        // But don't add a newline if the ext is empty, or Firefox in XHTML
  
                $wgOut->addHTML( '</div>' );
  
-               if ( $this->formtype == 'diff') {
+               if ( $this->formtype == 'diff' ) {
                        $this->showDiff();
                }
        }
         */
        protected function showPreview( $text ) {
                global $wgOut;
-               if ( $this->mTitle->getNamespace() == NS_CATEGORY) {
+               if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
                        $this->mArticle->openShowCategory();
                }
                # This hook seems slightly odd here, but makes things more
                # consistent for extensions.
-               wfRunHooks( 'OutputPageBeforeHTML',array( &$wgOut, &$text ) );
+               wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
                $wgOut->addHTML( $text );
                if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
                        $this->mArticle->closeShowCategory();
         * save and then make a comparison.
         */
        function showDiff() {
 -              global $wgUser, $wgContLang, $wgParser, $wgOut;
 +              global $wgUser, $wgContLang, $wgOut;
 +
 +              $oldContent = $this->getOriginalContent();
 +
 +              $textboxContent = ContentHandler::makeContent( $this->textbox1, $this->getTitle(),
 +                                                                                                              $this->content_model, $this->content_format ); #XXX: handle parse errors ?
  
 +              $newContent = $this->mArticle->replaceSectionContent(
 +                                                                                      $this->section, $textboxContent,
 +                                                                                      $this->summary, $this->edittime );
+               $oldtitlemsg = 'currentrev';
+               # if message does not exist, show diff against the preloaded default
+               if( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
+                       $oldtext = $this->mTitle->getDefaultMessageText();
+                       if( $oldtext !== false ) {
+                               $oldtitlemsg = 'defaultmessagetext';
+                       }
+               } else {
+                       $oldtext = $this->mArticle->getRawText();
+               }
+               $newtext = $this->mArticle->replaceSection(
+                       $this->section, $this->textbox1, $this->summary, $this->edittime );
  
 +              # hanlde legacy text-based hook
 +              $newtext_orig = $newContent->serialize( $this->content_format );
 +              $newtext = $newtext_orig; #clone
                wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
  
 +              if ( $newtext != $newtext_orig ) {
 +                      #if the hook changed the text, create a new Content object accordingly.
 +                      $newContent = ContentHandler::makeContent( $newtext, $this->getTitle(), $newContent->getModelName() ); #XXX: handle parse errors ?
 +              }
 +
 +              wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) ); #FIXME: document new hook
 +
                $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
 -              $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
 +              $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
  
 +              if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
 +                      $oldtitle = wfMsgExt( 'currentrev', array( 'parseinline' ) );
+               if ( $oldtext !== false  || $newtext != '' ) {
+                       $oldtitle = wfMsgExt( $oldtitlemsg, array( 'parseinline' ) );
                        $newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) );
  
 -                      $de = new DifferenceEngine( $this->mArticle->getContext() );
 -                      $de->setText( $oldtext, $newtext );
 +                      $de = $oldContent->getContentHandler()->getDifferenceEngine( $this->mArticle->getContext() );
 +                      $de->setContent( $oldContent, $newContent );
 +
                        $difftext = $de->getDiff( $oldtitle, $newtitle );
                        $de->showDiffStyle();
                } else {
        protected function showTosSummary() {
                $msg = 'editpage-tos-summary';
                wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
-               if( !wfMessage( $msg )->isDisabled() ) {
+               if ( !wfMessage( $msg )->isDisabled() ) {
                        global $wgOut;
                        $wgOut->addHTML( '<div class="mw-tos-summary">' );
                        $wgOut->addWikiMsg( $msg );
                wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) );
  
                return "<div id=\"editpage-copywarn\">\n" .
-                       call_user_func_array("wfMsgNoTrans", $copywarnMsg) . "\n</div>";
+                       call_user_func_array( "wfMsgNoTrans", $copywarnMsg ) . "\n</div>";
        }
  
        protected function showStandardInputs( &$tabindex = 2 ) {
                        $cancel .= wfMsgExt( 'pipe-separator' , 'escapenoentities' );
                }
                $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) );
-               $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
-                       htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
+               $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
+                       htmlspecialchars( wfMsg( 'edithelp' ) ) . '</a> ' .
                        htmlspecialchars( wfMsg( 'newwindow' ) );
                $wgOut->addHTML( "      <span class='editHelp'>{$cancel}{$edithelp}</span>\n" );
                $wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" );
                if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
  
 -                      $de = new DifferenceEngine( $this->mArticle->getContext() );
 -                      $de->setText( $this->textbox2, $this->textbox1 );
 +                      $content1 = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format ); #XXX: handle parse errors?
 +                      $content2 = ContentHandler::makeContent( $this->textbox2, $this->getTitle(), $this->content_model, $this->content_format ); #XXX: handle parse errors?
 +
 +                      $handler = ContentHandler::getForModelName( $this->content_model );
 +                      $de = $handler->getDifferenceEngine( $this->mArticle->getContext() );
 +                      $de->setContent( $content2, $content1 );
                        $de->showDiff( wfMsgExt( 'yourtext', 'parseinline' ), wfMsg( 'storedversion' ) );
  
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
                        array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
                );
                // Quick paranoid permission checks...
-               if( is_object( $data ) ) {
-                       if( $data->log_deleted & LogPage::DELETED_USER )
+               if ( is_object( $data ) ) {
+                       if ( $data->log_deleted & LogPage::DELETED_USER )
                                $data->user_name = wfMsgHtml( 'rev-deleted-user' );
-                       if( $data->log_deleted & LogPage::DELETED_COMMENT )
+                       if ( $data->log_deleted & LogPage::DELETED_COMMENT )
                                $data->log_comment = wfMsgHtml( 'rev-deleted-comment' );
                }
                return $data;
                        return $parsedNote;
                }
  
 +        try {
 +            $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
 +
 +            if ( $this->mTriedSave && !$this->mTokenOk ) {
 +                if ( $this->mTokenOkExceptSuffix ) {
 +                    $note = wfMsg( 'token_suffix_mismatch' );
 +                } else {
 +                    $note = wfMsg( 'session_fail_preview' );
 +                }
 +            } elseif ( $this->incompleteForm ) {
 +                $note = wfMsg( 'edit_form_incomplete' );
 +            } elseif ( $this->isCssJsSubpage || $this->mTitle->isCssOrJsPage() ) {
 +                # if this is a CSS or JS page used in the UI, show a special notice
 +                # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
 +
 +                if( $this->mTitle->isCssJsSubpage() ) {
 +                    $level = 'user';
 +                } elseif( $this->mTitle->isCssOrJsPage() ) {
 +                    $level = 'site';
 +                } else {
 +                    $level = false;
 +                }
 +
 +                if ( $content->getModelName() == CONTENT_MODEL_CSS ) {
 +                    $format = 'css';
 +                } elseif ( $content->getModelName() == CONTENT_MODEL_JAVASCRIPT ) {
 +                    $format = 'js';
 +                } else {
 +                    $format = false;
 +                }
 +
 +                # Used messages to make sure grep find them:
 +                # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
 +                if( $level && $format ) {
 +                    $note = "<div id='mw-{$level}{$format}preview'>" . wfMsg( "{$level}{$format}preview" ) . "</div>";
 +                } else {
 +                    $note = wfMsg( 'previewnote' );
 +                }
 +            } else {
 +                $note = wfMsg( 'previewnote' );
 +            }
 +
 +            $parserOptions = ParserOptions::newFromUser( $wgUser );
 +            $parserOptions->setEditSection( false );
 +            $parserOptions->setTidy( true );
 +            $parserOptions->setIsPreview( true );
 +            $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
 +
 +            $rt = $content->getRedirectChain();
 +
 +            if ( $rt ) {
 +                $previewHTML = $this->mArticle->viewRedirect( $rt, false );
 +            } else {
 +
 +                # 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 );
 +                }
 +
 +                $toparse_orig = $content->serialize( $this->content_format );
 +                $toparse = $toparse_orig;
 +                wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
 +
 +                if ( $toparse !== $toparse_orig ) {
 +                    #hook changed the text, create new Content object
 +                    $content = ContentHandler::makeContent( $toparse, $this->getTitle(), $this->content_model, $this->content_format );
 +                }
 +
 +                wfRunHooks( 'EditPageGetPreviewContent', array( $this, &$content ) ); # FIXME: document new hook
 +
 +                $parserOptions->enableLimitReport();
 +
 +                #XXX: For CSS/JS pages, we should have called the ShowRawCssJs hook here. But it's now deprecated, so never mind
 +                $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
 +                $parserOutput = $content->getParserOutput( $this->mTitle, 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) {
 +            $note .= "\n\n" . wfMsg('content-failed-to-parse', $this->content_model, $this->content_format, $ex->getMessage() );
 +            $previewHTML = '';
 +        }
+               if ( $this->mTriedSave && !$this->mTokenOk ) {
+                       if ( $this->mTokenOkExceptSuffix ) {
+                               $note = wfMsg( 'token_suffix_mismatch' );
+                       } else {
+                               $note = wfMsg( 'session_fail_preview' );
+                       }
+               } elseif ( $this->incompleteForm ) {
+                       $note = wfMsg( 'edit_form_incomplete' );
+               } else {
+                       $note = wfMsg( 'previewnote' );
+               }
+               $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
+               # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
+               if ( $this->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
+                       if ( $level ) {
+                               if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
+                                       $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
+                                       $class = "mw-code mw-css";
+                               } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
+                                       $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
+                                       $class = "mw-code mw-js";
+                               } else {
+                                       throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+                               }
+                       }
+                       $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
+                       $previewHTML = $parserOutput->mText;
+                       $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
+               } else {
+                       $toparse = $this->textbox1;
  
-               if( $this->isConflict ) {
+                       # If we're adding a comment, we need to show the
+                       # summary as the headline
+                       if ( $this->section == "new" && $this->summary != "" ) {
+                               $toparse = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $toparse;
+                       }
+                       wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+                       $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 ( count( $parserOutput->getWarnings() ) ) {
+                               $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+                       }
+               }
+               if ( $this->isConflict ) {
                        $conflict = '<h2 id="mw-previewconflict">' . htmlspecialchars( wfMsg( 'previewconflict' ) ) . "</h2>\n";
                } else {
                        $conflict = '<hr />';
  
                $pageLang = $this->mTitle->getPageLanguage();
                $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
-                       'class' => 'mw-content-'.$pageLang->getDir() );
+                       'class' => 'mw-content-' . $pageLang->getDir() );
                $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
  
                wfProfileOut( __METHOD__ );
                        if ( !isset( $this->mParserOutput ) ) {
                                return $templates;
                        }
-                       foreach( $this->mParserOutput->getTemplates() as $ns => $template) {
-                               foreach( array_keys( $template ) as $dbk ) {
-                                       $templates[] = Title::makeTitle($ns, $dbk);
+                       foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
+                               foreach ( array_keys( $template ) as $dbk ) {
+                                       $templates[] = Title::makeTitle( $ns, $dbk );
                                }
                        }
                        return $templates;
                                'tip'    => wfMsg( 'media_tip' ),
                                'key'    => 'M'
                        ) : false,
-                       $wgUseTeX ?     array(
+                       $wgUseTeX ? array(
                                'image'  => $wgLang->getImageFile( 'button-math' ),
                                'id'     => 'mw-editbutton-math',
                                'open'   => "<math>",
         * Returns an array of html code of the following checkboxes:
         * minor and watch
         *
-        * @param $tabindex Current tabindex
+        * @param $tabindex int Current tabindex
         * @param $checked Array of checkbox => bool, where bool indicates the checked
         *                 status of the checkbox
         *
         * Returns an array of html code of the following buttons:
         * save, diff, preview and live
         *
-        * @param $tabindex Current tabindex
+        * @param $tabindex int Current tabindex
         *
         * @return array
         */
                        'tabindex'  => ++$tabindex,
                        'value'     => wfMsg( 'savearticle' ),
                        'accesskey' => wfMsg( 'accesskey-save' ),
-                       'title'     => wfMsg( 'tooltip-save' ).' ['.wfMsg( 'accesskey-save' ).']',
+                       'title'     => wfMsg( 'tooltip-save' ) . ' [' . wfMsg( 'accesskey-save' ) . ']',
                );
-               $buttons['save'] = Xml::element('input', $temp, '');
+               $buttons['save'] = Xml::element( 'input', $temp, '' );
  
                ++$tabindex; // use the same for preview and live preview
                $temp = array(
                wfDeprecated( __METHOD__, '1.19' );
                global $wgUser;
  
-               throw new UserBlockedError( $wgUser->mBlock );
+               throw new UserBlockedError( $wgUser->getBlock() );
        }
  
        /**
        /**
         * Produce the stock "your edit contains spam" page
         *
-        * @param $match Text which triggered one or more filters
+        * @param $match string Text which triggered one or more filters
         * @deprecated since 1.17 Use method spamPageWithContent() instead
         */
        static function spamPage( $match = false ) {
        /**
         * Show "your edit contains spam" page with your diff and text
         *
-        * @param $match Text which triggered one or more filters
+        * @param $match string|Array|bool Text (or array of texts) which triggered one or more filters
         */
        public function spamPageWithContent( $match = false ) {
-               global $wgOut;
+               global $wgOut, $wgLang;
                $this->textbox2 = $this->textbox1;
  
+               if( is_array( $match ) ){
+                       $match = $wgLang->listToText( $match );
+               }
                $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
  
                $wgOut->addHTML( '<div id="spamprotected">' );
                $wgOut->addHTML( '</div>' );
  
                $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
 -              $this->showDiff();
 +
 +              $handler = ContentHandler::getForTitle( $this->getTitle() );
 +              $de = $handler->getDifferenceEngine( $this->mArticle->getContext() );
 +
 +              $content2 = ContentHandler::makeContent( $this->textbox2, $this->getTitle(), $this->content_model, $this->content_format ); #XXX: handle parse errors?
 +              $de->setContent( $this->getCurrentContent(), $content2 );
 +
 +              $de->showDiff( wfMsg( "storedversion" ), wfMsgExt( 'yourtext', 'parseinline' ) );
  
                $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
                $this->showTextbox2();
                }
                $currentbrowser = $_SERVER["HTTP_USER_AGENT"];
                foreach ( $wgBrowserBlackList as $browser ) {
-                       if ( preg_match($browser, $currentbrowser) ) {
+                       if ( preg_match( $browser, $currentbrowser ) ) {
                                return false;
                        }
                }
                $bytesleft = 0;
                $result = "";
                $working = 0;
-               for( $i = 0; $i < strlen( $invalue ); $i++ ) {
+               for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
                        $bytevalue = ord( $invalue[$i] );
-                       if ( $bytevalue <= 0x7F ) { //0xxx xxxx
+                       if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
                                $result .= chr( $bytevalue );
                                $bytesleft = 0;
-                       } elseif ( $bytevalue <= 0xBF ) { //10xx xxxx
+                       } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
                                $working = $working << 6;
-                               $working += ($bytevalue & 0x3F);
+                               $working += ( $bytevalue & 0x3F );
                                $bytesleft--;
                                if ( $bytesleft <= 0 ) {
                                        $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
                                }
-                       } elseif ( $bytevalue <= 0xDF ) { //110x xxxx
+                       } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
                                $working = $bytevalue & 0x1F;
                                $bytesleft = 1;
-                       } elseif ( $bytevalue <= 0xEF ) { //1110 xxxx
+                       } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
                                $working = $bytevalue & 0x0F;
                                $bytesleft = 2;
-                       } else { //1111 0xxx
+                       } else { // 1111 0xxx
                                $working = $bytevalue & 0x07;
                                $bytesleft = 3;
                        }
         */
        function unmakesafe( $invalue ) {
                $result = "";
-               for( $i = 0; $i < strlen( $invalue ); $i++ ) {
-                       if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i+3] != '0' ) ) {
+               for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
+                       if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
                                $i += 3;
                                $hexstring = "";
                                do {
                                        $hexstring .= $invalue[$i];
                                        $i++;
-                               } while( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
+                               } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
  
                                // Do some sanity checks. These aren't needed for reversability,
                                // but should help keep the breakage down if the editor
                                // breaks one of the entities whilst editing.
+                               if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
+                                       $codepoint = hexdec( $hexstring );
 +                              if ( (substr($invalue,$i,1)==";") and (strlen($hexstring) <= 6) ) {
 +                                      $codepoint = hexdec($hexstring);
                                        $result .= codepointToUtf8( $codepoint );
                                } else {
                                        $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
diff --combined includes/FeedUtils.php
@@@ -57,18 -57,17 +57,17 @@@ class FeedUtils 
                $timestamp = wfTimestamp( TS_MW, $row->rc_timestamp );
                $actiontext = '';
                if( $row->rc_type == RC_LOG ) {
-                       if( $row->rc_deleted & LogPage::DELETED_ACTION ) {
-                               $actiontext = wfMsgHtml('rev-deleted-event');
-                       } else {
-                               $actiontext = LogPage::actionText( $row->rc_log_type, $row->rc_log_action,
-                                       $titleObj, RequestContext::getMain()->getSkin(), LogPage::extractParams($row->rc_params,true,true) );
-                       }
+                       $rcRow = (array)$row; // newFromRow() only accepts arrays for RC rows
+                       $actiontext = LogFormatter::newFromRow( $rcRow )->getActionText();
                }
                return self::formatDiffRow( $titleObj,
                        $row->rc_last_oldid, $row->rc_this_oldid,
                        $timestamp,
-                       ($row->rc_deleted & Revision::DELETED_COMMENT) ? wfMsgHtml('rev-deleted-comment') : $row->rc_comment,
-                       $actiontext );
+                       ($row->rc_deleted & Revision::DELETED_COMMENT)
+                               ? wfMsgHtml('rev-deleted-comment') 
+                               : $row->rc_comment,
+                       $actiontext 
+               );
        }
  
        /**
                        #       $wgLang->time( $timestamp ) ),
                        #       wfMsg( 'currentrev' ) );
  
+                       $diffText = '';
                        // Don't bother generating the diff if we won't be able to show it
                        if ( $wgFeedDiffCutoff > 0 ) {
 -                              $de = new DifferenceEngine( $title, $oldid, $newid );
 +                $contentHandler = ContentHandler::getForTitle( $title );
 +                $de = $contentHandler->getDifferenceEngine( $title, $oldid, $newid );
                                $diffText = $de->getDiff(
                                        wfMsg( 'previousrevision' ), // hack
                                        wfMsg( 'revisionasof',
         * @param $title Title object: used to generate the diff URL
         * @param $newid Integer newid for this diff
         * @param $oldid Integer|null oldid for the diff. Null means it is a new article
+        * @return string
         */
        protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
                $queryParameters = ($oldid == null)
diff --combined includes/ImagePage.php
@@@ -30,6 -30,7 +30,7 @@@ class ImagePage extends Article 
        /**
         * Constructor from a page id
         * @param $id Int article ID to load
+        * @returnImagePage|null
         */
        public static function newFromID( $id ) {
                $t = Title::newFromID( $id );
                        }
                }
  
-               $this->showRedirectedFromHeader();
                if ( $wgShowEXIF && $this->displayImg->exists() ) {
                        // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
                        $formattedMetadata = $this->displayImg->formatMetadata();
                        $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
                                'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
                                'class' => 'mw-content-'.$pageLang->getDir() ) ) );
 -                      parent::view();
 +
 +            parent::view(); #FIXME: use ContentHandler::makeArticle() !!
 +
                        $wgOut->addHTML( Xml::closeElement( 'div' ) );
                } else {
                        # Just need to set the right headers
                return $r;
        }
  
 -      /**
 -       * Overloading Article's getContent method.
 -       *
 -       * Omit noarticletext if sharedupload; text will be fetched from the
 -       * shared upload server if possible.
 -       * @return string
 -       */
 -      public function getContent() {
 -              $this->loadFile();
 -              if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
 -                      return '';
 -              }
 -              return parent::getContent();
 -      }
 +    /**
 +     * Overloading Article's getContentObject method.
 +     *
 +     * Omit noarticletext if sharedupload; text will be fetched from the
 +     * shared upload server if possible.
 +     * @return string
 +     */
 +    public function getContentObject() {
 +        $this->loadFile();
 +        if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
 +            return null;
 +        }
 +        return parent::getContentObject();
 +    }
  
        protected function openShowImage() {
                global $wgOut, $wgUser, $wgImageLimits, $wgRequest,
                $max = $wgImageLimits[$sizeSel];
                $maxWidth = $max[0];
                $maxHeight = $max[1];
-               $dirmark = $wgLang->getDirMark();
+               $dirmark = $wgLang->getDirMarkEntity();
  
                if ( $this->displayImg->exists() ) {
                        # image
  
                                if ( !$this->displayImg->isSafeFile() ) {
                                        $warning = wfMsgNoTrans( 'mediawarning' );
+                                       // dirmark is needed here to separate the file name, which
+                                       // most likely ends in Latin characters, from the description,
+                                       // which may begin with the file type. In RTL environment
+                                       // this will get messy.
+                                       // The dirmark, however, must not be immediately adjacent
+                                       // to the filename, because it can get copied with it.
+                                       // See bug 25277.
                                        $wgOut->addWikiText( <<<EOT
- <div class="fullMedia"><span class="dangerousLink">{$medialink}</span>$dirmark <span class="fileInfo">$longDesc</span></div>
+ <div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
  <div class="mediaWarning">$warning</div>
  EOT
                                                );
                                } else {
                                        $wgOut->addWikiText( <<<EOT
- <div class="fullMedia">{$medialink}{$dirmark} <span class="fileInfo">$longDesc</span>
+ <div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
  </div>
  EOT
                                        );
                        }
                } else {
                        # Image does not exist
+                       if ( !$this->getID() ) {
+                               # No article exists either
+                               # Show deletion log to be consistent with normal articles
+                               LogEventsList::showLogExtract(
+                                       $wgOut,
+                                       array( 'delete', 'move' ),
+                                       $this->getTitle()->getPrefixedText(),
+                                       '',
+                                       array(  'lim' => 10,
+                                               'conds' => array( "log_action != 'revision'" ),
+                                               'showIfEmpty' => false,
+                                               'msgKey' => array( 'moveddeleted-notice' )
+                                       )
+                               );
+                       }
                        if ( $wgEnableUploads && $wgUser->isAllowed( 'upload' ) ) {
                                // Only show an upload link if the user can upload
                                $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
diff --combined includes/LinksUpdate.php
   *
   * @todo document (e.g. one-sentence top-level class description).
   */
 -class LinksUpdate {
 +class LinksUpdate extends SecondaryDBDataUpdate {
  
        /**@{{
         * @private
         */
        var $mId,            //!< Page ID of the article linked from
 -              $mTitle,         //!< Title object of the article linked from
 -              $mParserOutput,  //!< Parser output
 -              $mLinks,         //!< Map of title strings to IDs for the links in the document
 +        $mTitle,         //!< Title object of the article linked from
 +        $mParserOutput,  //!< Whether to queue jobs for recursive update
 +        $mLinks,         //!< Map of title strings to IDs for the links in the document
                $mImages,        //!< DB keys of the images used, in the array key only
                $mTemplates,     //!< Map of title strings to IDs for the template references, including broken ones
                $mExternals,     //!< URLs of external links, array key only
                $mCategories,    //!< Map of category names to sort keys
                $mInterlangs,    //!< Map of language codes to titles
                $mProperties,    //!< Map of arbitrary name to value
 -              $mDb,            //!< Database connection reference
 -              $mOptions,       //!< SELECT options to be used (array)
                $mRecursive;     //!< Whether to queue jobs for recursive updates
        /**@}}*/
  
         * @param $recursive Boolean: queue jobs for recursive updates?
         */
        function __construct( $title, $parserOutput, $recursive = true ) {
 -              global $wgAntiLockFlags;
 +        parent::__construct( );
  
 -              if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
 -                      $this->mOptions = array();
 -              } else {
 -                      $this->mOptions = array( 'FOR UPDATE' );
 -              }
 -              $this->mDb = wfGetDB( DB_MASTER );
 +        if ( !is_object( $title ) ) {
 +            throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
 +                "Please see Article::editUpdates() for an invocation example.\n" );
 +        }
  
 -              if ( !is_object( $title ) ) {
 -                      throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
++              if ( !is_object( $title ) ) {
++                      throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
+                               "Please see WikiPage::doEditUpdates() for an invocation example.\n" );
 -              }
 -              $this->mTitle = $title;
 -              $this->mId = $title->getArticleID();
++              }
 +        $this->mTitle = $title;
 +        $this->mId = $title->getArticleID();
 +
 +        $this->mParserOutput = $parserOutput;
  
 -              $this->mParserOutput = $parserOutput;
                $this->mLinks = $parserOutput->getLinks();
                $this->mImages = $parserOutput->getImages();
                $this->mTemplates = $parserOutput->getTemplates();
                wfProfileOut( __METHOD__ );
        }
  
 -      /**
 -       * Invalidate the cache of a list of pages from a single namespace
 -       *
 -       * @param $namespace Integer
 -       * @param $dbkeys Array
 -       */
 -      function invalidatePages( $namespace, $dbkeys ) {
 -              if ( !count( $dbkeys ) ) {
 -                      return;
 -              }
 -
 -              /**
 -               * Determine which pages need to be updated
 -               * This is necessary to prevent the job queue from smashing the DB with
 -               * large numbers of concurrent invalidations of the same page
 -               */
 -              $now = $this->mDb->timestamp();
 -              $ids = array();
 -              $res = $this->mDb->select( 'page', array( 'page_id' ),
 -                      array(
 -                              'page_namespace' => $namespace,
 -                              'page_title IN (' . $this->mDb->makeList( $dbkeys ) . ')',
 -                              'page_touched < ' . $this->mDb->addQuotes( $now )
 -                      ), __METHOD__
 -              );
 -              foreach ( $res as $row ) {
 -                      $ids[] = $row->page_id;
 -              }
 -              if ( !count( $ids ) ) {
 -                      return;
 -              }
 -
 -              /**
 -               * Do the update
 -               * We still need the page_touched condition, in case the row has changed since
 -               * the non-locking select above.
 -               */
 -              $this->mDb->update( 'page', array( 'page_touched' => $now ),
 -                      array(
 -                              'page_id IN (' . $this->mDb->makeList( $ids ) . ')',
 -                              'page_touched < ' . $this->mDb->addQuotes( $now )
 -                      ), __METHOD__
 -              );
 -      }
 -
        /**
         * @param $cats
         */
                $this->invalidatePages( NS_FILE, array_keys( $images ) );
        }
  
 -      /**
 -       * @param $table
 -       * @param $insertions
 -       * @param $fromField
 -       */
 -      private function dumbTableUpdate( $table, $insertions, $fromField ) {
 -              $this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ );
 -              if ( count( $insertions ) ) {
 -                      # The link array was constructed without FOR UPDATE, so there may
 -                      # be collisions.  This may cause minor link table inconsistencies,
 -                      # which is better than crippling the site with lock contention.
 -                      $this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) );
 -              }
 -      }
 +    /**
 +     * @param $table
 +     * @param $insertions
 +     * @param $fromField
 +     */
 +    private function dumbTableUpdate( $table, $insertions, $fromField ) {
 +        $this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ );
 +        if ( count( $insertions ) ) {
 +            # The link array was constructed without FOR UPDATE, so there may
 +            # be collisions.  This may cause minor link table inconsistencies,
 +            # which is better than crippling the site with lock contention.
 +            $this->mDb->insert( $table, $insertions, __METHOD__, array( 'IGNORE' ) );
 +        }
 +    }
  
        /**
         * Update a table by doing a delete query then an insert query
                return $arr;
        }
  
 -      /**
 -       * Return the title object of the page being updated
 -       * @return Title
 -       */
 -      public function getTitle() {
 -              return $this->mTitle;
 -      }
 -
 -      /**
 -       * Returns parser output
 -       * @since 1.19
 -       * @return ParserOutput
 -       */
 -      public function getParserOutput() {
 -              return $this->mParserOutput;
 -      }
 +    /**
 +     * Return the title object of the page being updated
 +     * @return Title
 +     */
 +    public function getTitle() {
 +        return $this->mTitle;
 +    }
 +
 +    /**
 +     * Returns parser output
 +     * @since 1.19
 +     * @return ParserOutput
 +     */
 +    public function getParserOutput() {
 +        return $this->mParserOutput;
 +    }
  
        /**
         * Return the list of images used as generated by the parser
diff --combined includes/Revision.php
@@@ -20,10 -20,6 +20,10 @@@ class Revision 
        protected $mTextRow;
        protected $mTitle;
        protected $mCurrent;
 +    protected $mContentModelName;
 +    protected $mContentFormat;
 +    protected $mContent;
 +    protected $mContentHandler;
  
        const DELETED_TEXT = 1;
        const DELETED_COMMENT = 2;
                        'deleted'    => $row->ar_deleted,
                        'len'        => $row->ar_len,
                        'sha1'       => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
 +            'content_model' => isset( $row->ar_content_model ) ? $row->ar_content_model : null,
 +            'content_format'  => isset( $row->ar_content_format ) ? $row->ar_content_format : null,
                );
                if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
                        // Pre-1.5 ar_text row
        /**
         * Return the list of revision fields that should be selected to create
         * a new revision.
+        * @return array
         */
        public static function selectFields() {
                return array(
                        'rev_deleted',
                        'rev_len',
                        'rev_parent_id',
 -                      'rev_sha1'
 +                      'rev_sha1',
 +                      'rev_content_format',
 +                      'rev_content_model'
                );
        }
  
        /**
         * Return the list of text fields that should be selected to read the
         * revision text
+        * @return array
         */
        public static function selectTextFields() {
                return array(
  
        /**
         * Return the list of page fields that should be selected from page table
+        * @return array
         */
        public static function selectPageFields() {
                return array(
  
        /**
         * Return the list of user fields that should be selected from user table
+        * @return array
         */
        public static function selectUserFields() {
                return array( 'user_name' );
                                $this->mTitle = null;
                        }
  
 +            if( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) {
 +                $this->mContentModelName = null; # determine on demand if needed
 +            } else {
 +                $this->mContentModelName = strval( $row->rev_content_model );
 +            }
 +
 +            if( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) {
 +                $this->mContentFormat = null; # determine on demand if needed
 +            } else {
 +                $this->mContentFormat = strval( $row->rev_content_format );
 +            }
 +
                        // Lazy extraction...
                        $this->mText      = null;
                        if( isset( $row->old_text ) ) {
                        // Build a new revision to be saved...
                        global $wgUser; // ugh
  
 +
 +            # if we have a content object, use it to set the model and type
 +            if ( !empty( $row['content'] ) ) {
 +                if ( !empty( $row['text_id'] ) ) { #FIXME: when is that set? test with external store setup! check out insertOn()
 +                    throw new MWException( "Text already stored in external store (id {$row['text_id']}), can't serialize content object" );
 +                }
 +
 +                $row['content_model'] = $row['content']->getModelName();
 +                # note: mContentFormat is initializes later accordingly
 +                # note: content is serialized later in this method!
 +                # also set text to null?
 +            }
 +
                        $this->mId        = isset( $row['id']         ) ? intval( $row['id']         ) : null;
                        $this->mPage      = isset( $row['page']       ) ? intval( $row['page']       ) : null;
                        $this->mTextId    = isset( $row['text_id']    ) ? intval( $row['text_id']    ) : null;
                        $this->mParentId  = isset( $row['parent_id']  ) ? intval( $row['parent_id']  ) : null;
                        $this->mSha1      = isset( $row['sha1']  )      ? strval( $row['sha1']  )      : null;
  
 +            $this->mContentModelName = isset( $row['content_model']  )  ? strval( $row['content_model'] ) : null;
 +            $this->mContentFormat    = isset( $row['content_format']  )   ? strval( $row['content_format'] ) : null;
 +
                        // Enforce spacing trimming on supplied text
                        $this->mComment   = isset( $row['comment']    ) ?  trim( strval( $row['comment'] ) ) : null;
                        $this->mText      = isset( $row['text']       ) ? rtrim( strval( $row['text']    ) ) : null;
                        $this->mCurrent   = false;
                        # If we still have no length, see it we have the text to figure it out
                        if ( !$this->mSize ) {
 +                #XXX: my be inconsistent with the notion of "size" use for the present content model
                                $this->mSize = is_null( $this->mText ) ? null : strlen( $this->mText );
                        }
                        # Same for sha1
                        if ( $this->mSha1 === null ) {
                                $this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
                        }
 +
 +            $this->getContentModelName(); # force lazy init
 +            $this->getContentFormat();      # force lazy init
 +
 +            # if we have a content object, serialize it, overriding mText
 +            if ( !empty( $row['content'] ) ) {
 +                $handler = $this->getContentHandler();
 +                $this->mText = $handler->serialize( $row['content'], $this->getContentFormat() );
 +            }
                } else {
                        throw new MWException( 'Revision constructor passed invalid row format.' );
                }
                $this->mUnpatrolled = null;
 +
 +        #FIXME: add patch for ar_content_format, ar_content_model, rev_content_format, rev_content_model to installer
 +        #FIXME: add support for ar_content_format, ar_content_model, rev_content_format, rev_content_model to API
        }
  
        /**
        /**
         * Get parent revision ID (the original previous page revision)
         *
-        * @return Integer
+        * @return Integer|null
         */
        public function getParentId() {
                return $this->mParentId;
                $dbr = wfGetDB( DB_SLAVE );
                $row = $dbr->selectRow(
                        array( 'page', 'revision' ),
-                       array( 'page_namespace', 'page_title' ),
+                       self::selectPageFields(),
                        array( 'page_id=rev_page',
                                   'rev_id' => $this->mId ),
-                       'Revision::getTitle' );
-               if( $row ) {
-                       $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
+                       __METHOD__ );
+               if ( $row ) {
+                       $this->mTitle = Title::newFromRow( $row );
                }
                return $this->mTitle;
        }
         * @param $user User object to check for, only if FOR_THIS_USER is passed
         *              to the $audience parameter
         * @return String
 +     * @deprectaed in 1.20, use getContent() instead
         */
 -      public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
 -              if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
 -                      return '';
 -              } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
 -                      return '';
 -              } else {
 -                      return $this->getRawText();
 -              }
 +      public function getText( $audience = self::FOR_PUBLIC, User $user = null ) { #FIXME: deprecated, replace usage! #FIXME: used a LOT!
 +        wfDeprecated( __METHOD__, '1.20' );
 +
 +        $content = $this->getContent();
 +        return ContentHandler::getContentText( $content ); # returns the raw content text, if applicable
        }
  
 +    /**
 +     * Fetch revision content if it's available to the specified audience.
 +     * If the specified audience does not have the ability to view this
 +     * revision, null will be returned.
 +     *
 +     * @param $audience Integer: one of:
 +     *      Revision::FOR_PUBLIC       to be displayed to all users
 +     *      Revision::FOR_THIS_USER    to be displayed to $wgUser
 +     *      Revision::RAW              get the text regardless of permissions
 +     * @param $user User object to check for, only if FOR_THIS_USER is passed
 +     *              to the $audience parameter
 +     * @return Content
 +     */
 +    public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) {
 +        if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
 +            return null;
 +        } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
 +            return null;
 +        } else {
 +            return $this->getContentInternal();
 +        }
 +    }
 +
        /**
         * Alias for getText(Revision::FOR_THIS_USER)
         *
         *
         * @return String
         */
 -      public function getRawText() {
 -              if( is_null( $this->mText ) ) {
 -                      // Revision text is immutable. Load on demand:
 -                      $this->mText = $this->loadText();
 -              }
 -              return $this->mText;
 +      public function getRawText() { #FIXME: deprecated, replace usage!
 +              return $this->getText( self::RAW );
        }
  
 +    protected function getContentInternal() {
 +        if( is_null( $this->mContent ) ) {
 +            // Revision is immutable. Load on demand:
 +
 +            $handler = $this->getContentHandler();
 +            $format = $this->getContentFormat();
 +            $title = $this->getTitle();
 +
 +            if( is_null( $this->mText ) ) {
 +                // Load text on demand:
 +                $this->mText = $this->loadText();
 +            }
 +
 +            $this->mContent = is_null( $this->mText ) ? null : $handler->unserialize( $this->mText, $format );
 +        }
 +
 +        return $this->mContent;
 +    }
 +
 +    public function getContentModelName() {
 +        if ( !$this->mContentModelName ) {
 +            $title = $this->getTitle();
 +            $this->mContentModelName = ( $title ? $title->getContentModelName() : CONTENT_MODEL_WIKITEXT );
 +        }
 +
 +        return $this->mContentModelName;
 +    }
 +
 +    public function getContentFormat() {
 +        if ( !$this->mContentFormat ) {
 +            $handler = $this->getContentHandler();
 +            $this->mContentFormat = $handler->getDefaultFormat();
 +        }
 +
 +        return $this->mContentFormat;
 +    }
 +
 +    public function getContentHandler() {
 +        if ( !$this->mContentHandler ) {
 +            $title = $this->getTitle();
 +
 +            if ( $title ) $model = $title->getContentModelName();
 +            else $model = CONTENT_MODEL_WIKITEXT;
 +
 +            $this->mContentHandler = ContentHandler::getForModelName( $model );
 +
 +            #XXX: do we need to verify that mContentHandler supports mContentFormat?
 +            #     otherwise, a fixed content format may cause problems on insert.
 +        }
 +
 +        return $this->mContentHandler;
 +    }
 +
        /**
         * @return String
         */
                $rev_id = isset( $this->mId )
                        ? $this->mId
                        : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
 -              $dbw->insert( 'revision',
 -                      array(
 -                              'rev_id'         => $rev_id,
 -                              'rev_page'       => $this->mPage,
 -                              'rev_text_id'    => $this->mTextId,
 -                              'rev_comment'    => $this->mComment,
 -                              'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
 -                              'rev_user'       => $this->mUser,
 -                              'rev_user_text'  => $this->mUserText,
 -                              'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
 -                              'rev_deleted'    => $this->mDeleted,
 -                              'rev_len'        => $this->mSize,
 -                              'rev_parent_id'  => is_null( $this->mParentId )
 -                                      ? $this->getPreviousRevisionId( $dbw )
 -                                      : $this->mParentId,
 -                              'rev_sha1'       => is_null( $this->mSha1 )
 -                                      ? Revision::base36Sha1( $this->mText )
 -                                      : $this->mSha1
 -                      ), __METHOD__
 -              );
 +
 +        $row = array(
 +            'rev_id'         => $rev_id,
 +            'rev_page'       => $this->mPage,
 +            'rev_text_id'    => $this->mTextId,
 +            'rev_comment'    => $this->mComment,
 +            'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
 +            'rev_user'       => $this->mUser,
 +            'rev_user_text'  => $this->mUserText,
 +            'rev_timestamp'  => $dbw->timestamp( $this->mTimestamp ),
 +            'rev_deleted'    => $this->mDeleted,
 +            'rev_len'        => $this->mSize,
 +            'rev_parent_id'  => is_null( $this->mParentId )
 +                ? $this->getPreviousRevisionId( $dbw )
 +                : $this->mParentId,
 +            'rev_sha1'       => is_null( $this->mSha1 )
 +                ? Revision::base36Sha1( $this->mText )
 +                : $this->mSha1,
 +            'rev_content_model'       => $this->getContentModelName(),
 +            'rev_content_format'        => $this->getContentFormat(),
 +        );
 +
 +              $dbw->insert( 'revision', $row, __METHOD__ );
  
                $this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
  
  
                $current = $dbw->selectRow(
                        array( 'page', 'revision' ),
 -                      array( 'page_latest', 'rev_text_id', 'rev_len', 'rev_sha1' ),
 +                      array( 'page_latest', 'rev_text_id', 'rev_len', 'rev_sha1',
 +                    'rev_content_model', 'rev_content_format' ),
                        array(
                                'page_id' => $pageId,
                                'page_latest=rev_id',
                                'text_id'    => $current->rev_text_id,
                                'parent_id'  => $current->page_latest,
                                'len'        => $current->rev_len,
 -                              'sha1'       => $current->rev_sha1
 +                              'sha1'       => $current->rev_sha1,
 +                              'content_model'  => $current->rev_content_model,
 +                              'content_format'   => $current->rev_content_format
                                ) );
                } else {
                        $revision = null;
                        $id = 0;
                }
                $conds = array( 'rev_id' => $id );
-               $conds['rev_page'] = $title->getArticleId();
+               $conds['rev_page'] = $title->getArticleID();
                $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
                if ( $timestamp === false && wfGetLB()->getServerCount() > 1 ) {
                        # Not in slave, try master
         * @return Integer
         */
        static function countByTitle( $db, $title ) {
-               $id = $title->getArticleId();
+               $id = $title->getArticleID();
                if( $id ) {
                        return Revision::countByPageId( $db, $id );
                }
diff --combined includes/Title.php
@@@ -260,8 -260,7 +260,7 @@@ class Title 
         * Load Title object fields from a DB row.
         * If false is given, the title will be treated as non-existing.
         *
-        * @param $row Object|false database row
-        * @return void
+        * @param $row Object|bool database row
         */
        public function loadFromRow( $row ) {
                if ( $row ) { // page found
                        if ( isset( $row->page_is_redirect ) )
                                $this->mRedirect = (bool)$row->page_is_redirect;
                        if ( isset( $row->page_latest ) )
 -                              $this->mLatestID = (int)$row->page_latest;
 +                              $this->mLatestID = (int)$row->page_latest; # FIXME: whene3ver page_latest is updated, also update page_content_model
 +            if ( isset( $row->page_content_model ) )
 +                $this->mContentModelName = $row->page_content_model;
 +            else
 +                $this->mContentModelName = null; # initialized lazily in getContentModelName()
                } else { // page not found
                        $this->mArticleID = 0;
                        $this->mLength = 0;
                        $this->mRedirect = false;
                        $this->mLatestID = 0;
 +            $this->mContentModelName = null; # initialized lazily in getContentModelName()
                }
        }
  
                $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
                $t->mUrlform = wfUrlencode( $t->mDbkeyform );
                $t->mTextform = str_replace( '_', ' ', $title );
 +        $t->mContentModelName = null; # initialized lazily in getContentModelName()
                return $t;
        }
  
                return $this->mNamespace;
        }
  
 +    /**
 +     * Get the page's content model name
 +     *
 +     * @return Integer: Namespace index
 +     */
 +    public function getContentModelName() {
 +        if ( empty( $this->mContentModelName ) ) {
 +            $this->mContentModelName = ContentHandler::getDefaultModelFor( $this );
 +        }
 +
 +        return $this->mContentModelName;
 +    }
 +
 +    /**
 +     * Conveniance method for checking a title's content model name
 +     *
 +     * @param $name
 +     * @return true if $this->getContentModelName() == $name
 +     */
 +    public function hasContentModel( $name ) {
 +        return $this->getContentModelName() == $name;
 +    }
 +
        /**
         * Get the namespace text
         *
         * This is MUCH simpler than individually testing for equivilance
         * against both NS_USER and NS_USER_TALK, and is also forward compatible.
         * @since 1.19
+        * @return bool
         */
        public function hasSubjectNamespace( $ns ) {
                return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
         * @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.
 +     *
 +     * 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->isCssOrJsPage() );
        }
  
        /**
         * @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 ) );
        }
  
        /**
         * andthe wfArrayToCGI moved to getLocalURL();
         *
         * @since 1.19 (r105919)
+        * @return String
         */
        private static function fixUrlQueryArgs( $query, $query2 = false ) {
                if( $query2 !== false ) {
         * with action=render, $wgServer is prepended.
         *
  
-        * @param $query \twotypes{\string,\array} an optional query string,
+        * @param $query string|array an optional query string,
         *   not used for interwiki     links. Can be specified as an associative array as well,
         *   e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped).
         *   Some query patterns will trigger various shorturl path replacements.
         *
         * @see self::getLocalURL
         * @since 1.18
+        * @return string
         */
        public function escapeCanonicalURL( $query = '', $query2 = false ) {
                wfDeprecated( __METHOD__, '1.19' );
                        // Don't block the user from editing their own talk page unless they've been
                        // explicitly blocked from that too.
                } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
-                       $block = $user->mBlock;
+                       $block = $user->getBlock();
  
                        // This is from OutputPage::blockedPage
                        // Copied at r23888 by werdna
  
                        $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
                        $blockid = $block->getId();
-                       $blockExpiry = $user->mBlock->mExpiry;
-                       $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
+                       $blockExpiry = $block->getExpiry();
+                       $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true );
                        if ( $blockExpiry == 'infinity' ) {
                                $blockExpiry = wfMessage( 'infiniteblock' )->text();
                        } else {
                                $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
                        }
  
-                       $intended = strval( $user->mBlock->getTarget() );
+                       $intended = strval( $block->getTarget() );
  
                        $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
                                $blockid, $blockExpiry, $intended, $blockTimestamp );
                        $name = $this->getPrefixedText();
                        $dbName = $this->getPrefixedDBKey();
  
-                       // Check with and without underscores
+                       // Check for explicit whitelisting with and without underscores
                        if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
-                               # Check for explicit whitelisting
                                $whitelisted = true;
                        } elseif ( $this->getNamespace() == NS_MAIN ) {
                                # Old settings might have the title prefixed with
  
                if ( $oldFashionedRestrictions === null ) {
                        $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
-                               array( 'page_id' => $this->getArticleId() ), __METHOD__ );
+                               array( 'page_id' => $this->getArticleID() ), __METHOD__ );
                }
  
                if ( $oldFashionedRestrictions != '' ) {
                                $res = $dbr->select(
                                        'page_restrictions',
                                        '*',
-                                       array( 'pr_page' => $this->getArticleId() ),
+                                       array( 'pr_page' => $this->getArticleID() ),
                                        __METHOD__
                                );
  
         * @return Array of Title objects linking here
         */
        public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
-               $id = $this->getArticleId();
+               $id = $this->getArticleID();
  
                # If the page doesn't exist; there can't be any link from this page
                if ( !$id ) {
         * @return Array of Title the Title objects
         */
        public function getBrokenLinksFrom() {
-               if ( $this->getArticleId() == 0 ) {
+               if ( $this->getArticleID() == 0 ) {
                        # All links from article ID 0 are false positives
                        return array();
                }
                        array( 'page', 'pagelinks' ),
                        array( 'pl_namespace', 'pl_title' ),
                        array(
-                               'pl_from' => $this->getArticleId(),
+                               'pl_from' => $this->getArticleID(),
                                'page_namespace IS NULL'
                        ),
                        __METHOD__, array(),
                        RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
                }
  
-               $dbw->begin(); # If $file was a LocalFile, its transaction would have closed our own.
+               $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
                $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
                $protected = $this->isProtected();
  
                $err = $this->moveToInternal( $nt, $reason, $createRedirect );
                if ( is_array( $err ) ) {
                        # @todo FIXME: What about the File we have already moved?
-                       $dbw->rollback();
+                       $dbw->rollback( __METHOD__ );
                        return $err;
                }
  
                        WatchedItem::duplicateEntries( $this, $nt );
                }
  
-               $dbw->commit();
+               $dbw->commit( __METHOD__ );
  
                wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
                return true;
                $comment = $wgContLang->truncate( $comment, 255 );
  
                $oldid = $this->getArticleID();
-               $latest = $this->getLatestRevID();
  
                $dbw = wfGetDB( DB_MASTER );
  
                $newpage->updateRevisionOn( $dbw, $nullRevision );
  
                wfRunHooks( 'NewRevisionFromEditComplete',
-                       array( $newpage, $nullRevision, $latest, $wgUser ) );
+                       array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
  
                $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
  
+               if ( !$moveOverRedirect ) {
+                       WikiPage::onArticleCreate( $nt );
+               }
                # Recreate the redirect, this time in the other direction.
                if ( $redirectSuppressed ) {
                        WikiPage::onArticleDelete( $this );
                        // We don't know whether this function was called before
                        // or after moving the root page, so check both
                        // $this and $nt
-                       if ( $oldSubpage->getArticleId() == $this->getArticleId() ||
-                                       $oldSubpage->getArticleID() == $nt->getArticleId() )
+                       if ( $oldSubpage->getArticleID() == $this->getArticleID() ||
+                                       $oldSubpage->getArticleID() == $nt->getArticleID() )
                        {
                                // When moving a page to a subpage of itself,
                                // don't move it twice
  
                $data = array();
  
-               $titleKey = $this->getArticleId();
+               $titleKey = $this->getArticleID();
  
                if ( $titleKey === 0 ) {
                        return $data;
                $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                return $db->selectField( 'revision', 'rev_id',
                        array(
-                               'rev_page' => $this->getArticleId( $flags ),
+                               'rev_page' => $this->getArticleID( $flags ),
                                'rev_id < ' . intval( $revId )
                        ),
                        __METHOD__,
                $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                return $db->selectField( 'revision', 'rev_id',
                        array(
-                               'rev_page' => $this->getArticleId( $flags ),
+                               'rev_page' => $this->getArticleID( $flags ),
                                'rev_id > ' . intval( $revId )
                        ),
                        __METHOD__,
         * @return Revision|Null if page doesn't exist
         */
        public function getFirstRevision( $flags = 0 ) {
-               $pageId = $this->getArticleId( $flags );
+               $pageId = $this->getArticleID( $flags );
                if ( $pageId ) {
                        $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                        $row = $db->selectRow( 'revision', '*',
                if ( $this->mEstimateRevisions === null ) {
                        $dbr = wfGetDB( DB_SLAVE );
                        $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
-                               array( 'rev_page' => $this->getArticleId() ), __METHOD__ );
+                               array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
                }
  
                return $this->mEstimateRevisions;
                $dbr = wfGetDB( DB_SLAVE );
                return (int)$dbr->selectField( 'revision', 'count(*)',
                        array(
-                               'rev_page' => $this->getArticleId(),
+                               'rev_page' => $this->getArticleID(),
                                'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
                                'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
                        ),
         * @return Bool
         */
        public function exists() {
-               return $this->getArticleId() != 0;
+               return $this->getArticleID() != 0;
        }
  
        /**
         * @return Bool
         */
        public function isAlwaysKnown() {
+               $isKnown = null;
+               /**
+                * Allows overriding default behaviour for determining if a page exists.
+                * If $isKnown is kept as null, regular checks happen. If it's
+                * a boolean, this value is returned by the isKnown method.
+                *
+                * @since 1.20
+                *
+                * @param Title $title
+                * @param boolean|null $isKnown
+                */
+               wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
+               if ( !is_null( $isKnown ) ) {
+                       return $isKnown;
+               }
                if ( $this->mInterwiki != '' ) {
                        return true;  // any interwiki link might be viewable, for all we know
                }
                switch( $this->mNamespace ) {
                        case NS_MEDIA:
                        case NS_FILE:
         * viewed?  In particular, this function may be used to determine if
         * links to the title should be rendered as "bluelinks" (as opposed to
         * "redlinks" to non-existent pages).
+        * Adding something else to this function will cause inconsistency
+        * since LinkHolderArray calls isAlwaysKnown() and does its own
+        * page existence check.
         *
         * @return Bool
         */
                if ( $this->isSpecialPage() ) {
                        // special pages are in the user language
                        return $wgLang;
-               } elseif ( $this->isCssOrJsPage() ) {
+               } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
                        // css/js should always be LTR and is, in fact, English
                        return wfGetLangObj( 'en' );
                } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
diff --combined includes/WikiPage.php
@@@ -13,6 -13,30 +13,30 @@@ abstract class Page {
   * @internal documentation reviewed 15 Mar 2010
   */
  class WikiPage extends Page {
+       // doDeleteArticleReal() return values. Values less than zero indicate fatal errors,
+       // values greater than zero indicate that there were problems not resulting in page
+       // not being deleted
+       /**
+        * Delete operation aborted by hook
+        */
+       const DELETE_HOOK_ABORTED = -1;
+       /**
+        * Deletion successful
+        */
+       const DELETE_SUCCESS = 0;
+       /**
+        * Page not found
+        */
+       const DELETE_NO_PAGE = 1;
+       /**
+        * No revisions found to delete
+        */
+       const DELETE_NO_REVISIONS = 2;
        /**
         * @var Title
         */
         *
         * @param $id Int article ID to load
         *
-        * @return WikiPage
+        * @return WikiPage|null
         */
        public static function newFromID( $id ) {
                $t = Title::newFromID( $id );
                        'page_touched',
                        'page_latest',
                        'page_len',
 +            'page_content_model',
                );
        }
  
         * @return bool
         */
        public function isRedirect( $text = false ) {
 -              if ( $text === false ) {
 -                      if ( !$this->mDataLoaded ) {
 -                              $this->loadPageData();
 -                      }
 +        if ( $text === false ) $content = $this->getContent();
 +        else $content = ContentHandler::makeContent( $text, $this->mTitle ); # TODO: allow model and format to be provided; or better, expect a Content object
  
 -                      return (bool)$this->mIsRedirect;
 -              } else {
 -                      return Title::newFromRedirect( $text ) !== null;
 -              }
 +
 +        if ( empty( $content ) ) return false;
 +        else return $content->isRedirect();
        }
  
 +    /**
 +     * Returns the page's content model name. Will use the revisions actual content model if the page exists,
 +     * and the page's default if the page doesn't exist yet.
 +     *
 +     * @return int
 +     */
 +    public function getContentModelName() {
 +        if ( $this->exists() ) {
 +            # look at the revision's actual content model
 +            $content = $this->getContent();
 +            return $content->getModelName();
 +        } else {
 +            # use the default model for this page
 +            return $this->mTitle->getContentModelName();
 +        }
 +    }
 +
        /**
         * Loads page_touched and returns a value indicating if it should be used
         * @return boolean true if not a redirect
                return null;
        }
  
 +    /**
 +     * Get the content of the current revision. No side-effects...
 +     *
 +     * @param $audience Integer: one of:
 +     *      Revision::FOR_PUBLIC       to be displayed to all users
 +     *      Revision::FOR_THIS_USER    to be displayed to $wgUser
 +     *      Revision::RAW              get the text regardless of permissions
 +     * @return Content|null The content of the current revision
 +     */
 +    public function getContent( $audience = Revision::FOR_PUBLIC ) {
 +        $this->loadLastEdit();
 +        if ( $this->mLastRevision ) {
 +            return $this->mLastRevision->getContent( $audience );
 +        }
 +        return false;
 +    }
 +
        /**
         * Get the text of the current revision. No side-effects...
         *
         *      Revision::FOR_PUBLIC       to be displayed to all users
         *      Revision::FOR_THIS_USER    to be displayed to $wgUser
         *      Revision::RAW              get the text regardless of permissions
 -       * @return String|bool The text of the current revision. False on failure
 +       * @return String|false The text of the current revision
 +     * @deprecated as of 1.20, getContent() should be used instead.
         */
 -      public function getText( $audience = Revision::FOR_PUBLIC ) {
 +      public function getText( $audience = Revision::FOR_PUBLIC ) { #FIXME: deprecated, replace usage!
 +        wfDeprecated( __METHOD__, '1.20' );
                $this->loadLastEdit();
                if ( $this->mLastRevision ) {
                        return $this->mLastRevision->getText( $audience );
        /**
         * Get the text of the current revision. No side-effects...
         *
-        * @return String|false The text of the current revision
+        * @return String|bool The text of the current revision. False on failure
         */
 -      public function getRawText() {
 -              $this->loadLastEdit();
 -              if ( $this->mLastRevision ) {
 -                      return $this->mLastRevision->getRawText();
 -              }
 -              return false;
 +      public function getRawText() { #FIXME: deprecated, replace usage!
 +              return $this->getText( Revision::RAW );
        }
  
 +    /**
 +     * Get the content of the current revision. No side-effects...
 +     *
 +     * @return Contet|false The text of the current revision
 +     */
 +    protected function getNativeData() { #FIXME: examine all uses carefully! caller must be aware of content model!
 +        $content = $this->getContent( Revision::RAW );
 +        if ( !$content ) return null;
 +
 +        return $content->getNativeData();
 +    }
 +
        /**
         * @return string MW timestamp of last article revision
         */
                if ( !$this->mTimestamp ) {
                        $this->loadLastEdit();
                }
+               
                return wfTimestamp( TS_MW, $this->mTimestamp );
        }
  
                        return false;
                }
  
 -              $text = $editInfo ? $editInfo->pst : false;
 +        if ( $editInfo ) {
 +            $content = ContentHandler::makeContent( $editInfo->pst, $this->mTitle );
 +            # TODO: take model and format from edit info!
 +        } else {
 +            $content = $this->getContent();
 +        }
  
 -              if ( $this->isRedirect( $text ) ) {
 +              if ( !$content || $content->isRedirect( ) ) {
                        return false;
                }
  
 -              switch ( $wgArticleCountMethod ) {
 -              case 'any':
 -                      return true;
 -              case 'comma':
 -                      if ( $text === false ) {
 -                              $text = $this->getRawText();
 -                      }
 -                      return strpos( $text,  ',' ) !== false;
 -              case 'link':
 -                      if ( $editInfo ) {
 -                              // ParserOutput::getLinks() is a 2D array of page links, so
 -                              // to be really correct we would need to recurse in the array
 -                              // but the main array should only have items in it if there are
 -                              // links.
 -                              return (bool)count( $editInfo->output->getLinks() );
 -                      } else {
 -                              return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
 -                                      array( 'pl_from' => $this->getId() ), __METHOD__ );
 -                      }
 -              }
 +        $hasLinks = null;
 +
 +        if ( $wgArticleCountMethod === 'link' ) {
 +            # nasty special case to avoid re-parsing to detect links
 +
 +            if ( $editInfo ) {
 +                // ParserOutput::getLinks() is a 2D array of page links, so
 +                // to be really correct we would need to recurse in the array
 +                // but the main array should only have items in it if there are
 +                // links.
 +                $hasLinks = (bool)count( $editInfo->output->getLinks() );
 +            } else {
 +                $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
 +                    array( 'pl_from' => $this->getId() ), __METHOD__ );
 +            }
 +        }
 +
 +              return $content->isCountable( $hasLinks );
        }
  
        /**
         */
        public function insertRedirect() {
                // recurse through to only get the final target
 -              $retval = Title::newFromRedirectRecurse( $this->getRawText() );
 +        $content = $this->getContent();
 +              $retval = $content ? $content->getUltimateRedirectTarget() : null;
                if ( !$retval ) {
                        return null;
                }
                        && $parserOptions->getStubThreshold() == 0
                        && $this->mTitle->exists()
                        && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
 -                      && $this->mTitle->isWikitextPage();
 +                      && $this->mTitle->isWikitextPage(); #FIXME: ask ContentHandler if cachable!
        }
  
        /**
  
        /**
         * Perform the actions of a page purging
+        * @return bool
         */
        public function doPurge() {
                global $wgUseSquid;
                if ( $wgUseSquid ) {
                        // Commit the transaction before the purge is sent
                        $dbw = wfGetDB( DB_MASTER );
-                       $dbw->commit();
+                       $dbw->commit( __METHOD__ );
  
                        // Send purge
                        $update = SquidUpdate::newSimplePurge( $this->mTitle );
  
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
                        if ( $this->mTitle->exists() ) {
 -                              $text = $this->getRawText();
 +                              $text = $this->getNativeData(); #FIXME: may not be a string. check Content model!
                        } else {
                                $text = false;
                        }
        public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
                wfProfileIn( __METHOD__ );
  
 -              $text = $revision->getText();
 -              $len = strlen( $text );
 -              $rt = Title::newFromRedirectRecurse( $text );
 +        $content = $revision->getContent();
 +              $len = $content->getSize();
 +              $rt = $content->getUltimateRedirectTarget();
  
                $conditions = array( 'page_id' => $this->getId() );
  
         * @param $dbw DatabaseBase
         * @param $redirectTitle Title object pointing to the redirect target,
         *                       or NULL if this is not a redirect
-        * @param $lastRevIsRedirect If given, will optimize adding and
+        * @param $lastRevIsRedirect null|bool If given, will optimize adding and
         *                           removing rows in redirect table.
         * @return bool true on success, false on failure
         * @private
         * If the given revision is newer than the currently set page_latest,
         * update the page record. Otherwise, do nothing.
         *
-        * @param $dbw Database object
+        * @param $dbw DatabaseBase object
         * @param $revision Revision object
         * @return mixed
         */
         * @param $undo Revision
         * @param $undoafter Revision Must be an earlier revision than $undo
         * @return mixed string on success, false on failure
 +     * @deprecated since 1.20: use ContentHandler::getUndoContent() instead.
         */
 -      public function getUndoText( Revision $undo, Revision $undoafter = null ) {
 -              $cur_text = $this->getRawText();
 -              if ( $cur_text === false ) {
 -                      return false; // no page
 -              }
 -              $undo_text = $undo->getText();
 -              $undoafter_text = $undoafter->getText();
 +      public function getUndoText( Revision $undo, Revision $undoafter = null ) { #FIXME: replace usages.
 +        $this->loadLastEdit();
  
 -              if ( $cur_text == $undo_text ) {
 -                      # No use doing a merge if it's just a straight revert.
 -                      return $undoafter_text;
 -              }
 +        if ( $this->mLastRevision ) {
 +            $handler = ContentHandler::getForTitle( $this->getTitle() );
 +            $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
  
 -              $undone_text = '';
 +            if ( !$undone ) {
 +                return false;
 +            } else {
 +                return ContentHandler::getContentText( $undone );
 +            }
 +        }
  
 -              if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
 -                      return false;
 -              }
 -
 -              return $undone_text;
 +        return false;
        }
  
        /**
-        * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...)
+        * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
         * @param $text String: new text of the section
         * @param $sectionTitle String: new section's subject, only if $section is 'new'
         * @param $edittime String: revision timestamp or null to use the current revision
 -       * @return string Complete article text, or null if error
 +       * @return Content new complete article content, or null if error
 +     * @deprected since 1.20, use replaceSectionContent() instead
         */
 -      public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
 -              wfProfileIn( __METHOD__ );
 +      public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) { #FIXME: use replaceSectionContent() instead!
 +        wfDeprecated( __METHOD__, '1.20' );
  
 -              if ( strval( $section ) == '' ) {
 -                      // Whole-page edit; let the whole text through
 -              } else {
 -                      // Bug 30711: always use current version when adding a new section
 -                      if ( is_null( $edittime ) || $section == 'new' ) {
 -                              $oldtext = $this->getRawText();
 -                              if ( $oldtext === false ) {
 -                                      wfDebug( __METHOD__ . ": no page text\n" );
 -                                      wfProfileOut( __METHOD__ );
 -                                      return null;
 -                              }
 -                      } else {
 -                              $dbw = wfGetDB( DB_MASTER );
 -                              $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
 +        $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() ); #XXX: could make section title, but that's not required.
  
 -                              if ( !$rev ) {
 -                                      wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
 -                                              $this->getId() . "; section: $section; edittime: $edittime)\n" );
 -                                      wfProfileOut( __METHOD__ );
 -                                      return null;
 -                              }
 +        $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
  
 -                              $oldtext = $rev->getText();
 -                      }
 +              return ContentHandler::getContentText( $newContent ); #XXX: unclear what will happen for non-wikitext!
 +      }
  
 -                      if ( $section == 'new' ) {
 -                              # Inserting a new section
 -                              $subject = $sectionTitle ? wfMsgForContent( 'newsectionheaderdefaultlevel', $sectionTitle ) . "\n\n" : '';
 -                              if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
 -                                      $text = strlen( trim( $oldtext ) ) > 0
 -                                              ? "{$oldtext}\n\n{$subject}{$text}"
 -                                              : "{$subject}{$text}";
 -                              }
 -                      } else {
 -                              # Replacing an existing section; roll out the big guns
 -                              global $wgParser;
 +    public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
 +        wfProfileIn( __METHOD__ );
  
 -                              $text = $wgParser->replaceSection( $oldtext, $section, $text );
 -                      }
 -              }
 +        if ( strval( $section ) == '' ) {
 +            // Whole-page edit; let the whole text through
 +            $newContent = $sectionContent;
 +        } else {
 +            // Bug 30711: always use current version when adding a new section
 +            if ( is_null( $edittime ) || $section == 'new' ) {
 +                $oldContent = $this->getContent();
 +                if ( ! $oldContent ) {
 +                    wfDebug( __METHOD__ . ": no page text\n" );
 +                    wfProfileOut( __METHOD__ );
 +                    return null;
 +                }
 +            } else {
 +                $dbw = wfGetDB( DB_MASTER );
 +                $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
  
 -              wfProfileOut( __METHOD__ );
 -              return $text;
 -      }
 +                if ( !$rev ) {
 +                    wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
 +                        $this->getId() . "; section: $section; edittime: $edittime)\n" );
 +                    wfProfileOut( __METHOD__ );
 +                    return null;
 +                }
 +
 +                $oldContent = $rev->getContent();
 +            }
 +
 +            $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
 +        }
 +
 +        wfProfileOut( __METHOD__ );
 +        return $newContent;
 +    }
  
        /**
         * Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
         * edit-already-exists error will be returned. These two conditions are also possible with
         * auto-detection due to MediaWiki's performance-optimised locking strategy.
         *
-        * @param $baseRevId the revision ID this edit was based off, if any
+        * @param $baseRevId int the revision ID this edit was based off, if any
         * @param $user User the user doing the edit
         *
         * @return Status object. Possible errors:
         *     revision:                The revision object for the inserted revision, or null
         *
         *  Compatibility note: this function previously returned a boolean value indicating success/failure
 -       */
 -      public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
 +     * @deprecated since 1.20: use doEditContent() instead.
 +       */
 +    public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) { #FIXME: use doEditContent() instead
 +        #TODO: log use of deprecated function
 +        $content = ContentHandler::makeContent( $text, $this->getTitle() );
 +
 +        return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
 +    }
 +
 +    /**
 +     * Change an existing article or create a new article. Updates RC and all necessary caches,
 +     * optionally via the deferred update array.
 +     *
 +     * @param $content Content: new content
 +     * @param $summary String: edit summary
 +     * @param $flags Integer bitfield:
 +     *      EDIT_NEW
 +     *          Article is known or assumed to be non-existent, create a new one
 +     *      EDIT_UPDATE
 +     *          Article is known or assumed to be pre-existing, update it
 +     *      EDIT_MINOR
 +     *          Mark this edit minor, if the user is allowed to do so
 +     *      EDIT_SUPPRESS_RC
 +     *          Do not log the change in recentchanges
 +     *      EDIT_FORCE_BOT
 +     *          Mark the edit a "bot" edit regardless of user rights
 +     *      EDIT_DEFER_UPDATES
 +     *          Defer some of the updates until the end of index.php
 +     *      EDIT_AUTOSUMMARY
 +     *          Fill in blank summaries with generated text where possible
 +     *
 +     * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
 +     * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
 +     * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
 +     * edit-already-exists error will be returned. These two conditions are also possible with
 +     * auto-detection due to MediaWiki's performance-optimised locking strategy.
 +     *
 +     * @param $baseRevId the revision ID this edit was based off, if any
 +     * @param $user User the user doing the edit
 +     * @param $serialisation_format String: format for storing the content in the database
 +     *
 +     * @return Status object. Possible errors:
 +     *     edit-hook-aborted:       The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
 +     *     edit-gone-missing:       In update mode, but the article didn't exist
 +     *     edit-conflict:           In update mode, the article changed unexpectedly
 +     *     edit-no-change:          Warning that the text was the same as before
 +     *     edit-already-exists:     In creation mode, but the article already exists
 +     *
 +     *  Extensions may define additional errors.
 +     *
 +     *  $return->value will contain an associative array with members as follows:
 +     *     new:                     Boolean indicating if the function attempted to create a new article
 +     *     revision:                The revision object for the inserted revision, or null
 +     *
 +     *  Compatibility note: this function previously returned a boolean value indicating success/failure
 +     */
 +      public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
 +                                   User $user = null, $serialisation_format = null ) { #FIXME: use this
                global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
  
                # Low-level sanity check
  
                $flags = $this->checkFlags( $flags );
  
 -              if ( !wfRunHooks( 'ArticleSave', array( &$this, &$user, &$text, &$summary,
 -                      $flags & EDIT_MINOR, null, null, &$flags, &$status ) ) )
 -              {
 -                      wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" );
 +        # call legacy hook
 +        $hook_ok = wfRunHooks( 'ArticleContentSave', array( &$this, &$user, &$content, &$summary, #FIXME: document new hook!
 +            $flags & EDIT_MINOR, null, null, &$flags, &$status ) );
 +
 +        if ( $hook_ok && !empty( $wgHooks['ArticleSave'] ) ) { # avoid serialization overhead if the hook isn't present
 +            $content_text = $content->serialize();
 +            $txt = $content_text; # clone
 +
 +            $hook_ok = wfRunHooks( 'ArticleSave', array( &$this, &$user, &$txt, &$summary, #FIXME: deprecate legacy hook!
 +                $flags & EDIT_MINOR, null, null, &$flags, &$status ) );
 +
 +            if ( $txt !== $content_text ) {
 +                # if the text changed, unserialize the new version to create an updated Content object.
 +                $content = $content->getContentHandler()->unserialize( $txt );
 +            }
 +        }
 +
 +              if ( !$hook_ok ) {
 +                      wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
  
                        if ( $status->isOK() ) {
                                $status->fatal( 'edit-hook-aborted' );
                $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
                $bot = $flags & EDIT_FORCE_BOT;
  
 -              $oldtext = $this->getRawText(); // current revision
 -              $oldsize = strlen( $oldtext );
 +              $old_content = $this->getContent( Revision::RAW ); // current revision's content
 +
 +              $oldsize = $old_content ? $old_content->getSize() : 0;
                $oldid = $this->getLatest();
                $oldIsRedirect = $this->isRedirect();
                $oldcountable = $this->isCountable();
  
 +        $handler = $content->getContentHandler();
 +
                # Provide autosummaries if one is not provided and autosummaries are enabled.
                if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
 -                      $summary = self::getAutosummary( $oldtext, $text, $flags );
 +            if ( !$old_content ) $old_content = null;
 +                      $summary = $handler->getAutosummary( $old_content, $content, $flags );
                }
  
 -              $editInfo = $this->prepareTextForEdit( $text, null, $user );
 -              $text = $editInfo->pst;
 -              $newsize = strlen( $text );
 +              $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
 +              $serialized = $editInfo->pst;
 +        $content = $editInfo->pstContent;
 +              $newsize =  $content->getSize();
  
                $dbw = wfGetDB( DB_MASTER );
                $now = wfTimestampNow();
                                'page'       => $this->getId(),
                                'comment'    => $summary,
                                'minor_edit' => $isminor,
 -                              'text'       => $text,
 +                              'text'       => $serialized,
 +                'len'        => $newsize,
                                'parent_id'  => $oldid,
                                'user'       => $user->getId(),
                                'user_text'  => $user->getName(),
 -                              'timestamp'  => $now
 +                              'timestamp'  => $now,
 +                'content_model' => $content->getModelName(),
 +                'content_format' => $serialisation_format,
                        ) );
  
 -                      $changed = ( strcmp( $text, $oldtext ) != 0 );
 +                      $changed = !$content->equals( $old_content );
  
                        if ( $changed ) {
-                               $dbw->begin();
+                               $dbw->begin( __METHOD__ );
                                $revisionId = $revision->insertOn( $dbw );
  
                                # Update page
                                        }
  
                                        $revisionId = 0;
-                                       $dbw->rollback();
+                                       $dbw->rollback( __METHOD__ );
                                } else {
                                        global $wgUseRCPatrol;
                                        wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
  
                                                # Log auto-patrolled edits
                                                if ( $patrolled ) {
-                                                       PatrolLog::record( $rc, true );
+                                                       PatrolLog::record( $rc, true, $user );
                                                }
                                        }
                                        $user->incEditCount();
-                                       $dbw->commit();
+                                       $dbw->commit( __METHOD__ );
                                }
                        } else {
                                // Bug 32948: revision ID must be set to page {{REVISIONID}} and
                        # Create new article
                        $status->value['new'] = true;
  
-                       $dbw->begin();
+                       $dbw->begin( __METHOD__ );
  
                        # Add the page record; stake our claim on this title!
                        # This will return false if the article already exists
                        $newid = $this->insertOn( $dbw );
  
                        if ( $newid === false ) {
-                               $dbw->rollback();
+                               $dbw->rollback( __METHOD__ );
                                $status->fatal( 'edit-already-exists' );
  
                                wfProfileOut( __METHOD__ );
                                'page'       => $newid,
                                'comment'    => $summary,
                                'minor_edit' => $isminor,
 -                              'text'       => $text,
 +                              'text'       => $serialized,
 +                'len'        => $newsize,
                                'user'       => $user->getId(),
                                'user_text'  => $user->getName(),
 -                              'timestamp'  => $now
 +                              'timestamp'  => $now,
 +                'content_model' => $content->getModelName(),
 +                'content_format' => $serialisation_format,
                        ) );
                        $revisionId = $revision->insertOn( $dbw );
  
                                        $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
                                # Add RC row to the DB
                                $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
 -                                      '', strlen( $text ), $revisionId, $patrolled );
 +                                      '', $content->getSize(), $revisionId, $patrolled );
  
                                # Log auto-patrolled edits
                                if ( $patrolled ) {
-                                       PatrolLog::record( $rc, true );
+                                       PatrolLog::record( $rc, true, $user );
                                }
                        }
                        $user->incEditCount();
-                       $dbw->commit();
+                       $dbw->commit( __METHOD__ );
  
                        # Update links, etc.
                        $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
  
 -                      wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
 +                      wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $serialized, $summary, #FIXME: deprecate legacy hook
                                $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
 +
 +            wfRunHooks( 'ArticleContentInsertComplete', array( &$this, &$user, $content, $summary, #FIXME: document new hook
 +                $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
                }
  
                # Do updates right now unless deferral was requested
                // Return the new revision (or null) to the caller
                $status->value['revision'] = $revision;
  
 -              wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
 +              wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $serialized, $summary,  #FIXME: deprecate legacy hook
                        $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
  
 +        wfRunHooks( 'ArticleContentSaveComplete', array( &$this, &$user, $content, $summary, #FIXME: document new hook
 +            $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
 +
                # Promote user to any groups they meet the criteria for
                $user->addAutopromoteOnceGroups( 'onEdit' );
  
        /**
         * Prepare text which is about to be saved.
         * Returns a stdclass with source, pst and output members
 -       * @return bool|object
 -       */
 -      public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
 +     * @deprecated in 1.20: use prepareContentForEdit instead.
 +       */
 +    public function prepareTextForEdit( $text, $revid = null, User $user = null ) {  #FIXME: use prepareContentForEdit() instead #XXX: who uses this?!
 +        #TODO: log use of deprecated function
 +        $content = ContentHandler::makeContent( $text, $this->getTitle() );
 +        return $this->prepareContentForEdit( $content, $revid , $user );
 +    }
 +
 +    /**
 +     * Prepare content which is about to be saved.
 +     * Returns a stdclass with source, pst and output members
 +     *
 +     * @param \Content $content
 +     * @param null $revid
 +     * @param null|\User $user
 +     * @param null $serialization_format
 +     * @return bool|object
 +     */
 +      public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) { #FIXME: use this #XXX: really public?!
                global $wgParser, $wgContLang, $wgUser;
                $user = is_null( $user ) ? $wgUser : $user;
                // @TODO fixme: check $user->getId() here???
 +
                if ( $this->mPreparedEdit
 -                      && $this->mPreparedEdit->newText == $text
 +                      && $this->mPreparedEdit->newContent
 +            && $this->mPreparedEdit->newContent->equals( $content )
                        && $this->mPreparedEdit->revid == $revid
 +            && $this->mPreparedEdit->format == $serialization_format
 +            #XXX: also check $user here?
                ) {
                        // Already prepared
                        return $this->mPreparedEdit;
  
                $edit = (object)array();
                $edit->revid = $revid;
 -              $edit->newText = $text;
 -              $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
 +
 +              $edit->pstContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
 +        $edit->pst = $edit->pstContent->serialize( $serialization_format );
 +        $edit->format = $serialization_format;
 +
                $edit->popts = $this->makeParserOptions( 'canonical' );
 -              $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
 -              $edit->oldText = $this->getRawText();
 +              $edit->output = $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts );
 +
 +        $edit->newContent = $content;
 +              $edit->oldContent = $this->getContent( Revision::RAW );
 +
 +        $edit->newText = ContentHandler::getContentText( $edit->newContent ); #FIXME: B/C only! don't use this field!
 +        $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : ''; #FIXME: B/C only! don't use this field!
  
                $this->mPreparedEdit = $edit;
  
                wfProfileIn( __METHOD__ );
  
                $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
 -              $text = $revision->getText();
 +        $content = $revision->getContent();
  
                # Parse the text
                # Be careful not to double-PST: $text is usually already PST-ed once
                if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
                        wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
 -                      $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
 +                      $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
                } else {
                        wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
                        $editInfo = $this->mPreparedEdit;
                        $parserCache->save( $editInfo->output, $this, $editInfo->popts );
                }
  
 -              # Update the links tables
 -              $u = new LinksUpdate( $this->mTitle, $editInfo->output );
 -              $u->doUpdate();
 +              # Update the links tables and other secondary data
 +        $updates = $editInfo->output->getLinksUpdateAndOtherUpdates( $this->mTitle );
 +        SecondaryDataUpdate::runUpdates( $updates );
  
                wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
  
                }
  
                DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
 -              DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $text ) );
 +        DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content->getTextForSearchIndex() ) );
  
                # If this is another user's talk page, update newtalk.
                # Don't do this if $options['changed'] = false (null-edits) nor if
                }
  
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
 -                      MessageCache::singleton()->replace( $shortTitle, $text );
 +            $msgtext = ContentHandler::getContentText( $content ); #XXX: could skip pseudo-messages like js/css here, based on content model.
 +            if ( $msgtext === false || $msgtext === null ) $msgtext = '';
 +
 +                      MessageCache::singleton()->replace( $shortTitle, $msgtext );
                }
  
                if( $options['created'] ) {
         * @param $comment String: comment submitted
         * @param $minor Boolean: whereas it's a minor modification
         */
 -      public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
 +    public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
 +        #TODO: log use of deprecated function
 +        $content = ContentHandler::makeContent( $text, $this->getTitle() );
 +        return $this->doQuickEdit( $content, $user, $comment , $minor );
 +    }
 +
 +    /**
 +     * Edit an article without doing all that other stuff
 +     * The article must already exist; link tables etc
 +     * are not updated, caches are not flushed.
 +     *
 +     * @param $content Content: content submitted
 +     * @param $user User The relevant user
 +     * @param $comment String: comment submitted
 +     * @param $serialisation_format String: format for storing the content in the database
 +     * @param $minor Boolean: whereas it's a minor modification
 +     */
 +      public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = 0, $serialisation_format = null ) {
                wfProfileIn( __METHOD__ );
  
 +        $serialized = $content->serialize( $serialisation_format );
 +
                $dbw = wfGetDB( DB_MASTER );
                $revision = new Revision( array(
                        'page'       => $this->getId(),
 -                      'text'       => $text,
 +                      'text'       => $serialized,
 +            'length'     => $content->getSize(),
                        'comment'    => $comment,
                        'minor_edit' => $minor ? 1 : 0,
                ) );
        }
  
        /**
-        * Back-end article deletion
+        * Same as doDeleteArticleReal(), but returns more detailed success/failure status
         * Deletes the article with database consistency, writes logs, purges caches
         *
         * @param $reason string delete reason for deletion log
-        * @param $suppress bitfield
+        * @param $suppress int bitfield
         *      Revision::DELETED_TEXT
         *      Revision::DELETED_COMMENT
         *      Revision::DELETED_USER
         *      Revision::DELETED_RESTRICTED
         * @param $id int article ID
         * @param $commit boolean defaults to true, triggers transaction end
-        * @param &$errors Array of errors to append to
-        * @param $user User The relevant user
+        * @param &$error Array of errors to append to
+        * @param $user User The deleting user
         * @return boolean true if successful
         */
        public function doDeleteArticle(
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+       ) {
+               return $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user )
+                       == WikiPage::DELETE_SUCCESS;
+       }
+       /**
+        * Back-end article deletion
+        * Deletes the article with database consistency, writes logs, purges caches
+        *
+        * @param $reason string delete reason for deletion log
+        * @param $suppress int bitfield
+        *      Revision::DELETED_TEXT
+        *      Revision::DELETED_COMMENT
+        *      Revision::DELETED_USER
+        *      Revision::DELETED_RESTRICTED
+        * @param $id int article ID
+        * @param $commit boolean defaults to true, triggers transaction end
+        * @param &$error Array of errors to append to
+        * @param $user User The deleting user
+        * @return int: One of WikiPage::DELETE_* constants
+        */
+       public function doDeleteArticleReal(
+               $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
                global $wgUser;
                $user = is_null( $user ) ? $wgUser : $user;
                wfDebug( __METHOD__ . "\n" );
  
                if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
-                       return false;
+                       return WikiPage::DELETE_HOOK_ABORTED;
                }
                $dbw = wfGetDB( DB_MASTER );
                $t = $this->mTitle->getDBkey();
                $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
  
                if ( $t === '' || $id == 0 ) {
-                       return false;
+                       return WikiPage::DELETE_NO_PAGE;
                }
  
                // Bitfields to further suppress the content
                        $bitfield = 'rev_deleted';
                }
  
-               $dbw->begin();
+               $dbw->begin( __METHOD__ );
                // For now, shunt the revision data into the archive table.
                // Text is *not* removed from the text table; bulk storage
                // is left intact to avoid breaking block-compression or
                                'ar_len'        => 'rev_len',
                                'ar_page_id'    => 'page_id',
                                'ar_deleted'    => $bitfield,
 -                              'ar_sha1'       => 'rev_sha1'
 +                              'ar_sha1'       => 'rev_content_model',
 +                              'ar_content_format'       => 'rev_content_format',
 +                              'ar_content_format'       => 'rev_sha1'
                        ), array(
                                'page_id' => $id,
                                'page_id = rev_page'
  
                # Now that it's safely backed up, delete it
                $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
-               $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
+               $ok = ( $dbw->affectedRows() > 0 ); // getArticleID() uses slave, could be laggy
  
                if ( !$ok ) {
-                       $dbw->rollback();
-                       return false;
+                       $dbw->rollback( __METHOD__ );
+                       return WikiPage::DELETE_NO_REVISIONS;
                }
  
                $this->doDeleteUpdates( $id );
                $logEntry->publish( $logid );
  
                if ( $commit ) {
-                       $dbw->commit();
+                       $dbw->commit( __METHOD__ );
                }
  
                wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
-               return true;
+               return WikiPage::DELETE_SUCCESS;
        }
  
        /**
                # Clear caches
                self::onArticleDelete( $this->mTitle );
  
+               # Reset this object
+               $this->clear();
                # Clear the cached article id so the interface doesn't act like we exist
                $this->mTitle->resetArticleID( 0 );
        }
         *
         * @param $resultDetails Array: contains result-specific array of additional values
         * @param $guser User The user performing the rollback
+        * @return array
         */
        public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
                global $wgUseRCPatrol, $wgContLang;
                }
  
                # Get the last editor
-               $current = Revision::newFromTitle( $this->mTitle );
+               $current = $this->getRevision();
                if ( is_null( $current ) ) {
                        # Something wrong... no page?
                        return array( array( 'notanarticle' ) );
                }
  
                # Actually store the edit
 -              $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId(), $guser );
 +              $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser );
                if ( !empty( $status->value['revision'] ) ) {
                        $revId = $status->value['revision']->getId();
                } else {
        * @param $newtext String: The submitted text of the page.
        * @param $flags Int bitmask: a bitmask of flags submitted for the edit.
        * @return string An appropriate autosummary, or an empty string.
 +    * @deprecated since 1.20, use ContentHandler::getAutosummary() instead
        */
        public static function getAutosummary( $oldtext, $newtext, $flags ) {
 -              global $wgContLang;
 -
 -              # Decide what kind of autosummary is needed.
 -
 -              # Redirect autosummaries
 -              $ot = Title::newFromRedirect( $oldtext );
 -              $rt = Title::newFromRedirect( $newtext );
 -
 -              if ( is_object( $rt ) && ( !is_object( $ot ) || !$rt->equals( $ot ) || $ot->getFragment() != $rt->getFragment() ) ) {
 -                      $truncatedtext = $wgContLang->truncate(
 -                              str_replace( "\n", ' ', $newtext ),
 -                              max( 0, 250
 -                                      - strlen( wfMsgForContent( 'autoredircomment' ) )
 -                                      - strlen( $rt->getFullText() )
 -                              ) );
 -                      return wfMsgForContent( 'autoredircomment', $rt->getFullText(), $truncatedtext );
 -              }
 +              # NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
  
 -              # New page autosummaries
 -              if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
 -                      # If they're making a new article, give its text, truncated, in the summary.
 +        $handler = ContentHandler::getForModelName( CONTENT_MODEL_WIKITEXT );
 +        $oldContent = $oldtext ? $handler->unserialize( $oldtext ) : null;
 +        $newContent = $newtext ? $handler->unserialize( $newtext ) : null;
  
 -                      $truncatedtext = $wgContLang->truncate(
 -                              str_replace( "\n", ' ', $newtext ),
 -                              max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
 -
 -                      return wfMsgForContent( 'autosumm-new', $truncatedtext );
 -              }
 -
 -              # Blanking autosummaries
 -              if ( $oldtext != '' && $newtext == '' ) {
 -                      return wfMsgForContent( 'autosumm-blank' );
 -              } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
 -                      # Removing more than 90% of the article
 -
 -                      $truncatedtext = $wgContLang->truncate(
 -                              $newtext,
 -                              max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
 -
 -                      return wfMsgForContent( 'autosumm-replace', $truncatedtext );
 -              }
 -
 -              # If we reach this point, there's no applicable autosummary for our case, so our
 -              # autosummary is empty.
 -              return '';
 +        return $handler->getAutosummary( $oldContent, $newContent, $flags );
        }
  
        /**
         * @param &$hasHistory Boolean: whether the page has a history
         * @return mixed String containing deletion reason or empty string, or boolean false
         *    if no revision occurred
 +     * @deprecated since 1.20, use ContentHandler::getAutoDeleteReason() instead
         */
        public function getAutoDeleteReason( &$hasHistory ) {
 +        #NOTE: stub for backwards-compatibility.
 +
 +              $handler = ContentHandler::getForTitle( $this->getTitle() );
 +        $handler->getAutoDeleteReason( $this->getTitle(), $hasHistory );
+               global $wgContLang;
+               // Get the last revision
+               $rev = $this->getRevision();
+               if ( is_null( $rev ) ) {
+                       return false;
+               }
+               // Get the article's contents
+               $contents = $rev->getText();
+               $blank = false;
+               // If the page is blank, use the text from the previous revision,
+               // which can only be blank if there's a move/import/protect dummy revision involved
+               if ( $contents == '' ) {
+                       $prev = $rev->getPrevious();
+                       if ( $prev )    {
+                               $contents = $prev->getText();
+                               $blank = true;
+                       }
+               }
+               $dbw = wfGetDB( DB_MASTER );
+               // Find out if there was only one contributor
+               // Only scan the last 20 revisions
+               $res = $dbw->select( 'revision', 'rev_user_text',
+                       array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
+                       __METHOD__,
+                       array( 'LIMIT' => 20 )
+               );
+               if ( $res === false ) {
+                       // This page has no revisions, which is very weird
+                       return false;
+               }
+               $hasHistory = ( $res->numRows() > 1 );
+               $row = $dbw->fetchObject( $res );
+               if ( $row ) { // $row is false if the only contributor is hidden
+                       $onlyAuthor = $row->rev_user_text;
+                       // Try to find a second contributor
+                       foreach ( $res as $row ) {
+                               if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
+                                       $onlyAuthor = false;
+                                       break;
+                               }
+                       }
+               } else {
+                       $onlyAuthor = false;
+               }
+               // Generate the summary with a '$1' placeholder
+               if ( $blank ) {
+                       // The current revision is blank and the one before is also
+                       // blank. It's just not our lucky day
+                       $reason = wfMsgForContent( 'exbeforeblank', '$1' );
+               } else {
+                       if ( $onlyAuthor ) {
+                               $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
+                       } else {
+                               $reason = wfMsgForContent( 'excontent', '$1' );
+                       }
+               }
+               if ( $reason == '-' ) {
+                       // Allow these UI messages to be blanked out cleanly
+                       return '';
+               }
+               // Replace newlines with spaces to prevent uglyness
+               $contents = preg_replace( "/[\n\r]/", ' ', $contents );
+               // Calculate the maximum amount of chars to get
+               // Max content length = max comment length - length of the comment (excl. $1)
+               $maxLength = 255 - ( strlen( $reason ) - 2 );
+               $contents = $wgContLang->truncate( $contents, $maxLength );
+               // Remove possible unfinished links
+               $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
+               // Now replace the '$1' placeholder
+               $reason = str_replace( '$1', $contents, $reason );
+               return $reason;
        }
  
        /**
  
                if ( count( $templates_diff ) > 0 ) {
                        # Whee, link updates time.
 +            # Note: we are only interested in links here. We don't need to get other SecondaryDataUpdate items from the parser output.
                        $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
                        $u->doUpdate();
                }
  
        /**
         * @deprecated since 1.18
+        * @return bool
         */
        public function useParserCache( $oldid ) {
                wfDeprecated( __METHOD__, '1.18' );
@@@ -2849,7 -2837,7 +2988,7 @@@ class PoolWorkArticleView extends PoolC
        private $text;
  
        /**
-        * @var ParserOutput|false
+        * @var ParserOutput|bool
         */
        private $parserOutput = false;
  
        private $isDirty = false;
  
        /**
-        * @var Status|false
+        * @var Status|bool
         */
        private $error = false;
  
         * @param $revid Integer: ID of the revision being parsed
         * @param $useParserCache Boolean: whether to use the parser cache
         * @param $parserOptions parserOptions to use for the parse operation
 -       * @param $text String: text to parse or null to load it
 +       * @param $content Content|String: content to parse or null to load it; may also be given as a wikitext string, for BC
         */
 -      function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
 +      function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
 +        if ( is_string($content) ) { #BC: old style call
 +            $modelName = $page->getRevision()->getContentModelName();
 +            $format = $page->getRevision()->getContentFormat();
 +            $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelName, $format );
 +        }
 +
                $this->page = $page;
                $this->revid = $revid;
                $this->cacheable = $useParserCache;
                $this->parserOptions = $parserOptions;
 -              $this->text = $text;
 +              $this->content = $content;
                $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
                parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
        }
        /**
         * Get a Status object in case of error or false otherwise
         *
-        * @return Status|false
+        * @return Status|bool
         */
        public function getError() {
                return $this->error;
  
                $isCurrent = $this->revid === $this->page->getLatest();
  
 -              if ( $this->text !== null ) {
 -                      $text = $this->text;
 +              if ( $this->content !== null ) {
 +                      $content = $this->content;
                } elseif ( $isCurrent ) {
 -                      $text = $this->page->getRawText();
 +            $content = $this->page->getContent( Revision::RAW ); #XXX: why use RAW audience here, and PUBLIC (default) below?
                } else {
                        $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
                        if ( $rev === null ) {
                                return false;
                        }
 -                      $text = $rev->getText();
 +            $content = $rev->getContent(); #XXX: why use PUBLIC audience here (default), and RAW above?
                }
  
 +              $time = - wfTime();
 +              $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
 +              $time += wfTime();
+               $time = - microtime( true );
+               $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
+                       $this->parserOptions, true, true, $this->revid );
+               $time += microtime( true );
  
                # Timing hack
                if ( $time > 3 ) {
  
        /**
         * @param $status Status
+        * @return bool
         */
        function error( $status ) {
                $this->error = $status;
@@@ -120,10 -120,13 +120,13 @@@ class RawAction extends FormlessAction 
  
                // If it's a MediaWiki message we can just hit the message cache
                if ( $request->getBool( 'usemsgcache' ) && $title->getNamespace() == NS_MEDIAWIKI ) {
-                       $key = $title->getDBkey();
-                       $msg = wfMessage( $key )->inContentLanguage();
-                       # If the message doesn't exist, return a blank
-                       $text = !$msg->exists() ? '' : $msg->plain();
+                       // The first "true" is to use the database, the second is to use the content langue
+                       // and the last one is to specify the message key already contains the language in it ("/de", etc.)
+                       $text = MessageCache::singleton()->get( $title->getDBkey(), true, true, true );
+                       // If the message doesn't exist, return a blank
+                       if ( $text === false ) {
+                               $text = '';
+                       }
                } else {
                        // Get it from the DB
                        $rev = Revision::newFromTitle( $title, $this->getOldId() );
                                $request->response()->header( "Last-modified: $lastmod" );
  
                                // Public-only due to cache headers
 -                              $text = $rev->getText();
 +                              $content = $rev->getContent();
 +
 +                              if ( !$content instanceof TextContent ) {
 +                                      wfHttpError( 406, "Not Acceptable", "The requeste page uses the content model `"
 +                                                                                                              . $content->getModelName() . "` which is not supported via this interface." );
 +                                      die();
 +                              }
 +
                                $section = $request->getIntOrNull( 'section' );
                                if ( $section !== null ) {
 -                                      $text = $wgParser->getSection( $text, $section );
 +                                      $content = $content->getSection( $section );
                                }
 +
 +                              $text = $content->getNativeData();
                        }
                }
  
                                # output previous revision, or nothing if there isn't one
                                if( !$oldid ) {
                                        # get the current revision so we can get the penultimate one
-                                       $oldid = $this->getTitle()->getLatestRevID();
+                                       $oldid = $this->page->getLatest();
                                }
                                $prev = $this->getTitle()->getPreviousRevisionId( $oldid );
                                $oldid = $prev ? $prev : -1 ;
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
   *
   * @file
-  * @ingroup Action
+  * @ingroup Actions
   */
  
  /**
   * User interface for the rollback action
   *
-  * @ingroup Action
+  * @ingroup Actions
   */
  class RollbackAction extends FormlessAction {
  
@@@ -63,7 -63,7 +63,7 @@@
                                $current = $details['current'];
  
                                if ( $current->getComment() != '' ) {
-                                       $this->getOutput()->addHTML( wfMessage( 'editcomment' )->rawParams(
+                                       $this->getOutput()->addHTML( $this->msg( 'editcomment' )->rawParams(
                                                Linker::formatComment( $current->getComment() ) )->parse() );
                                }
                        }
@@@ -97,7 -97,7 +97,7 @@@
                $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
  
                if ( $current->getUserText() === '' ) {
-                       $old = wfMsg( 'rev-deleted-user' );
+                       $old = $this->msg( 'rev-deleted-user' )->escaped();
                } else {
                        $old = Linker::userLink( $current->getUser(), $current->getUserText() )
                                . Linker::userToolLinks( $current->getUser(), $current->getUserText() );
  
                $new = Linker::userLink( $target->getUser(), $target->getUserText() )
                        . Linker::userToolLinks( $target->getUser(), $target->getUserText() );
-               $this->getOutput()->addHTML( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
+               $this->getOutput()->addHTML( $this->msg( 'rollback-success' )->rawParams( $old, $new )->parseAsBlock() );
                $this->getOutput()->returnToMain( false, $this->getTitle() );
  
                if ( !$request->getBool( 'hidediff', false ) && !$this->getUser()->getBoolOption( 'norollbackdiff', false ) ) {
 -                      $de = new DifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
 +            $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
 +            $de = $contentHandler->getDifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
                        $de->showDiff( '', '' );
                }
        }
@@@ -32,11 -32,10 +32,11 @@@ class ApiComparePages extends ApiBase 
        public function execute() {
                $params = $this->extractRequestParams();
  
-               $rev1 = $this->revisionOrTitle( $params['fromrev'], $params['fromtitle'] );
-               $rev2 = $this->revisionOrTitle( $params['torev'], $params['totitle'] );
+               $rev1 = $this->revisionOrTitleOrId( $params['fromrev'], $params['fromtitle'], $params['fromid'] );
+               $rev2 = $this->revisionOrTitleOrId( $params['torev'], $params['totitle'], $params['toid'] );
  
 -              $de = new DifferenceEngine( $this->getContext(),
 +        $contentHandler = ContentHandler::getForModelName( $rev1->getContentModelName() );
 +        $de = $contentHandler->getDifferenceEngine( $this->getContext(),
                        $rev1,
                        $rev2,
                        null, // rcid
                if ( isset( $params['fromtitle'] ) ) {
                        $vals['fromtitle'] = $params['fromtitle'];
                }
+               if ( isset( $params['fromid'] ) ) {
+                       $vals['fromid'] = $params['fromid'];
+               }
                $vals['fromrevid'] = $rev1;
                if ( isset( $params['totitle'] ) ) {
                        $vals['totitle'] = $params['totitle'];
                }
+               if ( isset( $params['toid'] ) ) {
+                       $vals['toid'] = $params['toid'];
+               }
                $vals['torevid'] = $rev2;
  
                $difftext = $de->getDiffBody();
        /**
         * @param $revision int
         * @param $titleText string
+        * @param $titleId int
         * @return int
         */
-       private function revisionOrTitle( $revision, $titleText ) {
+       private function revisionOrTitleOrId( $revision, $titleText, $titleId ) {
                if( $revision ){
                        return $revision;
                } elseif( $titleText ) {
                                $this->dieUsageMsg( array( 'invalidtitle', $titleText ) );
                        }
                        return $title->getLatestRevID();
+               } elseif ( $titleId ) {
+                       $title = Title::newFromID( $titleId );
+                       if( !$title ) {
+                               $this->dieUsageMsg( array( 'nosuchpageid', $titleId ) );
+                       }
+                       return $title->getLatestRevID();
                }
-               $this->dieUsage( 'inputneeded', 'A title or a revision number is needed for both the from and the to parameters' );
+               $this->dieUsage( 'inputneeded', 'A title, a page ID, or a revision number is needed for both the from and the to parameters' );
        }
  
        public function getAllowedParams() {
                return array(
                        'fromtitle' => null,
+                       'fromid' => array(
+                               ApiBase::PARAM_TYPE => 'integer'
+                       ),
                        'fromrev' => array(
                                ApiBase::PARAM_TYPE => 'integer'
                        ),
                        'totitle' => null,
+                       'toid' => array(
+                               ApiBase::PARAM_TYPE => 'integer'
+                       ),
                        'torev' => array(
                                ApiBase::PARAM_TYPE => 'integer'
                        ),
        public function getParamDescription() {
                return array(
                        'fromtitle' => 'First title to compare',
+                       'fromid' => 'First page ID to compare',
                        'fromrev' => 'First revision to compare',
                        'totitle' => 'Second title to compare',
+                       'toid' => 'Second page ID to compare',
                        'torev' => 'Second revision to compare',
                );
        }
        public function getDescription() {
                return array(
                        'Get the difference between 2 pages',
-                       'You must pass a revision number or a page title for each part (1 and 2)'
+                       'You must pass a revision number or a page title or a page ID id for each part (1 and 2)'
                );
        }
  
                return array_merge( parent::getPossibleErrors(), array(
                        array( 'code' => 'inputneeded', 'info' => 'A title or a revision is needed' ),
                        array( 'invalidtitle', 'title' ),
+                       array( 'nosuchpageid', 'pageid' ),
                        array( 'code' => 'baddiff', 'info' => 'The diff cannot be retrieved. Maybe one or both revisions do not exist or you do not have permission to view them.' ),
                ) );
        }
@@@ -123,7 -123,7 +123,7 @@@ class ApiDelete extends ApiBase 
                        // Need to pass a throwaway variable because generateReason expects
                        // a reference
                        $hasHistory = false;
 -                      $reason = $page->getAutoDeleteReason( $hasHistory );
 +                      $reason = $page->getAutoDeleteReason( $hasHistory ); #FIXME: use ContentHandler::getAutoDeleteReason()
                        if ( $reason === false ) {
                                return array( array( 'cannotdelete', $title->getPrefixedText() ) );
                        }
         * @param $oldimage
         * @param $reason
         * @param $suppress bool
-        * @return \type|array|Title
+        * @return array|Title
         */
        public static function deleteFile( Page $page, User $user, $token, $oldimage, &$reason = null, $suppress = false ) {
                $title = $page->getTitle();
@@@ -48,9 -48,18 +48,18 @@@ class ApiEditPage extends ApiBase 
                        $this->dieUsageMsg( 'missingtext' );
                }
  
-               $titleObj = Title::newFromText( $params['title'] );
-               if ( !$titleObj || $titleObj->isExternal() ) {
-                       $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+               $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
+               if ( isset( $params['title'] ) ) {
+                       $titleObj = Title::newFromText( $params['title'] );
+                       if ( !$titleObj || $titleObj->isExternal() ) {
+                               $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+                       }
+               } elseif ( isset( $params['pageid'] ) ) {
+                       $titleObj = Title::newFromID( $params['pageid'] );
+                       if ( !$titleObj ) {
+                               $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
+                       }
                }
  
                $apiResult = $this->getResult();
                        // We do want getContent()'s behavior for non-existent
                        // MediaWiki: pages, though
                        if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI ) {
 -                              $content = '';
 +                              $content = null;
 +                $text = '';
                        } else {
 -                              $content = $articleObj->getContent();
 +                $content = $articleObj->getContentObject();
 +                $text = ContentHandler::getContentText( $content ); #FIXME: serialize?! get format from params?...
                        }
  
                        if ( !is_null( $params['section'] ) ) {
                                // Process the content for section edits
 -                              global $wgParser;
                                $section = intval( $params['section'] );
 -                              $content = $wgParser->getSection( $content, $section, false );
 -                              if ( $content === false ) {
 +                $sectionContent = $content->getSection( $section );
 +                $text = ContentHandler::getContentText( $sectionContent ); #FIXME: serialize?! get format from params?...
 +                              if ( $text === false || $text === null ) {
                                        $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
                                }
                        }
 -                      $params['text'] = $params['prependtext'] . $content . $params['appendtext'];
 +                      $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
                        $toMD5 = $params['prependtext'] . $params['appendtext'];
                }
  
                // TODO: Make them not or check if they still do
                $wgTitle = $titleObj;
  
 -              $ep = new EditPage( $articleObj );
 +        $handler = ContentHandler::getForTitle( $titleObj );
 +              $ep = $handler->createEditPage( $articleObj );
 +
                $ep->setContextTitle( $titleObj );
                $ep->importFormData( $req );
  
        public function getPossibleErrors() {
                global $wgMaxArticleSize;
  
-               return array_merge( parent::getPossibleErrors(), array(
-                       array( 'missingtext' ),
-                       array( 'invalidtitle', 'title' ),
-                       array( 'createonly-exists' ),
-                       array( 'nocreate-missing' ),
-                       array( 'nosuchrevid', 'undo' ),
-                       array( 'nosuchrevid', 'undoafter' ),
-                       array( 'revwrongpage', 'id', 'text' ),
-                       array( 'undo-failure' ),
-                       array( 'hashcheckfailed' ),
-                       array( 'hookaborted' ),
-                       array( 'noimageredirect-anon' ),
-                       array( 'noimageredirect-logged' ),
-                       array( 'spamdetected', 'spam' ),
-                       array( 'summaryrequired' ),
-                       array( 'filtered' ),
-                       array( 'blockedtext' ),
-                       array( 'contenttoobig', $wgMaxArticleSize ),
-                       array( 'noedit-anon' ),
-                       array( 'noedit' ),
-                       array( 'actionthrottledtext' ),
-                       array( 'wasdeleted' ),
-                       array( 'nocreate-loggedin' ),
-                       array( 'blankpage' ),
-                       array( 'editconflict' ),
-                       array( 'emptynewsection' ),
-                       array( 'unknownerror', 'retval' ),
-                       array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
-                       array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
-                       array( 'customcssprotected' ),
-                       array( 'customjsprotected' ),
-               ) );
+               return array_merge( parent::getPossibleErrors(),
+                       $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+                       array(
+                               array( 'nosuchpageid', 'pageid' ),
+                               array( 'missingtext' ),
+                               array( 'invalidtitle', 'title' ),
+                               array( 'createonly-exists' ),
+                               array( 'nocreate-missing' ),
+                               array( 'nosuchrevid', 'undo' ),
+                               array( 'nosuchrevid', 'undoafter' ),
+                               array( 'revwrongpage', 'id', 'text' ),
+                               array( 'undo-failure' ),
+                               array( 'hashcheckfailed' ),
+                               array( 'hookaborted' ),
+                               array( 'noimageredirect-anon' ),
+                               array( 'noimageredirect-logged' ),
+                               array( 'spamdetected', 'spam' ),
+                               array( 'summaryrequired' ),
+                               array( 'filtered' ),
+                               array( 'blockedtext' ),
+                               array( 'contenttoobig', $wgMaxArticleSize ),
+                               array( 'noedit-anon' ),
+                               array( 'noedit' ),
+                               array( 'actionthrottledtext' ),
+                               array( 'wasdeleted' ),
+                               array( 'nocreate-loggedin' ),
+                               array( 'blankpage' ),
+                               array( 'editconflict' ),
+                               array( 'emptynewsection' ),
+                               array( 'unknownerror', 'retval' ),
+                               array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
+                               array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
+                               array( 'customcssprotected' ),
+                               array( 'customjsprotected' ),
+                       )
+               );
        }
  
        public function getAllowedParams() {
                return array(
                        'title' => array(
                                ApiBase::PARAM_TYPE => 'string',
-                               ApiBase::PARAM_REQUIRED => true
+                       ),
+                       'pageid' => array(
+                               ApiBase::PARAM_TYPE => 'integer',
                        ),
                        'section' => null,
                        'sectiontitle' => array(
        public function getParamDescription() {
                $p = $this->getModulePrefix();
                return array(
-                       'title' => 'Page title',
+                       'title' => "Title of the page you want to edit. Cannot be used together with {$p}pageid",
+                       'pageid' => "Page ID of the page you want to edit. Cannot be used together with {$p}title",
                        'section' => 'Section number. 0 for the top section, \'new\' for a new section',
                        'sectiontitle' => 'The title for a new section',
                        'text' => 'Page content',
@@@ -318,9 -318,9 +318,9 @@@ class ApiParse extends ApiBase 
  
                $page = WikiPage::factory( $titleObj );
  
 -              if ( $this->section !== false ) {
 +              if ( $this->section !== false ) { #FIXME: get section Content, get parser output, ...
                        $this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
 -                                      ? 'page id ' . $pageId : $titleObj->getText() );
 +                                      ? 'page id ' . $pageId : $titleObj->getText() ); #FIXME: get section...
  
                        // Not cached (save or load)
                        return $wgParser->parse( $this->text, $titleObj, $popts );
                        // getParserOutput will save to Parser cache if able
                        $pout = $page->getParserOutput( $popts );
                        if ( $getWikitext ) {
 -                              $this->text = $page->getRawText();
 +                $this->content = $page->getContent( Revision::RAW ); #FIXME: use $this->content everywhere
 +                              $this->text = ContentHandler::getContentText( $this->content ); #FIXME: serialize, get format from params; or use object structure in result?
                        }
                        return $pout;
                }
        }
  
 -      private function getSectionText( $text, $what ) {
 +      private function getSectionText( $text, $what ) { #FIXME: replace with Content::getSection
                global $wgParser;
                // Not cached (save or load)
                $text = $wgParser->getSection( $text, $this->section, false );
                $langs = array();
                foreach ( $languages as $l ) {
                        $nt = Title::newFromText( $l );
-                       $text = $wgContLang->getLanguageName( $nt->getInterwiki() );
+                       $text = Language::fetchLanguageName( $nt->getInterwiki() );
  
                        $langs[] = Html::element( 'a',
                                array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ),
@@@ -15,7 -15,7 +15,7 @@@
   * @private
   * @ingroup DifferenceEngine
   */
 -class _DiffOp {
 +class _DiffOp { #FIXME: no longer private!
        var $type;
        var $orig;
        var $closing;
@@@ -44,7 -44,7 +44,7 @@@
   * @private
   * @ingroup DifferenceEngine
   */
 -class _DiffOp_Copy extends _DiffOp {
 +class _DiffOp_Copy extends _DiffOp { #FIXME: no longer private!
        var $type = 'copy';
  
        function __construct( $orig, $closing = false ) {
@@@ -68,7 -68,7 +68,7 @@@
   * @private
   * @ingroup DifferenceEngine
   */
 -class _DiffOp_Delete extends _DiffOp {
 +class _DiffOp_Delete extends _DiffOp { #FIXME: no longer private!
        var $type = 'delete';
  
        function __construct( $lines ) {
@@@ -89,7 -89,7 +89,7 @@@
   * @private
   * @ingroup DifferenceEngine
   */
 -class _DiffOp_Add extends _DiffOp {
 +class _DiffOp_Add extends _DiffOp { #FIXME: no longer private!
        var $type = 'add';
  
        function __construct( $lines ) {
   * @private
   * @ingroup DifferenceEngine
   */
 -class _DiffOp_Change extends _DiffOp {
 +class _DiffOp_Change extends _DiffOp { #FIXME: no longer private!
        var $type = 'change';
  
        function __construct( $orig, $closing ) {
   * @private
   * @ingroup DifferenceEngine
   */
 -class _DiffEngine {
 +class _DiffEngine { #FIXME: no longer private!
  
        const MAX_XREF_LENGTH =  10000;
  
                $edits = array();
                $xi = $yi = 0;
                while ( $xi < $n_from || $yi < $n_to ) {
-                       assert( $yi < $n_to || $this->xchanged[$xi] );
-                       assert( $xi < $n_from || $this->ychanged[$yi] );
+                       assert( '$yi < $n_to || $this->xchanged[$xi]' );
+                       assert( '$xi < $n_from || $this->ychanged[$yi]' );
  
                        // Skip matching "snake".
                        $copy = array();
                                while ( list( , $y ) = each( $matches ) ) {
                                        if ( empty( $this->in_seq[$y] ) ) {
                                                $k = $this->_lcs_pos( $y );
-                                               assert( $k > 0 );
+                                               assert( '$k > 0' );
                                                $ymids[$k] = $ymids[$k -1];
                                                break;
                                        }
                                }
                                while ( list ( , $y ) = each( $matches ) ) {
                                        if ( $y > $this->seq[$k -1] ) {
-                                               assert( $y < $this->seq[$k] );
+                                               assert( '$y < $this->seq[$k]' );
                                                // Optimization: this is a common case:
                                                //      next match is just replacing previous match.
                                                $this->in_seq[$this->seq[$k]] = false;
                                                $this->in_seq[$y] = 1;
                                        } elseif ( empty( $this->in_seq[$y] ) ) {
                                                $k = $this->_lcs_pos( $y );
-                                               assert( $k > 0 );
+                                               assert( '$k > 0' );
                                                $ymids[$k] = $ymids[$k -1];
                                        }
                                }
                        }
                }
  
-               assert( $ypos != $this->seq[$end] );
+               assert( '$ypos != $this->seq[$end]' );
  
                $this->in_seq[$this->seq[$end]] = false;
                $this->seq[$end] = $ypos;
   * @private
   * @ingroup DifferenceEngine
   */
 -class Diff {
 +class Diff extends DiffResult {
        var $edits;
  
        /**
         * @param $from_lines array An array of strings.
         *                (Typically these are lines from a file.)
         * @param $to_lines array An array of strings.
 +       * @param $eng _DiffEngine|null The diff engine to use.
         */
 -      function __construct( $from_lines, $to_lines ) {
 -              $eng = new _DiffEngine;
 -              $this->edits = $eng->diff( $from_lines, $to_lines );
 -              // $this->_check($from_lines, $to_lines);
 +      function __construct( $from_lines, $to_lines, $eng = null  ) {
 +              if ( !$eng ) {
 +                      $eng = new _DiffEngine();
 +              }
 +
 +              $edits = $eng->diff( $from_lines, $to_lines );
 +
 +              parent::__construct( $edits );
 +
 +              //$this->_check( $from_lines, $to_lines );
 +      }
 +}
 +
 +/**
 + * Class representing the result of 'diffin' two sequences of strings.
 + * @todo document
 + * @private
 + * @ingroup DifferenceEngine
 + */
 +class DiffResult {
 +
 +      /**
 +       * Constructor.
 +       *
 +       * @param $edits array An array of Edit.
 +       */
 +      function __construct( $edits  ) {
 +              $this->edits = $edits;
        }
  
        /**
         *
         *      $diff = new Diff($lines1, $lines2);
         *      $rev = $diff->reverse();
-        * @return object A Diff object representing the inverse of the
+        * @return Object A Diff object representing the inverse of the
         *                                original diff.
         */
        function reverse() {
@@@ -839,8 -814,8 +839,8 @@@ class MappedDiff extends Diff 
                $mapped_from_lines, $mapped_to_lines ) {
                wfProfileIn( __METHOD__ );
  
-               assert( sizeof( $from_lines ) == sizeof( $mapped_from_lines ) );
-               assert( sizeof( $to_lines ) == sizeof( $mapped_to_lines ) );
+               assert( 'sizeof( $from_lines ) == sizeof( $mapped_from_lines )' );
+               assert( 'sizeof( $to_lines ) == sizeof( $mapped_to_lines )' );
  
                parent::__construct( $mapped_from_lines, $mapped_to_lines );
  
@@@ -1230,7 -1205,7 +1230,7 @@@ class _HWLDF_WordAccumulator 
                                $this->_flushLine( $tag );
                                $word = substr( $word, 1 );
                        }
-                       assert( !strstr( $word, "\n" ) );
+                       assert( '!strstr( $word, "\n" )' );
                        $this->_group .= $word;
                }
        }
@@@ -23,7 -23,7 +23,7 @@@ class DifferenceEngine extends ContextS
         * @private
         */
        var $mOldid, $mNewid;
 -      var $mOldtext, $mNewtext;
 +      var $mOldContent, $mNewContent;
        protected $mDiffLang;
  
        /**
                                $samePage = false;
                        }
  
-                       if ( $samePage && $this->mNewPage->userCan( 'edit', $user ) ) {
+                       if ( $samePage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
                                if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
                                        $out->preventClickjacking();
                                        $rollback = '&#160;&#160;&#160;' . Linker::generateRollback( $this->mNewRev );
  
                if ( $this->mMarkPatrolledLink === null ) {
                        // Prepare a change patrol link, if applicable
-                       if ( $wgUseRCPatrol && $this->mNewPage->userCan( 'patrol', $this->getUser() ) ) {
+                       if ( $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $this->getUser() ) ) {
                                // If we've been given an explicit change identifier, use it; saves time
                                if ( $this->mRcidMarkPatrolled ) {
                                        $rcid = $this->mRcidMarkPatrolled;
                        $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
                        $out->setArticleFlag( true );
  
 -                      if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
 +                      if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) { #NOTE: only needed for B/C: custom rendering of JS/CSS via hook
                                // 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 ( !Hook::isRegistered( 'ShowRawCssJs' )
 +                    || wfRunHooks( 'ShowRawCssJs', array( ContentHandler::getContentText( $this->mNewContent ), $this->mNewPage, $out ) ) ) { #NOTE: deperecated 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 ) ) ) {
 -                              // Handled by extension
 +            } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
 +                // Handled by extension
 +            } elseif( Hooks::isRegistered( 'ArticleViewCustom' )
 +                    && !wfRunHooks( 'ArticleViewCustom', array( ContentHandler::getContentText( $this->mNewContent ), $this->mNewPage, $out ) ) ) { #NOTE: deperecated hook, B/C only
 +                // Handled by extension
                        } else {
                                // Normal page
                                if ( $this->getTitle()->equals( $this->mNewPage ) ) {
                        return false;
                }
  
 -              $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
 +        #TODO: make sure both Content objects have the same content model. What do we do if they don't?
 +
 +              $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
 +     */
 +    function generateContentDiffBody( Content $old, Content $new ) {
 +        #XXX: generate a warning if $old or $new are not instances of TextContent?
 +        #XXX: fail if $old and $new don't have the same content model? or what?
 +
 +        $otext = $old->serialize();
 +        $ntext = $new->serialize();
 +
 +        #XXX: text should be "already segmented". what does that mean?
 +        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
 +     * @deprecated since 1.20, use generateContentDiffBody() instead!
 +     */
 +    function generateDiffBody( $otext, $ntext ) {
 +        wfDeprecated( __METHOD__, "1.20" );
 +
 +        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 generateDiffBody( $otext, $ntext ) {
 +      function generateTextDiffBody( $otext, $ntext ) {
                global $wgExternalDiffEngine, $wgContLang;
  
                wfProfileIn( __METHOD__ );
        /**
         * Generate a debug comment indicating diff generating time,
         * server node, and generator backend.
+        * @return string
         */
        protected function debug( $generator = "internal" ) {
                global $wgShowHostnames;
  
        /**
         * Replace line numbers with the text in the user's language
+        * @return mixed
         */
        function localiseLineNumbers( $text ) {
                return preg_replace_callback( '/<!--LINE (\d+)-->/',
                                $editQuery['oldid'] = $rev->getID();
                        }
  
-                       $msg = $this->msg( $title->userCan( 'edit', $user ) ? 'editold' : 'viewsourceold' )->escaped();
+                       $msg = $this->msg( $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold' )->escaped();
                        $header .= ' (' . Linker::linkKnown( $title, $msg, array(), $editQuery ) . ')';
                        if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
                                $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
  
        /**
         * Use specified text instead of loading from the database
 +     * @deprecated since 1.20
         */
 -      function setText( $oldText, $newText ) {
 -              $this->mOldtext = $oldText;
 -              $this->mNewtext = $newText;
 -              $this->mTextLoaded = 2;
 -              $this->mRevisionsLoaded = true;
 -      }
 +      function setText( $oldText, $newText ) { #FIXME: no longer use this, use setContent()!
 +        wfDeprecated( __METHOD__, "1.20" );
 +
 +        $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.20
 +     */
 +    function setContent( Content $oldContent, Content $newContent ) {
 +        $this->mOldContent = $oldContent;
 +        $this->mNewContent = $newContent;
 +
 +        $this->mTextLoaded = 2;
 +        $this->mRevisionsLoaded = true;
 +    }
  
        /**
         * Set the language in which the diff text is written
                        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 Ibm_db2Updater extends DatabaseUp
                        array( 'addField', 'categorylinks',  'cl_sortkey_prefix', 'patch-cl_sortkey_prefix-field.sql' ),
                        array( 'addField', 'categorylinks',  'cl_collation',      'patch-cl_collation-field.sql' ),
                        array( 'addField', 'categorylinks',  'cl_type',           'patch-cl_type-field.sql' ),
-                       
                        //1.18
                        array( 'doUserNewTalkTimestampNotNull' ),
                        array( 'addIndex', 'user',          'user_email',       'patch-user_email_index.sql' ),
                        array( 'addTable', 'uploadstash',                       'patch-uploadstash.sql' ),
                        array( 'addTable', 'user_former_groups',                'patch-user_former_groups.sql'),
                        array( 'doRebuildLocalisationCache' ), 
-                       
                        // 1.19
-                       array( 'addTable', 'config',                            'patch-config.sql' ),
                        array( 'addIndex', 'logging',       'type_action',      'patch-logging-type-action-index.sql'),
                        array( 'dropField', 'user',         'user_options', 'patch-drop-user_options.sql' ),
                        array( 'addField', 'revision',      'rev_sha1',         'patch-rev_sha1.sql' ),
                        array( 'addField', 'archive',       'ar_sha1',          'patch-ar_sha1.sql' ),
  
 +            // 1.20
 +            // content model stuff for WikiData
 +            array( 'addField',        'revision',     'rev_content_format',           'patch-revision-rev_content_format.sql' ),
 +            array( 'addField',        'revision',     'rev_content_model',            'patch-revision-rev_content_model.sql' ),
 +            array( 'addField',        'archive',      'ar_content_format',            'patch-archive-ar_content_format.sql' ),
 +            array( 'addField',        'archive',      'ar_content_model',                 'patch-archive-ar_content_model.sql' ),
 +            array( 'addField',        'page',     'page_content_model',               'patch-page-page_content_model.sql' ),
+                       // 1.20
+                       array( 'addTable', 'config',                            'patch-config.sql' ),
                );
        }
  }
@@@ -182,24 -182,20 +182,28 @@@ class MysqlUpdater extends DatabaseUpda
                        array( 'addTable', 'user_former_groups',                'patch-user_former_groups.sql'),
  
                        // 1.19
-                       array( 'addTable', 'config',                            'patch-config.sql' ),
                        array( 'addIndex', 'logging',       'type_action',      'patch-logging-type-action-index.sql'),
                        array( 'doMigrateUserOptions' ),
                        array( 'dropField', 'user',         'user_options', 'patch-drop-user_options.sql' ),
                        array( 'addField', 'revision',      'rev_sha1',         'patch-rev_sha1.sql' ),
                        array( 'addField', 'archive',       'ar_sha1',          'patch-ar_sha1.sql' ),
                        array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
-                       array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ),
+                       array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
                        array( 'addField',      'uploadstash',  'us_chunk_inx',         'patch-uploadstash_chunk.sql' ),
                        array( 'addfield', 'job',           'job_timestamp',    'patch-jobs-add-timestamp.sql' ),
 +
 +            // 1.20
 +            // content model stuff for WikiData
 +            array( 'addField',        'revision',     'rev_content_format',           'patch-revision-rev_content_format.sql' ),
 +            array( 'addField',        'revision',     'rev_content_model',            'patch-revision-rev_content_model.sql' ),
 +            array( 'addField',        'archive',      'ar_content_format',            'patch-archive-ar_content_format.sql' ),
 +            array( 'addField',        'archive',      'ar_content_model',                 'patch-archive-ar_content_model.sql' ),
 +            array( 'addField',        'page',     'page_content_model',               'patch-page-page_content_model.sql' ),
+                       array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase.sql' ),
+                       // 1.20
+                       array( 'addTable', 'config',                            'patch-config.sql' ),
+                       array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ),
                );
        }
  
@@@ -39,7 -39,6 +39,6 @@@ class OracleUpdater extends DatabaseUpd
                        array( 'doRecentchangesFK2Cascade' ),
  
                        //1.19
-                       array( 'addTable', 'config', 'patch-config.sql' ),
                        array( 'addIndex', 'logging',       'i05',      'patch-logging_type_action_index.sql'),
                        array( 'addTable', 'globaltemplatelinks', 'patch-globaltemplatelinks.sql' ),
                        array( 'addTable', 'globalnamespaces', 'patch-globalnamespaces.sql' ),
                        array( 'addField', 'job', 'job_timestamp', 'patch-job_timestamp_field.sql' ),
                        array( 'addIndex', 'job', 'i02', 'patch-job_timestamp_index.sql' ),
  
 +            // 1.20
 +            // content model stuff for WikiData
 +            array( 'addField',        'revision',     'rev_content_format',           'patch-revision-rev_content_format.sql' ),
 +            array( 'addField',        'revision',     'rev_content_model',            'patch-revision-rev_content_model.sql' ),
 +            array( 'addField',        'archive',      'ar_content_format',            'patch-archive-ar_content_format.sql' ),
 +            array( 'addField',        'archive',      'ar_content_model',                 'patch-archive-ar_content_model.sql' ),
 +            array( 'addField',        'page',     'page_content_model',               'patch-page-page_content_model.sql' ),
+                       //1.20
+                       array( 'addTable', 'config', 'patch-config.sql' ),
  
                        // KEEP THIS AT THE BOTTOM!!
                        array( 'doRebuildDuplicateFunction' ),
@@@ -61,24 -61,20 +61,28 @@@ class SqliteUpdater extends DatabaseUpd
                        array( 'addTable', 'user_former_groups',                'patch-user_former_groups.sql'),
  
                        // 1.19
-                       array( 'addTable', 'config',                            'patch-config.sql' ),
                        array( 'addIndex', 'logging',       'type_action',      'patch-logging-type-action-index.sql'),
                        array( 'doMigrateUserOptions' ),
                        array( 'dropField', 'user',         'user_options', 'patch-drop-user_options.sql' ),
                        array( 'addField', 'revision',      'rev_sha1',         'patch-rev_sha1.sql' ),
                        array( 'addField', 'archive',       'ar_sha1',          'patch-ar_sha1.sql' ),
                        array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
-                       array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ),
+                       array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
                        array( 'addField',      'uploadstash',  'us_chunk_inx',         'patch-uploadstash_chunk.sql' ),
                        array( 'addfield', 'job',           'job_timestamp',    'patch-jobs-add-timestamp.sql' ),
 +
 +            // 1.20
 +            // content model stuff for WikiData
 +            array( 'addField',        'revision',     'rev_content_format',           'patch-revision-rev_content_format.sql' ),
 +            array( 'addField',        'revision',     'rev_content_model',            'patch-revision-rev_content_model.sql' ),
 +            array( 'addField',        'archive',      'ar_content_format',            'patch-archive-ar_content_format.sql' ),
 +            array( 'addField',        'archive',      'ar_content_model',                 'patch-archive-ar_content_model.sql' ),
 +            array( 'addField',        'page',     'page_content_model',               'patch-page-page_content_model.sql' ),
+                       array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ug_group-length-increase.sql' ),
+                       // 1.20
+                       array( 'addTable', 'config',                            'patch-config.sql' ),
+                       array( 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ),
                );
        }
  
@@@ -30,7 -30,7 +30,7 @@@ class CacheTime 
         */
        function setCacheTime( $t )          { return wfSetVar( $this->mCacheTime, $t ); }
  
 -      /**
 +      /**abstract
         * Sets the number of seconds after which this object should expire.
         * This value is used with the ParserCache.
         * If called with a value greater than the value provided at any previous call,
@@@ -60,6 -60,7 +60,7 @@@
         * The value returned by getCacheExpiry is smaller or equal to the smallest number
         * that was provided to a call of updateCacheExpiry(), and smaller or equal to the
         * value of $wgParserCacheExpireTime.
+        * @return int|mixed|null
         */
        function getCacheExpiry() {
                global $wgParserCacheExpireTime;
@@@ -140,9 -141,8 +141,9 @@@ class ParserOutput extends CacheTime 
                $mProperties = array(),       # Name/value pairs to be cached in the DB
                $mTOCHTML = '',               # HTML of the TOC
                $mTimestamp;                  # Timestamp of the revision
 -      private $mIndexPolicy = '';       # 'index' or 'noindex'?  Any other value will result in no change.
 -      private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
 +          private $mIndexPolicy = '';       # 'index' or 'noindex'?  Any other value will result in no change.
 +          private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
 +        private $mSecondaryDataUpdates = array(); # List of instances of SecondaryDataObject(), used to cause some information extracted from the page in a custom place.
  
        const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
  
        /**
         * callback used by getText to replace editsection tokens
         * @private
+        * @return mixed
         */
        function replaceEditSectionLinksCallback( $m ) {
                global $wgOut, $wgLang;
         * Register a file dependency for this output
         * @param $name string Title dbKey
         * @param $timestamp string MW timestamp of file creation (or false if non-existing)
-        * @param $sha string base 36 SHA-1 of file (or false if non-existing)
+        * @param $sha1 string base 36 SHA-1 of file (or false if non-existing)
         * @return void
         */
        function addImage( $name, $timestamp = null, $sha1 = null ) {
         function recordOption( $option ) {
                 $this->mAccessedOptions[$option] = true;
         }
 +
 +    /**
 +     * Adds an update job to the output. Any update jobs added to the output will eventually bexecuted in order to
 +     * store any secondary information extracted from the page's content.
 +     *
 +     * @param SecondaryDataUpdate $update
 +     */
 +    public function addSecondaryDataUpdate( SecondaryDataUpdate $update ) {
 +        $this->mSecondaryDataUpdates[] = $update;
 +    }
 +
 +    /**
 +     * Returns any SecondaryDataUpdate jobs to be executed in order to store secondary information
 +     * extracted from the page's content.
 +     *
 +     * This does not automatically include an LinksUpdate object for the links in this ParserOutput instance.
 +     * Use getLinksUpdateAndOtherUpdates() if you want that.
 +     *
 +     * @return array an array of instances of SecondaryDataUpdate
 +     */
 +    public function getSecondaryDataUpdates() {
 +        return $this->mSecondaryDataUpdates;
 +    }
 +
 +    /**
 +     * Conveniance method that returns any SecondaryDataUpdate jobs to be executed in order
 +     * to store secondary information extracted from the page's content, including the LinksUpdate object
 +     * for all links stopred in this ParserOutput object.
 +     *
 +     * @param $title Title of the page we're updating. If not given, a title object will be created based on $this->getTitleText()
 +     * @param $recursive Boolean: queue jobs for recursive updates?
 +     *
 +     * @return array an array of instances of SecondaryDataUpdate
 +     */
 +    public function getLinksUpdateAndOtherUpdates( Title $title = null, $recursive = true ) {
 +        if ( empty( $title ) ) {
 +            $title = Title::newFromText( $this->getTitleText() );
 +        }
 +
 +        $linksUpdate = new LinksUpdate( $title, $this, $recursive );
 +
 +        if ( empty( $this->mSecondaryDataUpdates ) ) {
 +            return array( $linksUpdate );
 +        } else {
 +            $updates = array_merge( $this->mSecondaryDataUpdates, array( $linksUpdate ) );
 +        }
 +
 +        return $updates;
 +    }
  }
@@@ -80,7 -80,7 +80,7 @@@ abstract class ResourceLoaderWikiModul
                if ( !$revision ) {
                        return null;
                }
 -              return $revision->getRawText();
 +              return $revision->getRawText(); #FIXME: get raw data from content object after checking the type;
        }
  
        /* Methods */
                if ( count( $mtimes ) ) {
                        $modifiedTime = max( $modifiedTime, max( $mtimes ) );
                }
+               $modifiedTime = max( $modifiedTime, $this->getMsgBlobMtime( $context->getLanguage() ) );
                return $modifiedTime;
        }
  
@@@ -116,8 -116,7 +116,8 @@@ class PageArchive 
                $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'
 +                              'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
 +                'ar_content_format', 'ar_content_model'
                        ),
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                   'ar_title' => $this->title->getDBkey() ),
                                'ar_deleted',
                                'ar_len',
                                'ar_sha1',
 +                'ar_content_format',
 +                'ar_content_model',
                        ),
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                        'ar_title' => $this->title->getDBkey(),
                                        'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
                        __METHOD__ );
                if( $row ) {
-                       return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleId() ) );
+                       return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleID() ) );
                } else {
                        return null;
                }
                $article->loadPageData( 'fromdbmaster' );
                $oldcountable = $article->isCountable();
  
-               $options = 'FOR UPDATE'; // lock page
                $page = $dbw->selectRow( 'page',
                        array( 'page_id', 'page_latest' ),
                        array( 'page_namespace' => $this->title->getNamespace(),
                                   'page_title'     => $this->title->getDBkey() ),
                        __METHOD__,
-                       $options
+                       array( 'FOR UPDATE' ) // lock page
                );
                if( $page ) {
                        $makepage = false;
                                'ar_deleted',
                                'ar_page_id',
                                'ar_len',
 -                              'ar_sha1' ),
 +                              'ar_sha1',
 +                'ar_content_format',
 +                'ar_content_model' ),
                        /* WHERE */ array(
                                'ar_namespace' => $this->title->getNamespace(),
                                'ar_title'     => $this->title->getDBkey(),
@@@ -751,14 -745,20 +750,20 @@@ class SpecialUndelete extends SpecialPa
                $out->addHTML( "<ul>\n" );
                foreach ( $result as $row ) {
                        $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
-                       $link = Linker::linkKnown(
-                               $undelete,
-                               htmlspecialchars( $title->getPrefixedText() ),
-                               array(),
-                               array( 'target' => $title->getPrefixedText() )
-                       );
+                       if ( $title !== null ) {
+                               $item = Linker::linkKnown(
+                                       $undelete,
+                                       htmlspecialchars( $title->getPrefixedText() ),
+                                       array(),
+                                       array( 'target' => $title->getPrefixedText() )
+                               );
+                       } else {
+                               // The title is no longer valid, show as text
+                               $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
+                               $item = htmlspecialchars( $title->getPrefixedText() );
+                       }
                        $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
-                       $out->addHTML( "<li>{$link} ({$revs})</li>\n" );
+                       $out->addHTML( "<li>{$item} ({$revs})</li>\n" );
                }
                $result->free();
                $out->addHTML( "</ul>\n" );
         * @return String: HTML
         */
        function showDiff( $previousRev, $currentRev ) {
 -              $diffEngine = new DifferenceEngine( $this->getContext() );
 +        $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
 +              $diffEngine = $contentHandler->getDifferenceEngine( $this->getContext() );
                $diffEngine->showDiffStyle();
                $this->getOutput()->addHTML(
                        "<div>" .
                                $this->diffHeader( $currentRev, 'n' ) .
                                "</td>\n" .
                        "</tr>" .
 -                      $diffEngine->generateDiffBody(
 -                              $previousRev->getText(), $currentRev->getText() ) .
 +                      $diffEngine->generateContentDiffBody(
 +                              $previousRev->getContent(), $currentRev->getContent() ) .
                        "</table>" .
                        "</div>\n"
                );
  
        private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
                $rev = Revision::newFromArchiveRow( $row,
-                       array( 'page' => $this->mTargetObj->getArticleId() ) );
+                       array( 'page' => $this->mTargetObj->getArticleID() ) );
                $stxt = '';
                $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
                // Build checkboxen...
                        $pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user );
                }
                $userLink = $this->getFileUser( $file );
-               $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text() .
-                       ' (' . $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() . ')';
-               $data = htmlspecialchars( $data );
+               $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
+               $bytes = $this->msg( 'parentheses' )->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )->plain();
+               $data = htmlspecialchars( $data . ' ' . $bytes );
                $comment = $this->getFileComment( $file );
  
                // Add show/hide deletion links if available
         * Fetch revision text link if it's available to all users
         *
         * @param $rev Revision
+        * @param $titleObj Title
+        * @param $ts string Timestamp
         * @return string
         */
        function getPageLink( $rev, $titleObj, $ts ) {
         * Fetch image view link if it's available to all users
         *
         * @param $file File
+        * @param $titleObj Title
+        * @param $ts string A timestamp
+        * @param $key String: a storage key
+        *
         * @return String: HTML fragment
         */
        function getFileLink( $file, $titleObj, $ts, $key ) {
                // Show file deletion warnings and errors
                $status = $archive->getFileStatus();
                if( $status && !$status->isGood() ) {
-                       $out->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) );
+                       $out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
                }
        }
  }
@@@ -183,7 -183,7 +183,7 @@@ $bookstoreList = array
        'AddALL' => 'http://www.addall.com/New/Partner.cgi?query=$1&type=ISBN',
        'PriceSCAN' => 'http://www.pricescan.com/books/bookDetail.asp?isbn=$1',
        'Barnes & Noble' => 'http://search.barnesandnoble.com/bookSearch/isbnInquiry.asp?isbn=$1',
-       'Amazon.com' => 'http://www.amazon.com/exec/obidos/ISBN=$1'
+       'Amazon.com' => 'http://www.amazon.com/gp/search/?field-isbn=$1'
  );
  
  /**
@@@ -240,6 -240,7 +240,7 @@@ $magicWords = array
        'pagenamee'              => array( 1,    'PAGENAMEE'              ),
        'namespace'              => array( 1,    'NAMESPACE'              ),
        'namespacee'             => array( 1,    'NAMESPACEE'             ),
+       'namespacenumber'        => array( 1,    'NAMESPACENUMBER'        ),
        'talkspace'              => array( 1,    'TALKSPACE'              ),
        'talkspacee'             => array( 1,    'TALKSPACEE'              ),
        'subjectspace'           => array( 1,    'SUBJECTSPACE', 'ARTICLESPACE' ),
        'padleft'                => array( 0,    'PADLEFT'                ),
        'padright'               => array( 0,    'PADRIGHT'               ),
        'special'                => array( 0,    'special',               ),
+       'speciale'               => array( 0,    'speciale',              ),
        'defaultsort'            => array( 1,    'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DEFAULTCATEGORYSORT:' ),
        'filepath'               => array( 0,    'FILEPATH:'              ),
        'tag'                    => array( 0,    'tag'                    ),
@@@ -499,42 -501,46 +501,46 @@@ $preloadedMessages = array
        'accesskey-ca-history',
        'accesskey-ca-nstab-main',
        'accesskey-ca-talk',
+       'accesskey-ca-view',
        'accesskey-n-currentevents',
        'accesskey-n-help',
        'accesskey-n-mainpage-description',
        'accesskey-n-portal',
        'accesskey-n-randompage',
        'accesskey-n-recentchanges',
-       'accesskey-n-sitesupport',
        'accesskey-p-logo',
        'accesskey-pt-login',
        'accesskey-search',
        'accesskey-search-fulltext',
        'accesskey-search-go',
        'accesskey-t-permalink',
-       'accesskey-t-print',
        'accesskey-t-recentchangeslinked',
        'accesskey-t-specialpages',
        'accesskey-t-whatlinkshere',
+       'actions',
        'anonnotice',
-       'colon-separator',
        'currentevents',
        'currentevents-url',
        'disclaimerpage',
        'disclaimers',
        'edit',
+       'editsection',
+       'editsection-brackets',
+       'editsectionhint',
        'help',
        'helppage',
-       'history_short',
        'jumpto',
        'jumptonavigation',
        'jumptosearch',
        'lastmodifiedat',
        'mainpage',
        'mainpage-description',
-       'nav-login-createaccount',
+       'mainpage-nstab',
+       'namespaces',
        'navigation',
+       'nav-login-createaccount',
        'nstab-main',
+       'nstab-talk',
        'opensearch-desc',
        'pagecategories',
        'pagecategorieslink',
        'randompage',
        'randompage-url',
        'recentchanges',
-       'recentchanges-url',
        'recentchangeslinked-toolbox',
+       'recentchanges-url',
        'retrievedfrom',
        'search',
        'searcharticle',
        'searchbutton',
        'sidebar',
        'site-atom-feed',
-       'site-rss-feed',
        'sitenotice',
        'specialpages',
        'tagline',
        'tooltip-ca-history',
        'tooltip-ca-nstab-main',
        'tooltip-ca-talk',
+       'tooltip-ca-view',
        'tooltip-n-currentevents',
        'tooltip-n-help',
        'tooltip-n-mainpage-description',
        'tooltip-n-portal',
        'tooltip-n-randompage',
        'tooltip-n-recentchanges',
-       'tooltip-n-sitesupport',
        'tooltip-p-logo',
        'tooltip-p-navigation',
+       'tooltip-p-tb',
        'tooltip-pt-login',
        'tooltip-search',
        'tooltip-search-fulltext',
        'tooltip-search-go',
        'tooltip-t-permalink',
-       'tooltip-t-print',
        'tooltip-t-recentchangeslinked',
        'tooltip-t-specialpages',
        'tooltip-t-whatlinkshere',
+       'variants',
+       'vector-view-edit',
+       'vector-view-history',
+       'vector-view-view',
+       'viewcount',
        'views',
        'whatlinkshere',
  );
@@@ -620,7 -630,6 +630,6 @@@ XHTML id names
  
  # User preference toggles
  'tog-underline'               => 'Link underlining:',
- 'tog-highlightbroken'         => 'Format broken links <a href="" class="new">like this</a> (alternative: Like this<a href="" class="internal">?</a>)',
  'tog-justify'                 => 'Justify paragraphs',
  'tog-hideminor'               => 'Hide minor edits in recent changes',
  'tog-hidepatrolled'           => 'Hide patrolled edits in recent changes',
@@@ -878,7 -887,6 +887,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.',
@@@ -991,6 -999,9 +1000,9 @@@ Please report this to an [[Special:List
  'directorycreateerror' => 'Could not create directory "$1".',
  'filenotfound'         => 'Could not find file "$1".',
  'fileexistserror'      => 'Unable to write to file "$1": File exists.',
+ 'filereadonlyerror'    => 'Unable to modify the file "$1" because the file repository "$2" is in read-only mode.
+ The administrator who locked it offered this explanation: "$3".',
  'unexpected'           => 'Unexpected value: "$1"="$2".',
  'formerror'            => 'Error: Could not submit form.',
  'badarticleerror'      => 'This action cannot be performed on this page.',
@@@ -1056,6 -1067,7 +1068,7 @@@ Do not forget to change your [[Special:
  'userloginnocreate'          => 'Log in',
  'logout'                     => 'Log out',
  'userlogout'                 => 'Log out',
+ 'userlogout-summary'         => '', # do not translate or duplicate this message to other languages
  'notloggedin'                => 'Not logged in',
  'nologin'                    => "Don't have an account? $1.",
  'nologinlink'                => 'Create an account',
@@@ -1134,6 -1146,7 +1147,7 @@@ No e-mail will be sent for any of the f
  'invalidemailaddress'        => 'The e-mail address cannot be accepted as it appears to have an invalid format.
  Please enter a well-formatted address or empty that field.',
  'cannotchangeemail'          => 'Account e-mail addresses cannot be changed on this wiki.',
+ 'emaildisabled'              => 'This site cannot send e-mails.',
  'accountcreated'             => 'Account created',
  'accountcreatedtext'         => 'The user account for $1 has been created.',
  'createaccount-title'        => 'Account creation for {{SITENAME}}',
@@@ -1222,6 -1235,7 +1236,7 @@@ Temporary password: $2'
  
  # Special:ChangeEmail
  'changeemail'          => 'Change e-mail address',
+ 'changeemail-summary'  => '', # do not translate or duplicate this message to other languages
  'changeemail-header'   => 'Change account e-mail address',
  'changeemail-text'     => 'Complete this form to change your e-mail address. You will need to enter your password to confirm this change.',
  'changeemail-no-info'  => 'You must be logged in to access this page directly.',
@@@ -1286,7 -1300,7 +1301,7 @@@ You cannot use the 'e-mail this user' f
  Your current IP address is $3, and the block ID is #$5.
  Please include all above details in any queries you make.",
  'autoblockedtext'                  => 'Your IP address has been automatically blocked because it was used by another user, who was blocked by $1.
- The reason given is this:
+ The reason given is:
  
  :\'\'$2\'\'
  
@@@ -1358,7 -1372,7 +1373,7 @@@ Custom .css and .js pages use a lowerca
  'updated'                          => '(Updated)',
  'note'                             => "'''Note:'''",
  'previewnote'                      => "'''Remember that this is only a preview.'''
- Your changes have not yet been saved!",
+ Your changes have not yet been saved! [[#editform|→ Continue editing]]",
  'previewconflict'                  => 'This preview reflects the text in the upper text editing area as it will appear if you choose to save.',
  'session_fail_preview'             => "'''Sorry! We could not process your edit due to a loss of session data.'''
  Please try again.
@@@ -1374,6 -1388,7 +1389,7 @@@ The edit has been rejected to prevent c
  This sometimes happens when you are using a buggy web-based anonymous proxy service.",
  'edit_form_incomplete'             => "'''Some parts of the edit form did not reach the server; double-check that your edits are intact and try again.'''",
  'editing'                          => 'Editing $1',
+ 'creating'                         => 'Creating $1',
  'editingsection'                   => 'Editing $1 (section)',
  'editingcomment'                   => 'Editing $1 (new section)',
  'editconflict'                     => 'Edit conflict: $1',
@@@ -1446,6 -1461,7 +1462,7 @@@ It appears to have been deleted.'
  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',
  
  # Parser/template warnings
  'expensive-parserfunction-warning'        => "'''Warning:''' This page contains too many expensive parser function calls.
@@@ -1758,6 -1774,7 +1775,7 @@@ Note that their indexes of {{SITENAME}
  'prefsnologin'                  => 'Not logged in',
  'prefsnologintext'              => 'You must be <span class="plainlinks">[{{fullurl:{{#Special:UserLogin}}|returnto=$1}} logged in]</span> to set user preferences.',
  'changepassword'                => 'Change password',
+ 'changepassword-summary'        => '', # do not translate or duplicate this message to other languages
  'prefs-skin'                    => 'Skin',
  'skin-preview'                  => 'Preview',
  'datedefault'                   => 'No preference',
@@@ -2067,6 -2084,7 +2085,7 @@@ Your e-mail address is not revealed whe
  'newsectionsummary'                 => '/* $1 */ new section',
  'rc-enhanced-expand'                => 'Show details (requires JavaScript)',
  'rc-enhanced-hide'                  => 'Hide details',
+ 'rc-old-title'                      => 'originally created as "$1"',
  
  # Recent changes linked
  'recentchangeslinked'          => 'Related changes',
  'upload-warning-subj'         => 'Upload warning',
  'upload-warning-msg'          => 'There was a problem with your upload from [$2]. You may return to the [[Special:Upload/stash/$1|upload form]] to correct this problem.',
  
- 'upload-proto-error'        => 'Incorrect protocol',
- 'upload-proto-error-text'   => 'Remote upload requires URLs beginning with <code>http://</code> or <code>ftp://</code>.',
- 'upload-file-error'         => 'Internal error',
- 'upload-file-error-text'    => 'An internal error occurred when attempting to create a temporary file on the server.
+ 'upload-proto-error'                => 'Incorrect protocol',
+ 'upload-proto-error-text'           => 'Remote upload requires URLs beginning with <code>http://</code> or <code>ftp://</code>.',
+ 'upload-file-error'                 => 'Internal error',
+ 'upload-file-error-text'            => 'An internal error occurred when attempting to create a temporary file on the server.
  Please contact an [[Special:ListUsers/sysop|administrator]].',
- 'upload-misc-error'         => 'Unknown upload error',
- 'upload-misc-error-text'    => 'An unknown error occurred during the upload.
+ 'upload-misc-error'                 => 'Unknown upload error',
+ 'upload-misc-error-text'            => 'An unknown error occurred during the upload.
  Please verify that the URL is valid and accessible and try again.
  If the problem persists, contact an [[Special:ListUsers/sysop|administrator]].',
- 'upload-too-many-redirects' => 'The URL contained too many redirects',
- 'upload-unknown-size'       => 'Unknown size',
- 'upload-http-error'         => 'An HTTP error occured: $1',
+ 'upload-too-many-redirects'         => 'The URL contained too many redirects',
+ 'upload-unknown-size'               => 'Unknown size',
+ 'upload-http-error'                 => 'An HTTP error occured: $1',
+ 'upload-copy-upload-invalid-domain' => 'Copy uploads are not available from this domain.',
  
  # File backend
  'backend-fail-stream'        => 'Could not stream file $1.',
  'backend-fail-closetemp'     => 'Could not close temporary file.',
  'backend-fail-read'          => 'Could not read file $1.',
  'backend-fail-create'        => 'Could not create file $1.',
- 'backend-fail-readonly'      => 'The backend "$1" is currently read-only. The reason given is: "$2"',
- 'backend-fail-synced'        => 'The file "$1" is in an inconsistent state within the internal backends',
- 'backend-fail-connect'       => 'Could not connect to file backend "$1".',
- 'backend-fail-internal'      => 'An unknown error occurred in file backend "$1".',
- 'backend-fail-contenttype'   => 'Could not determine the content type of file to store at "$1".',
- 'backend-fail-batchsize'     => 'Backend given a batch of $1 file {{PLURAL:$1|operation|operations}}; the limit is $2 {{PLURAL:$2|operation|operations}}.',
+ 'backend-fail-maxsize'       => 'Could not create file $1 because it is larger than {{PLURAL:$2|one byte|$2 bytes}}.',
+ 'backend-fail-readonly'      => 'The storage backend "$1" is currently read-only. The reason given is: "\'\'$2\'\'"',
+ 'backend-fail-synced'        => 'The file "$1" is in an inconsistent state within the internal storage backends',
+ 'backend-fail-connect'       => 'Could not connect to storage backend "$1".',
+ 'backend-fail-internal'      => 'An unknown error occurred in storage backend "$1".',
+ 'backend-fail-contenttype'   => 'Could not determine the content type of the file to store at "$1".',
+ 'backend-fail-batchsize'     => 'Storage backend given a batch of $1 file {{PLURAL:$1|operation|operations}}; the limit is $2 {{PLURAL:$2|operation|operations}}.',
+ # File journal
+ 'filejournal-fail-dbconnect' => 'Could not connect to the journal database for storage backend "$1".',
+ 'filejournal-fail-dbquery'   => 'Could not update the journal database for storage backend "$1".',
  
  # Lock manager
  'lockmanager-notlocked'        => 'Could not unlock "$1"; it is not locked.',
@@@ -2296,7 -2320,7 +2321,7 @@@ It cannot be properly checked for secur
  'img-auth-nopathinfo'       => 'Missing PATH_INFO.
  Your server is not set up to pass this information.
  It may be CGI-based and cannot support img_auth.
[//www.mediawiki.org/wiki/Manual:Image_Authorization See image authorization.]',
See https://www.mediawiki.org/wiki/Manual:Image_Authorization.',
  'img-auth-notindir'         => 'Requested path is not in the configured upload directory.',
  'img-auth-badtitle'         => 'Unable to construct a valid title from "$1".',
  'img-auth-nologinnWL'       => 'You are not logged in and "$1" is not in the whitelist.',
@@@ -2382,6 -2406,10 +2407,10 @@@ A [[Special:WhatLinksHere/$2|full list]
  Please see the [$2 file description page] for further information.',
  'sharedupload-desc-here'            => 'This file is from $1 and may be used by other projects.
  The description on its [$2 file description page] there is shown below.',
+ 'sharedupload-desc-edit'            => 'This file is from $1 and may be used by other projects.
+ Maybe you want to edit the description on its [$2 file description page] there.',
+ 'sharedupload-desc-create'          => 'This file is from $1 and may be used by other projects.
+ Maybe you want to edit the description on its [$2 file description page] there.',
  'shareddescriptionfollows'          => '-', # do not translate or duplicate this message to other languages
  'filepage-nofile'                   => 'No file by this name exists.',
  'filepage-nofile-link'              => 'No file by this name exists, but you can [$1 upload it].',
@@@ -2528,7 -2556,9 +2557,9 @@@ It now redirects to [[$2]].'
  'uncategorizedtemplates'          => 'Uncategorized templates',
  'uncategorizedtemplates-summary'  => '', # do not translate or duplicate this message to other languages
  'unusedcategories'                => 'Unused categories',
+ 'unusedcategories-summary'        => '', # do not translate or duplicate this message to other languages
  'unusedimages'                    => 'Unused files',
+ 'unusedimages-summary'            => '', # do not translate or duplicate this message to other languages
  'popularpages'                    => 'Popular pages',
  'popularpages-summary'            => '', # do not translate or duplicate this message to other languages
  'wantedcategories'                => 'Wanted categories',
@@@ -2640,6 -2670,7 +2671,7 @@@ You can narrow down the view by selecti
  'allpagesbadtitle'  => 'The given page title was invalid or had an inter-language or inter-wiki prefix.
  It may contain one or more characters which cannot be used in titles.',
  'allpages-bad-ns'   => '{{SITENAME}} does not have namespace "$1".',
+ 'allpages-hide-redirects' => 'Hide redirects',
  
  # Special:Categories
  'categories'                    => 'Categories',
@@@ -2653,19 -2684,21 +2685,21 @@@ Also see [[Special:WantedCategories|wan
  
  # Special:DeletedContributions
  'deletedcontributions'             => 'Deleted user contributions',
+ 'deletedcontributions-summary'     => '', # do not translate or duplicate this message to other languages
  'deletedcontributions-title'       => 'Deleted user contributions',
  'sp-deletedcontributions-contribs' => 'contributions',
  
  # Special:LinkSearch
- 'linksearch'       => 'External links search',
- 'linksearch-pat'   => 'Search pattern:',
- 'linksearch-ns'    => 'Namespace:',
- 'linksearch-ok'    => 'Search',
- 'linksearch-text'  => 'Wildcards such as "*.wikipedia.org" may be used.
+ 'linksearch'         => 'External links search',
+ 'linksearch-summary' => '', # do not translate or duplicate this message to other languages
+ 'linksearch-pat'     => 'Search pattern:',
+ 'linksearch-ns'      => 'Namespace:',
+ 'linksearch-ok'      => 'Search',
+ 'linksearch-text'    => 'Wildcards such as "*.wikipedia.org" may be used.
  Needs at least a top-level domain, for example "*.org".<br />
  Supported protocols: <tt>$1</tt> (do not add any of these in your search).',
- 'linksearch-line'  => '$1 is linked from $2',
- 'linksearch-error' => 'Wildcards may appear only at the start of the hostname.',
+ 'linksearch-line'    => '$1 is linked from $2',
+ 'linksearch-error'   => 'Wildcards may appear only at the start of the hostname.',
  
  # Special:ListUsers
  'listusersfrom'      => 'Display users starting at:',
@@@ -2712,6 -2745,7 +2746,7 @@@ There may be [[{{MediaWiki:Listgrouprig
  'mailnologin'          => 'No send address',
  'mailnologintext'      => 'You must be [[Special:UserLogin|logged in]] and have a valid e-mail address in your [[Special:Preferences|preferences]] to send e-mail to other users.',
  'emailuser'            => 'E-mail this user',
+ 'emailuser-summary'    => '', # do not translate or duplicate this message to other languages
  'emailpage'            => 'E-mail user',
  'emailpagetext'        => 'You can use the form below to send an e-mail message to this user.
  The e-mail address you entered in [[Special:Preferences|your user preferences]] will appear as the "From" address of the e-mail, so the recipient will be able to reply directly to you.',
  
  # Watchlist
  'watchlist'            => 'My watchlist',
+ 'watchlist-summary'    => '', # do not translate or duplicate this message to other languages
  'mywatchlist'          => 'My watchlist',
  'watchlistfor2'        => 'For $1 $2',
  'nowatchlist'          => 'You have no items on your watchlist.',
@@@ -2947,6 -2982,7 +2983,7 @@@ You can change this page's protection l
  
  # Undelete
  'undelete'                     => 'View deleted pages',
+ 'undelete-summary'             => '', # do not translate or duplicate this message to other languages
  'undeletepage'                 => 'View and restore deleted pages',
  'undeletepagetitle'            => "'''The following consists of deleted revisions of [[:$1|$1]]'''.",
  'viewdeletedpage'              => 'View deleted pages',
  'blanknamespace'                => '(Main)',
  
  # Contributions
- 'contributions'       => 'User contributions',
- 'contributions-title' => 'User contributions for $1',
- 'mycontris'           => 'My contributions',
- 'contribsub2'         => 'For $1 ($2)',
- 'nocontribs'          => 'No changes were found matching these criteria.',
- 'uctop'               => '(top)',
- 'month'               => 'From month (and earlier):',
- 'year'                => 'From year (and earlier):',
+ 'contributions'         => 'User contributions',
+ 'contributions-summary' => '', # do not translate or duplicate this message to other languages
+ 'contributions-title'   => 'User contributions for $1',
+ 'mycontris'             => 'My contributions',
+ 'contribsub2'           => 'For $1 ($2)',
+ 'nocontribs'            => 'No changes were found matching these criteria.',
+ 'uctop'                 => '(top)',
+ 'month'                 => 'From month (and earlier):',
+ 'year'                  => 'From year (and earlier):',
  
  'sp-contributions-newbies'             => 'Show contributions of new accounts only',
  'sp-contributions-newbies-sub'         => 'For new accounts',
@@@ -3038,6 -3075,7 +3076,7 @@@ The latest block log entry is provided 
  'sp-contributions-explain'             => '', # only translate this message to other languages if you have to change it
  'sp-contributions-footer'              => '-', # do not translate or duplicate this message to other languages
  'sp-contributions-footer-anon'         => '-', # do not translate or duplicate this message to other languages
+ 'sp-contributions-footer-newbies'      => '-', # do not translate or duplicate this message to other languages
  
  # What links here
  'whatlinkshere'            => 'What links here',
  'autoblockid'                     => 'Autoblock #$1',
  'block'                           => 'Block user',
  'unblock'                         => 'Unblock user',
+ 'unblock-summary'                 => '', # do not translate or duplicate this message to other languages
  'blockip'                         => 'Block user',
  'blockip-title'                   => 'Block user',
  'blockip-legend'                  => 'Block user',
@@@ -3144,7 -3183,7 +3184,7 @@@ See [[Special:BlockList|IP block list]
  'contribslink'                    => 'contribs',
  'emaillink'                       => 'send e-mail',
  'autoblocker'                     => 'Autoblocked because your IP address has been recently used by "[[User:$1|$1]]".
- The reason given for $1\'s block is: "$2"',
+ The reason given for $1\'s block is "\'\'$2\'\'"',
  'blocklogpage'                    => 'Block log',
  'blocklog-showlog'                => 'This user has been blocked previously.
  The block log is provided below for reference:',
@@@ -3216,6 -3255,7 +3256,7 @@@ To lock or unlock the database, this ne
  
  # Move page
  'move-page'                    => 'Move $1',
+ 'movepage-summary'             => '', # do not translate or duplicate this message to other languages
  'move-page-legend'             => 'Move page',
  'movepagetext'                 => "Using the form below will rename a page, moving all of its history to the new name.
  The old title will become a redirect page to the new title.
@@@ -3310,6 -3350,7 +3351,7 @@@ Please choose another name.'
  
  # Export
  'export'            => 'Export pages',
+ 'export-summary'    => '', # do not translate or duplicate this message to other languages
  'exporttext'        => 'You can export the text and editing history of a particular page or set of pages wrapped in some XML.
  This can be imported into another wiki using MediaWiki via the [[Special:Import|import page]].
  
@@@ -3363,6 -3404,7 +3405,7 @@@ Please visit [//www.mediawiki.org/wiki/
  
  # Special:Import
  'import'                     => 'Import pages',
+ 'import-summary'             => '', # do not translate or duplicate this message to other languages
  'importinterwiki'            => 'Transwiki import',
  'import-interwiki-text'      => "Select a wiki and page title to import.
  Revision dates and editors' names will be preserved.
@@@ -3420,7 -3462,7 +3463,7 @@@ Please try again.'
  # JavaScriptTest
  'javascripttest'                           => 'JavaScript testing',
  'javascripttest-backlink'                  => '< $1', # do not translate or duplicate this message to other languages
- 'javascripttest-disabled'                  => 'This function is disabled.',
+ 'javascripttest-disabled'                  => 'This function has not been enabled on this wiki.',
  'javascripttest-title'                     => 'Running $1 tests',
  'javascripttest-pagetext-noframework'      => 'This page is reserved for running JavaScript tests.',
  'javascripttest-pagetext-unknownframework' => 'Unknown testing framework "$1".',
@@@ -4451,6 -4493,7 +4494,7 @@@ Try normal preview.'
  'lag-warn-high'   => 'Due to high database server lag, changes newer than $1 {{PLURAL:$1|second|seconds}} may not be shown in this list.',
  
  # Watchlist editor
+ 'editwatchlist-summary'        => '', # do not translate or duplicate this message to other languages
  'watchlistedit-numitems'       => 'Your watchlist contains {{PLURAL:$1|1 title|$1 titles}}, excluding talk pages.',
  'watchlistedit-noitems'        => 'Your watchlist contains no titles.',
  'watchlistedit-normal-title'   => 'Edit watchlist',
@@@ -4546,6 -4589,7 +4590,7 @@@ You can also [[Special:EditWatchlist|us
  
  # Special:Version
  'version'                       => 'Version',
+ 'version-summary'               => '', # do not translate or duplicate this message to other languages
  'version-extensions'            => 'Installed extensions',
  'version-specialpages'          => 'Special pages',
  'version-parserhooks'           => 'Parser hooks',
@@@ -4628,6 -4672,7 +4673,7 @@@ Images are shown in full resolution, ot
  
  # Special:Tags
  'tags'                    => 'Valid change tags',
+ 'tags-summary'            => '', # do not translate or duplicate this message to other languages
  'tag-filter'              => '[[Special:Tags|Tag]] filter:',
  'tag-filter-submit'       => 'Filter',
  'tags-title'              => 'Tags',
  
  # Special:ComparePages
  'comparepages'                => 'Compare pages',
+ 'comparepages-summary'        => '', # do not translate or duplicate this message to other languages
  'compare-selector'            => 'Compare page revisions',
  'compare-page1'               => 'Page 1',
  'compare-page2'               => 'Page 2',
@@@ -4678,17 -4724,17 +4725,17 @@@ This site is experiencing technical dif
  'sqlite-no-fts'  => '$1 without full-text search support',
  
  # New logging system
- 'logentry-delete-delete'              => '$1 {{GENDER:$2|deleted}} page $3',
- 'logentry-delete-restore'             => '$1 {{GENDER:$2|restored}} page $3',
- 'logentry-delete-event'               => '$1 {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4',
- 'logentry-delete-revision'            => '$1 {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a revision|$5 revisions}} on page $3: $4',
- 'logentry-delete-event-legacy'        => '$1 {{GENDER:$2|changed}} visibility of log events on $3',
- 'logentry-delete-revision-legacy'     => '$1 {{GENDER:$2|changed}} visibility of revisions on page $3',
- 'logentry-suppress-delete'            => '$1 {{GENDER:$2|suppressed}} page $3',
- 'logentry-suppress-event'             => '$1 secretly {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4',
- 'logentry-suppress-revision'          => '$1 secretly {{GENDER:$2|changed}} visibility of {{PLURAL:$5|a revision|$5 revisions}} on page $3: $4',
- 'logentry-suppress-event-legacy'      => '$1 secretly {{GENDER:$2|changed}} visibility of log events on $3',
- 'logentry-suppress-revision-legacy'   => '$1 secretly {{GENDER:$2|changed}} visibility of revisions on page $3',
+ 'logentry-delete-delete'              => '$1 deleted page $3',
+ 'logentry-delete-restore'             => '$1 restored page $3',
+ 'logentry-delete-event'               => '$1 changed visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4',
+ 'logentry-delete-revision'            => '$1 changed visibility of {{PLURAL:$5|a revision|$5 revisions}} on page $3: $4',
+ 'logentry-delete-event-legacy'        => '$1 changed visibility of log events on $3',
+ 'logentry-delete-revision-legacy'     => '$1 changed visibility of revisions on page $3',
+ 'logentry-suppress-delete'            => '$1 suppressed page $3',
+ 'logentry-suppress-event'             => '$1 secretly changed visibility of {{PLURAL:$5|a log event|$5 log events}} on $3: $4',
+ 'logentry-suppress-revision'          => '$1 secretly changed visibility of {{PLURAL:$5|a revision|$5 revisions}} on page $3: $4',
+ 'logentry-suppress-event-legacy'      => '$1 secretly changed visibility of log events on $3',
+ 'logentry-suppress-revision-legacy'   => '$1 secretly changed visibility of revisions on page $3',
  'revdelete-content-hid'               => 'content hidden',
  'revdelete-summary-hid'               => 'edit summary hidden',
  'revdelete-uname-hid'                 => 'username hidden',
  'revdelete-uname-unhid'               => 'username unhidden',
  'revdelete-restricted'                => 'applied restrictions to administrators',
  'revdelete-unrestricted'              => 'removed restrictions for administrators',
- 'logentry-move-move'                  => '$1 {{GENDER:$2|moved}} page $3 to $4',
- 'logentry-move-move-noredirect'       => '$1 {{GENDER:$2|moved}} page $3 to $4 without leaving a redirect',
- 'logentry-move-move_redir'            => '$1 {{GENDER:$2|moved}} page $3 to $4 over redirect',
- 'logentry-move-move_redir-noredirect' => '$1 {{GENDER:$2|moved}} page $3 to $4 over a redirect without leaving a redirect',
- 'logentry-patrol-patrol'              => '$1 {{GENDER:$2|marked}} revision $4 of page $3 patrolled',
- 'logentry-patrol-patrol-auto'         => '$1 automatically {{GENDER:$2|marked}} revision $4 of page $3 patrolled',
- 'logentry-newusers-newusers'          => '$1 {{GENDER:$2|created}} a user account',
- 'logentry-newusers-create'            => '$1 {{GENDER:$2|created}} a user account',
- 'logentry-newusers-create2'           => '$1 {{GENDER:$2|created}} {{GENDER:$4|a user account}} $3',
- 'logentry-newusers-autocreate'        => 'Account $1 was {{GENDER:$2|created}} automatically',
+ 'logentry-move-move'                  => '$1 moved page $3 to $4',
+ 'logentry-move-move-noredirect'       => '$1 moved page $3 to $4 without leaving a redirect',
+ 'logentry-move-move_redir'            => '$1 moved page $3 to $4 over redirect',
+ 'logentry-move-move_redir-noredirect' => '$1 moved page $3 to $4 over a redirect without leaving a redirect',
+ 'logentry-patrol-patrol'              => '$1 marked revision $4 of page $3 patrolled',
+ 'logentry-patrol-patrol-auto'         => '$1 automatically marked revision $4 of page $3 patrolled',
+ 'logentry-newusers-newusers'          => '$1 created a user account',
+ 'logentry-newusers-create'            => '$1 created a user account',
+ 'logentry-newusers-create2'           => '$1 created a user account $3',
+ 'logentry-newusers-autocreate'        => 'Account $1 was created automatically',
  'newuserlog-byemail'                  => 'password sent by e-mail',
  
+ # For IRC, see bug 34508. Do not change
+ 'revdelete-logentry'          => 'changed revision visibility of "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'logdelete-logentry'          => 'changed event visibility of "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'revdelete-content'           => 'content', # do not translate or duplicate this message to other languages
+ 'revdelete-summary'           => 'edit summary', # do not translate or duplicate this message to other languages
+ 'revdelete-uname'             => 'username', # do not translate or duplicate this message to other languages
+ 'revdelete-hid'               => 'hid $1', # do not translate or duplicate this message to other languages
+ 'revdelete-unhid'             => 'unhid $1', # do not translate or duplicate this message to other languages
+ 'revdelete-log-message'       => '$1 for $2 {{PLURAL:$2|revision|revisions}}', # do not translate or duplicate this message to other languages
+ 'logdelete-log-message'       => '$1 for $2 {{PLURAL:$2|event|events}}', # do not translate or duplicate this message to other languages
+ 'deletedarticle'              => 'deleted "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'suppressedarticle'           => 'suppressed "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'undeletedarticle'            => 'restored "[[$1]]"', # do not translate or duplicate this message to other languages
+ 'patrol-log-line'             => 'marked $1 of $2 patrolled $3', # do not translate or duplicate this message to other languages
+ 'patrol-log-auto'             => '(automatic)', # do not translate or duplicate this message to other languages
+ 'patrol-log-diff'             => 'revision $1', # do not translate or duplicate this message to other languages
+ '1movedto2'                   => 'moved [[$1]] to [[$2]]', # do not translate or duplicate this message to other languages
+ '1movedto2_redir'             => 'moved [[$1]] to [[$2]] over redirect', # do not translate or duplicate this message to other languages
+ 'move-redirect-suppressed'    => 'redirect suppressed', # do not translate or duplicate this message to other languages
+ 'newuserlog-create-entry'     => 'New user account', # do not translate or duplicate this message to other languages
+ 'newuserlog-create2-entry'    => 'created new account $1', # do not translate or duplicate this message to other languages
+ 'newuserlog-autocreate-entry' => 'Account created automatically', # do not translate or duplicate this message to other languages
  # Feedback
  'feedback-bugornote' => 'If you are ready to describe a technical problem in detail please [$1 report a bug].
  Otherwise, you can use the easy form below. Your comment will be added to the page "[$3 $2]", along with your username and what browser you are using.',
@@@ -217,23 -217,21 +217,27 @@@ class RefreshLinks extends Maintenance 
                        return;
                }
  
-               $dbw->begin();
+               $dbw->begin( __METHOD__ );
  
                $options = new ParserOptions;
                $parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() );
 +
 +        $updates = $parserOutput->getLinksUpdateAndOtherUpdates( $title, false );
 +        SecondaryDataUpdate::runUpdates( $updates );
 +
 +        $dbw->commit();
++        // TODO: We don't know what happens here.
+               $update = new LinksUpdate( $title, $parserOutput, false );
+               $update->doUpdate();
+               $dbw->commit( __METHOD__ );
        }
  
        /**
         * Removes non-existing links from pages from pagelinks, imagelinks,
         * categorylinks, templatelinks, externallinks, interwikilinks, langlinks and redirect tables.
         *
-        * @param $maxLag
-        * @param $batchSize The size of deletion batches
+        * @param $maxLag int
+        * @param $batchSize int The size of deletion batches
         *
         * @author Merlijn van Deen <valhallasw@arctus.nl>
         */
diff --combined maintenance/tables.sql
@@@ -25,7 -25,7 +25,7 @@@
  -- in early 2002 after a lot of trouble with the fields
  -- auto-updating.
  --
- -- The Postgres backend uses DATETIME fields for timestamps,
+ -- The Postgres backend uses TIMESTAMPTZ fields for timestamps,
  -- and we will migrate the MySQL definitions at some point as
  -- well.
  --
@@@ -159,11 -159,12 +159,12 @@@ CREATE UNIQUE INDEX /*i*/ug_user_group 
  CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
  
  -- Stores the groups the user has once belonged to.
- -- The user may still belong these groups. Check user_groups.
+ -- The user may still belong to these groups (check user_groups).
+ -- Users are not autopromoted to groups from which they were removed.
  CREATE TABLE /*_*/user_former_groups (
    -- Key to user_id
    ufg_user int unsigned NOT NULL default 0,
-   ufg_group varbinary(16) NOT NULL default ''
+   ufg_group varbinary(32) NOT NULL default ''
  ) /*$wgDBTableOptions*/;
  
  CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
@@@ -259,10 -260,7 +260,10 @@@ CREATE TABLE /*_*/page 
    page_latest int unsigned NOT NULL,
  
    -- Uncompressed length in bytes of the page's current source text.
 -  page_len int unsigned NOT NULL
 +  page_len int unsigned NOT NULL,
 +
 +  -- content model
 +  page_content_model  varbinary(32) default NULL
  ) /*$wgDBTableOptions*/;
  
  CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
@@@ -318,13 -316,7 +319,13 @@@ CREATE TABLE /*_*/revision 
    rev_parent_id int unsigned default NULL,
  
    -- SHA-1 text content hash in base-36
 -  rev_sha1 varbinary(32) NOT NULL default ''
 +  rev_sha1 varbinary(32) NOT NULL default '',
 +
 +  -- content model
 +  rev_content_model  varbinary(32) default NULL,
 +
 +  -- content format (mime type)
 +  rev_content_format varbinary(64) default NULL
  
  ) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
  -- In case tables are created as MyISAM, use row hints for MySQL <5.0 to avoid 4GB limit
@@@ -334,6 -326,7 +335,7 @@@ CREATE INDEX /*i*/rev_timestamp ON /*_*
  CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
  CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
  CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+ CREATE INDEX /*i*/page_user_timestamp ON /*_*/revision (rev_page,rev_user,rev_timestamp);
  
  --
  -- Holds text of individual page revisions.
@@@ -434,14 -427,7 +436,14 @@@ CREATE TABLE /*_*/archive 
    ar_parent_id int unsigned default NULL,
  
    -- SHA-1 text content hash in base-36
 -  ar_sha1 varbinary(32) NOT NULL default ''
 +  ar_sha1 varbinary(32) NOT NULL default '',
 +
 +  -- content model
 +  ar_content_model  varbinary(32) default NULL,
 +
 +  -- content format (mime type)
 +  ar_content_format varbinary(64) default NULL
 +
  ) /*$wgDBTableOptions*/;
  
  CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
@@@ -960,44 -946,44 +962,44 @@@ CREATE INDEX /*i*/fa_user_timestamp ON 
  -- moved into the actual filestore
  --
  CREATE TABLE /*_*/uploadstash (
-       us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
+   us_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
  
-       -- the user who uploaded the file.
-       us_user int unsigned NOT NULL,
+   -- the user who uploaded the file.
+   us_user int unsigned NOT NULL,
  
-       -- file key. this is how applications actually search for the file.
-       -- this might go away, or become the primary key.
-       us_key varchar(255) NOT NULL,
+   -- file key. this is how applications actually search for the file.
+   -- this might go away, or become the primary key.
+   us_key varchar(255) NOT NULL,
  
-       -- the original path
-       us_orig_path varchar(255) NOT NULL,
+   -- the original path
+   us_orig_path varchar(255) NOT NULL,
  
-       -- the temporary path at which the file is actually stored
-       us_path varchar(255) NOT NULL,
+   -- the temporary path at which the file is actually stored
+   us_path varchar(255) NOT NULL,
  
-       -- which type of upload the file came from (sometimes)
-       us_source_type varchar(50),
+   -- which type of upload the file came from (sometimes)
+   us_source_type varchar(50),
  
-       -- the date/time on which the file was added
-       us_timestamp varbinary(14) not null,
+   -- the date/time on which the file was added
+   us_timestamp varbinary(14) NOT NULL,
  
-       us_status varchar(50) not null,
+   us_status varchar(50) NOT NULL,
  
-       -- chunk counter starts at 0, current offset is stored in us_size
-       us_chunk_inx int unsigned NULL,
+   -- chunk counter starts at 0, current offset is stored in us_size
+   us_chunk_inx int unsigned NULL,
  
-       -- file properties from File::getPropsFromPath.  these may prove unnecessary.
-       --
-       us_size int unsigned NOT NULL,
-       -- this hash comes from File::sha1Base36(), and is 31 characters
-       us_sha1 varchar(31) NOT NULL,
-       us_mime varchar(255),
-       -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table
-       us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-       -- image-specific properties
-       us_image_width int unsigned,
-       us_image_height int unsigned,
-       us_image_bits smallint unsigned
+   -- file properties from File::getPropsFromPath.  these may prove unnecessary.
+   --
+   us_size int unsigned NOT NULL,
+   -- this hash comes from File::sha1Base36(), and is 31 characters
+   us_sha1 varchar(31) NOT NULL,
+   us_mime varchar(255),
+   -- Media type as defined by the MEDIATYPE_xxx constants, should duplicate definition in the image table
+   us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+   -- image-specific properties
+   us_image_width int unsigned,
+   us_image_height int unsigned,
+   us_image_bits smallint unsigned
  
  ) /*$wgDBTableOptions*/;
  
@@@ -1251,7 -1237,7 +1253,7 @@@ CREATE INDEX /*i*/page_time ON /*_*/log
  CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
  CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
  CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
- CREATE INDEX /*i*/type_action ON /*_*/logging(log_type, log_action, log_timestamp);
+ CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
  
  
  CREATE TABLE /*_*/log_search (
@@@ -1289,7 -1275,7 +1291,7 @@@ CREATE TABLE /*_*/job 
  ) /*$wgDBTableOptions*/;
  
  CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
- CREATE INDEX /*i*/job_timestamp ON /*_*/job(job_timestamp);
+ CREATE INDEX /*i*/job_timestamp ON /*_*/job (job_timestamp);
  
  
  -- Details of updates to cached special pages
@@@ -1455,7 -1441,7 +1457,7 @@@ CREATE TABLE /*_*/l10n_cache 
  ) /*$wgDBTableOptions*/;
  CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
  
- -- Table for storing JSON message blobs for the resource loader
+ -- Table for caching JSON message blobs for the resource loader
  CREATE TABLE /*_*/msg_resource (
    -- Resource name
    mr_resource varbinary(255) NOT NULL,
@@@ -1476,8 -1462,8 +1478,8 @@@ CREATE TABLE /*_*/msg_resource_links 
  ) /*$wgDBTableOptions*/;
  CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
  
- -- Table for tracking which local files a module depends on that aren't
- -- registered directly.
+ -- Table caching which local files a module depends on that aren't
+ -- registered directly, used for fast retrieval of file dependency.
  -- Currently only used for tracking images that CSS depends on
  CREATE TABLE /*_*/module_deps (
    -- Module name