merged master after 1.20wmf3
authordaniel <daniel.kinzler@wikimedia.de>
Tue, 15 May 2012 06:46:34 +0000 (08:46 +0200)
committerdaniel <daniel.kinzler@wikimedia.de>
Tue, 15 May 2012 06:46:34 +0000 (08:46 +0200)
1  2 
docs/hooks.txt
includes/AutoLoader.php
includes/DefaultSettings.php
includes/Export.php
includes/FeedUtils.php
includes/LinksUpdate.php
includes/Namespace.php
includes/WikiPage.php
includes/api/ApiPurge.php
includes/parser/ParserOutput.php
includes/specials/SpecialUndelete.php

diff --combined docs/hooks.txt
@@@ -401,14 -401,9 +401,14 @@@ token types
  used to retrieve this type of tokens.
  
  'ArticleAfterFetchContent': after fetching content of an article from
 +the database. DEPRECATED, use ArticleAfterFetchContentObject instead.
 +$article: the article (object) being loaded from the database
 +&$content: the content (string) of the article
 +
 +'ArticleAfterFetchContentObject': after fetching content of an article from
  the database
  $article: the article (object) being loaded from the database
 -$content: the content (string) of the article
 +&$content: the content of the article, as a Content object
  
  'ArticleConfirmDelete': before writing the confirmation form for article
        deletion
@@@ -454,7 -449,7 +454,7 @@@ Wiki::articleFromTitle(
  $title: title (object) used to create the article object
  $article: article (object) that will be returned
  
 -'ArticleInsertComplete': After a new article is created
 +'ArticleInsertComplete': After a new article is created. DEPRECATED, use ArticleContentInsertComplete
  $article: WikiPage created
  $user: User creating the article
  $text: New content
@@@ -465,17 -460,6 +465,17 @@@ $section: (No longer used
  $flags: Flags passed to Article::doEdit()
  $revision: New Revision of the article
  
 +'ArticleContentInsertComplete': After a new article is created
 +$article: WikiPage created
 +$user: User creating the article
 +$content: New content as a Content object
 +$summary: Edit summary/comment
 +$isMinor: Whether or not the edit was marked as minor
 +$isWatch: (No longer used)
 +$section: (No longer used)
 +$flags: Flags passed to Article::doEdit()
 +$revision: New Revision of the article
 +
  'ArticleMergeComplete': after merging to article using Special:Mergehistory
  $targetTitle: target title (object)
  $destTitle: destination title (object)
@@@ -524,7 -508,7 +524,7 @@@ $user: the user who did the rollbac
  $revision: the revision the page was reverted back to
  $current: the reverted revision
  
 -'ArticleSave': before an article is saved
 +'ArticleSave': before an article is saved. DEPRECATED, use ArticleContentSave instead
  $article: the WikiPage (object) being saved
  $user: the user (object) saving the article
  $text: the new article text
@@@ -533,16 -517,7 +533,16 @@@ $isminor: minor fla
  $iswatch: watch flag
  $section: section #
  
 -'ArticleSaveComplete': After an article has been updated
 +'ArticleContentSave': before an article is saved.
 +$article: the WikiPage (object) being saved
 +$user: the user (object) saving the article
 +$content: the new article content, as a Content object
 +$summary: the article summary (comment)
 +$isminor: minor flag
 +$iswatch: watch flag
 +$section: section #
 +
 +'ArticleSaveComplete': After an article has been updated. DEPRECATED, use ArticleContentSaveComplete instead.
  $article: WikiPage modified
  $user: User performing the modification
  $text: New content
@@@ -555,19 -530,6 +555,19 @@@ $revision: New Revision of the articl
  $status: Status object about to be returned by doEdit()
  $baseRevId: the rev ID (or false) this edit was based on
  
 +'ArticleContentSaveComplete': After an article has been updated
 +$article: WikiPage modified
 +$user: User performing the modification
 +$content: New content, as a Content object
 +$summary: Edit summary/comment
 +$isMinor: Whether or not the edit was marked as minor
 +$isWatch: (No longer used)
 +$section: (No longer used)
 +$flags: Flags passed to Article::doEdit()
 +$revision: New Revision of the article
 +$status: Status object about to be returned by doEdit()
 +$baseRevId: the rev ID (or false) this edit was based on
 +
  'ArticleUndelete': When one or more revisions of an article are restored
  $title: Title corresponding to the article restored
  $create: Whether or not the restoration caused the page to be created
@@@ -594,19 -556,11 +594,19 @@@ object to both indicate that the outpu
  follwed an redirect
  $article: target article (object)
  
 -'ArticleViewCustom': allows to output the text of the article in a different format than wikitext
 +'ArticleViewCustom': allows to output the text of the article in a different format than wikitext.
 +DEPRECATED, use ArticleContentViewCustom instead.
 +Note that it is preferrable to implement proper handing for a custom data type using the ContentHandler facility.
  $text: text of the page
  $title: title of the page
  $output: reference to $wgOut
  
 +'ArticleContentViewCustom': allows to output the text of the article in a different format than wikitext.
 +Note that it is preferrable to implement proper handing for a custom data type using the ContentHandler facility.
 +$content: content of the page, as a Content object
 +$title: title of the page
 +$output: reference to $wgOut
 +
  'AuthPluginAutoCreate': Called when creating a local account for an user logged
  in from an external authentication method
  $user: User object created locally
@@@ -734,16 -688,6 +734,16 @@@ the collation given in $collationName
  'ConfirmEmailComplete': Called after a user's email has been confirmed successfully
  $user: user (object) whose email is being confirmed
  
 +'ContentHandlerDefaultModelFor': Called when the default content model is determiend
 +for a given title. May be used to assign a different model for that title.
 +$title: the Title in question
 +&$model: the model name. Use with CONTENT_MODEL_XXX constants.
 +
 +'ContentHandlerForModelID': Called when a ContentHandler is requested for a given
 +cointent model name, but no entry for that model exists in $wgContentHandlers.
 +$modeName: the requested content model name
 +&$handler: set this to a ContentHandler object, if desired.
 +
  'ContribsPager::getQueryInfo': Before the contributions query is about to run
  &$pager: Pager object for contributions
  &$queryInfo: The query for the contribs Pager
@@@ -809,19 -753,12 +809,19 @@@ $section: Section being edite
  &$error: Error message to return
  $summary: Edit summary for page
  
 -'EditFilterMerged': Post-section-merge edit filter
 +'EditFilterMerged': Post-section-merge edit filter.
 +DEPRECATED, use EditFilterMergedContent instead.
  $editor: EditPage instance (object)
  $text: content of the edit box
  &$error: error message to return
  $summary: Edit summary for page
  
 +'EditFilterMergedContent': Post-section-merge edit filter
 +$editor: EditPage instance (object)
 +$content: content of the edit box, as a Content object
 +&$error: error message to return
 +$summary: Edit summary for page
 +
  'EditFormPreloadText': Allows population of the edit form when creating
  new pages
  &$text: Text to preload with
@@@ -884,28 -821,14 +884,28 @@@ $title: title of page being edite
  &$msg: localization message name, overridable. Default is either 'copyrightwarning' or 'copyrightwarning2'
  
  'EditPageGetDiffText': Allow modifying the wikitext that will be used in
 -"Show changes"
 +"Show changes". DEPRECATED. Use EditPageGetDiffContent instead.
 +Note that it is preferrable to implement diff handling for different data types using the ContentHandler facility.
  $editPage: EditPage object
  &$newtext: wikitext that will be used as "your version"
  
 -'EditPageGetPreviewText': Allow modifying the wikitext that will be previewed
 +'EditPageGetDiffContent': Allow modifying the wikitext that will be used in
 +"Show changes".
 +Note that it is preferrable to implement diff handling for different data types using the ContentHandler facility.
 +$editPage: EditPage object
 +&$newtext: wikitext that will be used as "your version"
 +
 +'EditPageGetPreviewText': Allow modifying the wikitext that will be previewed.
 +DEPRECATED. Use EditPageGetPreviewContent instead.
 +Note that it is preferrable to implement previews for different data types using the COntentHandler facility.
  $editPage: EditPage object
  &$toparse: wikitext that will be parsed
  
 +'EditPageGetPreviewContent': Allow modifying the wikitext that will be previewed.
 +Note that it is preferrable to implement previews for different data types using the COntentHandler facility.
 +$editPage: EditPage object
 +&$content: Content object to be previewed (may be replaced by hook function)
 +
  'EditPageNoSuchSection': When a section edit request is given for an non-existent section
  &$editpage: The current EditPage object
  &$res: the HTML of the error text
@@@ -1428,6 -1351,11 +1428,11 @@@ using this hook
        BaseTemplate::makeListItem for details on the format of individual
        items inside of this array
  
+ 'NamespaceIsMovable': Called when determining if it is possible to pages in a namespace.
+ $index: Integer; the index of the namespace being checked.
+ $result: Boolean; whether MediaWiki currently thinks that pages in this namespace are movable.
+ Hooks may change this value to override the return value of MWNamespace::isMovable()
  'NewRevisionFromEditComplete': called when a revision was inserted
  due to an edit
  $article: the WikiPage edited
@@@ -1724,8 -1652,7 +1729,8 @@@ $query : Original query
  'ShowMissingArticle': Called when generating the output for a non-existent page
  $article: The article object corresponding to the page
  
 -'ShowRawCssJs': Customise the output of raw CSS and JavaScript in page views
 +'ShowRawCssJs': Customise the output of raw CSS and JavaScript in page views.
 +DEPRECATED, use the ContentHandler facility to handle CSS and JavaScript!
  $text: Text being shown
  $title: Title of the custom script/stylesheet page
  $output: Current OutputPage object
diff --combined includes/AutoLoader.php
@@@ -58,7 -58,6 +58,6 @@@ $wgAutoloadLocalClasses = array
        'DerivativeRequest' => 'includes/WebRequest.php',
        'DeviceDetection' => 'includes/DeviceDetection.php',
        'DiffHistoryBlob' => 'includes/HistoryBlob.php',
        'DoubleReplacer' => 'includes/StringUtils.php',
        'DummyLinker' => 'includes/Linker.php',
        'Dump7ZipOutput' => 'includes/Export.php',
        'RevisionList' => 'includes/RevisionList.php',
        'RSSFeed' => 'includes/Feed.php',
        'Sanitizer' => 'includes/Sanitizer.php',
-     'SecondaryDataUpdate' => 'includes/SecondaryDataUpdate.php',
-     'SecondaryDBDataUpdate' => 'includes/SecondaryDBDataUpdate.php',
+       'DataUpdate' => 'includes/DataUpdate.php',
+       'SqlDataUpdate' => 'includes/SqlDataUpdate.php',
        'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php',
        'SiteConfiguration' => 'includes/SiteConfiguration.php',
        'SiteStats' => '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
        'CachedAction' => 'includes/actions/CachedAction.php',
        'CreditsAction' => 'includes/actions/CreditsAction.php',
        'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
        'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
        'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
-       'ApiFormatNone' => 'includes/api/ApiFormatNone.php',
        'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
        'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php',
        'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
        'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php',
        'MediaWikiBagOStuff' => 'includes/objectcache/SqlBagOStuff.php',
        'MemCachedClientforWiki' => 'includes/objectcache/MemcachedClient.php',
+       'MemcachedBagOStuff' => 'includes/objectcache/MemcachedBagOStuff.php',
+       'MemcachedPeclBagOStuff' => 'includes/objectcache/MemcachedPeclBagOStuff.php',
        'MemcachedPhpBagOStuff' => 'includes/objectcache/MemcachedPhpBagOStuff.php',
        'MultiWriteBagOStuff' => 'includes/objectcache/MultiWriteBagOStuff.php',
        'MWMemcached' => 'includes/objectcache/MemcachedClient.php',
        'TestFileIterator' => 'tests/testHelpers.inc',
        'TestRecorder' => 'tests/testHelpers.inc',
  
 +      # tests/phpunit
 +      'RevisionStorageTest' => 'tests/phpunit/includes/RevisionStorageTest.php',
 +      'WikiPageTest' => 'tests/phpunit/includes/WikiPageTest.php',
 +      'WikitextContentTest' => 'tests/phpunit/includes/WikitextContentTest.php',
 +      'JavascriptContentTest' => 'tests/phpunit/includes/JavascriptContentTest.php',
 +      'DummyContentHandlerForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
 +      'DummyContentForTesting' => 'tests/phpunit/includes/ContentHandlerTest.php',
 +
        # tests/parser
        'ParserTest' => 'tests/parser/parserTest.inc',
        'ParserTestParserHook' => 'tests/parser/parserTestsParserHook.php',
@@@ -640,40 -640,6 +640,40 @@@ $wgMediaHandlers = array
        'image/x-djvu' => 'DjVuHandler', // compat
  );
  
 +/**
 + * Plugins for page content model handling.
 + * Each entry in the array maps a model id 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>
 +);
 +
 +/**
 + * Mime types for content formats.
 + * Each entry in the array maps a content format to a mime type.
 + *
 + * Extensions that define their own content formats can register
 + * the appropriate mime types in this array.
 + *
 + * Such extensions shall use content format IDs
 + * larger than 100 and register the ids they use at
 + * <http://mediawiki.org/ContentHandler/registry>
 + * to avoid conflicts with other extensions.
 + */
 +$wgContentFormatMimeTypes = array(
 +      CONTENT_FORMAT_WIKITEXT => 'text/x-wiki',
 +      CONTENT_FORMAT_JAVASCRIPT => 'text/javascript',
 +      CONTENT_FORMAT_CSS => 'text/css',
 +      CONTENT_FORMAT_TEXT => 'text/plain',
 +      CONTENT_FORMAT_HTML => 'text/html',
 +      CONTENT_FORMAT_XML => 'application/xml',
 +      CONTENT_FORMAT_JSON => 'application/json',
 +      CONTENT_FORMAT_SERIALIZED => 'application/vnd.php.serialized',
 +);
 +
  /**
   * Resizing can be done using PHP's internal image libraries or using
   * ImageMagick or another third-party converter, e.g. GraphicMagick.
@@@ -1626,6 -1592,7 +1626,7 @@@ $wgObjectCaches = array
        'xcache' => array( 'class' => 'XCacheBagOStuff' ),
        'wincache' => array( 'class' => 'WinCacheBagOStuff' ),
        'memcached-php' => array( 'class' => 'MemcachedPhpBagOStuff' ),
+       'memcached-pecl' => array( 'class' => 'MemcachedPeclBagOStuff' ),
        'hash' => array( 'class' => 'HashBagOStuff' ),
  );
  
@@@ -5852,31 -5819,6 +5853,31 @@@ $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
 + * * 'serializeContent': serializeContent to default format
 + */
 +$wgContentHandlerTextFallback = 'ignore';
 +
 +/**
 + * Compatibility switch for running ContentHandler code withoput a schema update.
 + * Set to false to disable use of the database fields introduced by the ContentHandler facility.
 + *
 + * @deprecated this is only here to allow code deployment without a database schema update on large sites.
 + *             get rid of it in the next version.
 + */
 +$wgContentHandlerUseDB = true;
 +
  /**
   * For really cool vim folding this needs to be at the end:
   * vim: foldmarker=@{,@} foldmethod=marker
diff --combined includes/Export.php
@@@ -338,7 -338,7 +338,7 @@@ class WikiExporter 
                        } elseif ( $this->history & WikiExporter::RANGE ) {
                                # Dump of revisions within a specified range
                                $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page' );
-                               $opts['ORDER BY'] = 'rev_page ASC, rev_id ASC';
+                               $opts['ORDER BY'] = array( 'rev_page ASC', 'rev_id ASC' );
                        } else {
                                # Uknown history specification parameter?
                                wfProfileOut( __METHOD__ );
@@@ -644,16 -644,6 +644,16 @@@ class XmlDumpWriter 
                        $out .= "      " . Xml::elementClean( 'comment', array(), strval( $row->rev_comment ) ) . "\n";
                }
  
 +              if ( isset( $row->rev_content_model ) && !is_null( $row->rev_content_model )  ) {
 +                      $name = ContentHandler::getContentModelName( $row->rev_content_model );
 +                      $out .= "      " . Xml::element('model', array( 'name' => $name ), strval( $row->rev_content_model ) ) . "\n";
 +              }
 +
 +              if ( isset( $row->rev_content_format ) && !is_null( $row->rev_content_format ) ) {
 +                      $mime = ContentHandler::getContentFormatMimeType( $row->rev_content_format );
 +                      $out .= "      " . Xml::element('format', array( 'mime' => $mime ), strval( $row->rev_content_format ) ) . "\n";
 +              }
 +
                $text = '';
                if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
                        $out .= "      " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
diff --combined includes/FeedUtils.php
@@@ -1,4 -1,25 +1,25 @@@
  <?php
+ /**
+  * Helper functions for feeds.
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  * GNU General Public License for more details.
+  *
+  * You should have received a copy of the GNU General Public License along
+  * with this program; if not, write to the Free Software Foundation, Inc.,
+  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+  * http://www.gnu.org/copyleft/gpl.html
+  *
+  * @file
+  * @ingroup Feed
+  */
  
  /**
   * Helper functions for feeds
@@@ -117,8 -138,7 +138,8 @@@ class FeedUtils 
                        $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->createDifferenceEngine( $title, $oldid, $newid );
                                $diffText = $de->getDiff(
                                        wfMsg( 'previousrevision' ), // hack
                                        wfMsg( 'revisionasof',
diff --combined includes/LinksUpdate.php
   *
   * @todo document (e.g. one-sentence top-level class description).
   */
- class LinksUpdate extends SecondaryDBDataUpdate {
+ class LinksUpdate extends SqlDataUpdate {
  
-       /**@{{
-        * @private
-        */
-       var $mId,            //!< Page ID of the article linked from
+       // @todo: make members protected, but make sure extensions don't break
+       public $mId,         //!< Page ID of the article linked from
                $mTitle,         //!< Title object of the article linked from
-               $mParserOutput,  //!< Whether to queue jobs for recursive update
+               $mParserOutput,  //!< Parser output
                $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
@@@ -40,8 -39,9 +39,7 @@@
                $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
-       /**@}}*/
  
        /**
         * Constructor
        function __construct( $title, $parserOutput, $recursive = true ) {
                parent::__construct( );
  
-               if ( !is_object( $title ) ) {
+               if ( !( $title instanceof Title ) ) {
                        throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
                                "Please see Article::editUpdates() for an invocation example.\n" );
                }
  
-               if ( !is_object( $parserOutput ) ) {
+               if ( !( $parserOutput instanceof ParserOutput ) ) {
                        throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
                                "Please see WikiPage::doEditUpdates() for an invocation example.\n" );
                }
@@@ -67,7 -67,6 +65,7 @@@
                $this->mId = $title->getArticleID();
  
                $this->mParserOutput = $parserOutput;
 +
                $this->mLinks = $parserOutput->getLinks();
                $this->mImages = $parserOutput->getImages();
                $this->mTemplates = $parserOutput->getTemplates();
  /**
   * Update object handling the cleanup of links tables after a page was deleted.
   **/
- class LinksDeletionUpdate extends SecondaryDBDataUpdate {
+ class LinksDeletionUpdate extends SqlDataUpdate {
  
-       /**@{{
-        * @private
-        */
-       var $mWikiPage;     //!< WikiPage the wikipage that was deleted
-       /**@}}*/
+       protected $mPage;     //!< WikiPage the wikipage that was deleted
  
        /**
         * Constructor
                if ( !$this->mDb->cleanupTriggers() ) {
                        # Clean up recentchanges entries...
                        $this->mDb->delete( 'recentchanges',
-                                     array( 'rc_type != ' . RC_LOG,
-                                          'rc_namespace' => $title->getNamespace(),
-                                          'rc_title' => $title->getDBkey() ),
-                                     __METHOD__ );
+                               array( 'rc_type != ' . RC_LOG,
+                                       'rc_namespace' => $title->getNamespace(),
+                                       'rc_title' => $title->getDBkey() ),
+                               __METHOD__ );
                        $this->mDb->delete( 'recentchanges',
-                                     array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
-                                     __METHOD__ );
+                               array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
+                               __METHOD__ );
                }
        }
  }
diff --combined includes/Namespace.php
@@@ -65,7 -65,15 +65,15 @@@ class MWNamespace 
         */
        public static function isMovable( $index ) {
                global $wgAllowImageMoving;
-               return !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving )  || $index == NS_CATEGORY );
+               $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving )  || $index == NS_CATEGORY );
+               /**
+                * @since 1.20
+                */
+               wfRunHooks( 'NamespaceIsMovable', array( $index, &$result ) );
+               return $result;
        }
  
        /**
         * Returns array of all defined namespaces with their canonical
         * (English) names.
         *
 +       * @param bool $rebuild rebuild namespace list (default = false). Used for testing.
 +       *
         * @return array
         * @since 1.17
         */
 -      public static function getCanonicalNamespaces() {
 +      public static function getCanonicalNamespaces( $rebuild = false ) {
                static $namespaces = null;
 -              if ( $namespaces === null ) {
 +              if ( $namespaces === null || $rebuild ) {
                        global $wgExtraNamespaces, $wgCanonicalNamespaceNames;
                        $namespaces = array( NS_MAIN => '' ) + $wgCanonicalNamespaceNames;
                        if ( is_array( $wgExtraNamespaces ) ) {
diff --combined includes/WikiPage.php
@@@ -230,21 -230,7 +230,21 @@@ class WikiPage extends Page 
         * @return Array
         */
        public function getActionOverrides() {
 -              return array();
 +              $content_handler = $this->getContentHandler();
 +              return $content_handler->getActionOverrides();
 +      }
 +
 +      /**
 +       * Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
 +       *
 +       * Shorthand for ContentHandler::getForModelID( $this->getContentModel() );
 +       *
 +       * @return ContentHandler
 +       *
 +       * @since 1.WD
 +       */
 +      public function getContentHandler() {
 +              return ContentHandler::getForModelID( $this->getContentModel() );
        }
  
        /**
         * @return array
         */
        public static function selectFields() {
 -              return array(
 +              global $wgContentHandlerUseDB;
 +
 +              $fields = array(
                        'page_id',
                        'page_namespace',
                        'page_title',
                        'page_latest',
                        'page_len',
                );
 +
 +              if ( $wgContentHandlerUseDB ) {
 +                      $fields[] = 'page_content_model';
 +              }
 +
 +              return $fields;
        }
  
        /**
         * @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 id (see the CONTENT_MODEL_XXX constants).
 +       *
 +       * Will use the revisions actual content model if the page exists,
 +       * and the page's default if the page doesn't exist yet.
 +       *
 +       * @return int
 +       *
 +       * @since 1.WD
 +       */
 +      public function getContentModel() {
 +              if ( $this->exists() ) {
 +                      # look at the revision's actual content model
 +                      $rev = $this->getRevision();
 +
 +                      if ( $rev !== null ) {
 +                              return $rev->getContentModel();
 +                      } else {
 +                              wfWarn( "Page exists but has no revision!" );
 +                      }
                }
 +
 +              # use the default model for this page
 +              return $this->mTitle->getContentModel();
        }
  
        /**
        }
  
        /**
 -       * Get the text of the current revision. No side-effects...
 +       * 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 String|bool The text of the current revision. False on failure
 +       * @return Content|null The content of the current revision
 +       *
 +       * @since 1.WD
         */
 -      public function getText( $audience = Revision::FOR_PUBLIC ) {
 +      public function getContent( $audience = Revision::FOR_PUBLIC ) {
                $this->loadLastEdit();
                if ( $this->mLastRevision ) {
 -                      return $this->mLastRevision->getText( $audience );
 +                      return $this->mLastRevision->getContent( $audience );
                }
 -              return false;
 +              return null;
        }
  
        /**
         * Get the text of the current revision. No side-effects...
         *
 -       * @return String|bool The text of the current revision. False on failure
 +       * @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 String|false The text of the current revision
 +       * @deprecated as of 1.WD, getContent() should be used instead.
         */
 -      public function getRawText() {
 +      public function getText( $audience = Revision::FOR_PUBLIC ) { #@todo: deprecated, replace usage!
 +              wfDeprecated( __METHOD__, '1.WD' );
 +
                $this->loadLastEdit();
                if ( $this->mLastRevision ) {
 -                      return $this->mLastRevision->getRawText();
 +                      return $this->mLastRevision->getText( $audience );
                }
                return false;
        }
  
 +      /**
 +       * Get the text of the current revision. No side-effects...
 +       *
 +       * @return String|bool The text of the current revision. False on failure
 +       * @deprecated as of 1.WD, getContent() should be used instead.
 +       */
 +      public function getRawText() { #@todo: deprecated, replace usage!
 +              wfDeprecated( __METHOD__, '1.WD' );
 +
 +              return $this->getText( Revision::RAW );
 +      }
 +
        /**
         * @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 = $editInfo->pstContent;
 +              } 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':
 +              $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.
 -                              return (bool)count( $editInfo->output->getLinks() );
 +                              $hasLinks = (bool)count( $editInfo->output->getLinks() );
                        } else {
 -                              return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
 +                              $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->getContentHandler()->isParserCacheSupported();
        }
  
        /**
         * @param $parserOptions ParserOptions to use for the parse operation
         * @param $oldid Revision ID to get the text from, passing null or 0 will
         *               get the current revision (default value)
 +       * @param $context IContextSource context for parsing
 +       *
         * @return ParserOutput or false if the revision was not found
         */
 -      public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
 +      public function getParserOutput( ParserOptions $parserOptions, $oldid = null, IContextSource $context = null ) {
                wfProfileIn( __METHOD__ );
  
                $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
                        $oldid = $this->getLatest();
                }
  
 -              $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
 +              $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache, null, $context );
                $pool->execute();
  
                wfProfileOut( __METHOD__ );
  
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
                        if ( $this->mTitle->exists() ) {
 -                              $text = $this->getRawText();
 +                              $text = ContentHandler::getContentText( $this->getContent() );
                        } else {
                                $text = false;
                        }
         * @private
         */
        public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
 +              global $wgContentHandlerUseDB;
 +
                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() );
  
                }
  
                $now = wfTimestampNow();
 +              $row = array( /* SET */
 +                      'page_latest'      => $revision->getId(),
 +                      'page_touched'     => $dbw->timestamp( $now ),
 +                      'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
 +                      'page_is_redirect' => $rt !== null ? 1 : 0,
 +                      'page_len'         => $len,
 +              );
 +
 +              if ( $wgContentHandlerUseDB ) {
 +                      $row[ 'page_content_model' ] = $revision->getContentModel();
 +              }
 +
                $dbw->update( 'page',
 -                      array( /* SET */
 -                              'page_latest'      => $revision->getId(),
 -                              'page_touched'     => $dbw->timestamp( $now ),
 -                              'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
 -                              'page_is_redirect' => $rt !== null ? 1 : 0,
 -                              'page_len'         => $len,
 -                      ),
 +                      $row,
                        $conditions,
                        __METHOD__ );
  
         * @param $undo Revision
         * @param $undoafter Revision Must be an earlier revision than $undo
         * @return mixed string on success, false on failure
 +       * @deprecated since 1.WD: 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();
 +              wfDeprecated( __METHOD__, '1.WD' );
  
 -              if ( $cur_text == $undo_text ) {
 -                      # No use doing a merge if it's just a straight revert.
 -                      return $undoafter_text;
 -              }
 +              $this->loadLastEdit();
  
 -              $undone_text = '';
 +              if ( $this->mLastRevision ) {
 +                      if ( is_null( $undoafter ) ) {
 +                              $undoafter = $undo->getPrevious();
 +                      }
  
 -              if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) {
 -                      return false;
 +                      $handler = $this->getContentHandler();
 +                      $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
 +
 +                      if ( !$undone ) {
 +                              return false;
 +                      } else {
 +                              return ContentHandler::getContentText( $undone );
 +                      }
                }
  
 -              return $undone_text;
 +              return false;
        }
  
        /**
         * @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 String new complete article text, or null if error
 +       *
 +       * @deprecated since 1.WD, use replaceSectionContent() instead
         */
        public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
 +              wfDeprecated( __METHOD__, '1.WD' );
 +
 +              $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() ); #XXX: could make section title, but that's not required.
 +
 +              $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
 +
 +              return ContentHandler::getContentText( $newContent ); #XXX: unclear what will happen for non-wikitext!
 +      }
 +
 +      /**
 +       * @param $section null|bool|int or a section number (0, 1, 2, T1, T2...)
 +       * @param $content Content: new content 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 Content new complete article content, or null if error
 +       *
 +       * @since 1.WD
 +       */
 +      public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
                wfProfileIn( __METHOD__ );
  
                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' ) {
 -                              $oldtext = $this->getRawText();
 -                              if ( $oldtext === false ) {
 +                              $oldContent = $this->getContent();
 +                              if ( ! $oldContent ) {
                                        wfDebug( __METHOD__ . ": no page text\n" );
                                        wfProfileOut( __METHOD__ );
                                        return null;
                                        return null;
                                }
  
 -                              $oldtext = $rev->getText();
 +                              $oldContent = $rev->getContent();
                        }
  
 -                      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;
 -
 -                              $text = $wgParser->replaceSection( $oldtext, $section, $text );
 -                      }
 +                      $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
                }
  
                wfProfileOut( __METHOD__ );
 -              return $text;
 +              return $newContent;
        }
  
        /**
         *     revision:                The revision object for the inserted revision, or null
         *
         *  Compatibility note: this function previously returned a boolean value indicating success/failure
 +       *
 +       * @deprecated since 1.WD: use doEditContent() instead.
         */
 -      public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
 +      public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) { #@todo: use doEditContent() instead
 +              wfDeprecated( __METHOD__, '1.WD' );
 +
 +              $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
 +       *
 +       * @since 1.WD
 +       */
 +      public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
 +                                                                 User $user = null, $serialisation_format = null ) {
                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,
 +                      $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,
 +                              $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()->unserializeContent( $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->getModel(),
 +                              'content_format' => $serialisation_format,
                        ) );
  
 -                      $changed = ( strcmp( $text, $oldtext ) != 0 );
 +                      $changed = !$content->equals( $old_content );
  
                        if ( $changed ) {
 +                              // TODO: validate!
 +                              if ( $content->isValid() ) {
 +
 +                              }
 +
                                $dbw->begin( __METHOD__ );
                                $revisionId = $revision->insertOn( $dbw );
  
                                return $status;
                        }
  
 +                      // TODO: create content diff to pass to update objects that might need it
 +
                        # Update links tables, site stats, etc.
 -                      $this->doEditUpdates( $revision, $user, array( 'changed' => $changed,
 -                              'oldcountable' => $oldcountable ) );
 +                      $this->doEditUpdates(
 +                              $revision,
 +                              $user,
 +                              array(
 +                                      'changed' => $changed,
 +                                      'oldcountable' => $oldcountable
 +                              )
 +                      );
  
                        if ( !$changed ) {
                                $status->warning( 'edit-no-change' );
                                '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->getModel(),
 +                              '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 ) {
                        # Update links, etc.
                        $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
  
 -                      wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
 +                      wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $serialized, $summary,
 +                              $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
 +
 +                      wfRunHooks( 'ArticleContentInsertComplete', array( &$this, &$user, $content, $summary,
                                $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
                }
  
                // 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,
 +                      $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
 +
 +              wfRunHooks( 'ArticleContentSaveComplete', array( &$this, &$user, $content, $summary,
                        $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
  
                # Promote user to any groups they meet the criteria for
        /**
         * Prepare text which is about to be saved.
         * Returns a stdclass with source, pst and output members
 -       * @return bool|object
 +       *
 +       * @deprecated in 1.WD: use prepareContentForEdit instead.
         */
        public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
 +              wfDeprecated( __METHOD__, '1.WD' );
 +              $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
 +       *
 +       * @since 1.WD
 +       */
 +      public function prepareContentForEdit( Content $content, $revid = null, User $user = null, $serialization_format = null ) {
                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();
 +
 +              // TODO: is there no better way to obtain a context here?
 +              $context = RequestContext::getMain();
 +              $context->setTitle( $this->mTitle );
 +              $edit->output = $edit->pstContent->getParserOutput( $context, $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;
  
         * Purges pages that include this page if the text was changed here.
         * Every 100th edit, prune the recent changes table.
         *
 -       * @private
         * @param $revision Revision object
         * @param $user User object that did the revision
         * @param $options Array of options, following indexes are used:
                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;
  
                # Update the links tables and other secondary data
                $updates = $editInfo->output->getSecondaryDataUpdates( $this->mTitle );
-               SecondaryDataUpdate::runUpdates( $updates );
+               DataUpdate::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 $user User The relevant user
         * @param $comment String: comment submitted
         * @param $minor Boolean: whereas it's a minor modification
 +       *
 +       * @deprecated since 1.WD, use doEditContent() instead.
         */
        public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
 +              wfDeprecated( __METHOD__, "1.WD" );
 +
 +              $content = ContentHandler::makeContent( $text, $this->getTitle() );
 +              return $this->doQuickEditContent( $content, $user, $comment , $minor );
 +      }
 +
 +      /**
 +       * Edit an article without doing all that other stuff
 +       * The article must already exist; link tables etc
 +       * are not updated, caches are not flushed.
 +       *
 +       * @param $content Content: content submitted
 +       * @param $user User The relevant user
 +       * @param $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,
                ) );
        public function doDeleteArticleReal(
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
 -              global $wgUser;
 +              global $wgUser, $wgContentHandlerUseDB;
  
                wfDebug( __METHOD__ . "\n" );
  
                //
                // In the future, we may keep revisions and mark them with
                // the rev_deleted field, which is reserved for this purpose.
 +
 +              $row = array(
 +                      'ar_namespace'  => 'page_namespace',
 +                      'ar_title'      => 'page_title',
 +                      'ar_comment'    => 'rev_comment',
 +                      'ar_user'       => 'rev_user',
 +                      'ar_user_text'  => 'rev_user_text',
 +                      'ar_timestamp'  => 'rev_timestamp',
 +                      'ar_minor_edit' => 'rev_minor_edit',
 +                      'ar_rev_id'     => 'rev_id',
 +                      'ar_parent_id'  => 'rev_parent_id',
 +                      'ar_text_id'    => 'rev_text_id',
 +                      'ar_text'       => '\'\'', // Be explicit to appease
 +                      'ar_flags'      => '\'\'', // MySQL's "strict mode"...
 +                      'ar_len'        => 'rev_len',
 +                      'ar_page_id'    => 'page_id',
 +                      'ar_deleted'    => $bitfield,
 +                      'ar_sha1'       => 'rev_sha1',
 +              );
 +
 +              if ( $wgContentHandlerUseDB ) {
 +                      $row[ 'ar_content_model' ] = 'rev_content_model';
 +                      $row[ 'ar_content_format' ] = 'rev_content_format';
 +              }
 +
                $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
 +                      $row,
                        array(
 -                              'ar_namespace'  => 'page_namespace',
 -                              'ar_title'      => 'page_title',
 -                              'ar_comment'    => 'rev_comment',
 -                              'ar_user'       => 'rev_user',
 -                              'ar_user_text'  => 'rev_user_text',
 -                              'ar_timestamp'  => 'rev_timestamp',
 -                              'ar_minor_edit' => 'rev_minor_edit',
 -                              'ar_rev_id'     => 'rev_id',
 -                              'ar_parent_id'  => 'rev_parent_id',
 -                              'ar_text_id'    => 'rev_text_id',
 -                              'ar_text'       => '\'\'', // Be explicit to appease
 -                              'ar_flags'      => '\'\'', // MySQL's "strict mode"...
 -                              'ar_len'        => 'rev_len',
 -                              'ar_page_id'    => 'page_id',
 -                              'ar_deleted'    => $bitfield,
 -                              'ar_sha1'       => 'rev_sha1'
 -                      ), array(
                                'page_id' => $id,
                                'page_id = rev_page'
                        ), __METHOD__
                DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
  
                # remove secondary indexes, etc
-               $handler = $this->getContentHandler();
-               $updates = $handler->getDeletionUpdates( $this );
-               SecondaryDataUpdate::runUpdates( $updates );
+               $updates = $this->getDeletionUpdates( );
+               DataUpdate::runUpdates( $updates );
  
                # Clear caches
                WikiPage::onArticleDelete( $this->mTitle );
                }
  
                # 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 {
  
        /**
        * Return an applicable autosummary if one exists for the given edit.
 -      * @param $oldtext String: the previous text of the page.
 -      * @param $newtext String: The submitted text of the page.
 +      * @param $oldtext String|null: the previous text of the page.
 +      * @param $newtext String|null: 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.WD, use ContentHandler::getAutosummary() instead
        */
        public static function getAutosummary( $oldtext, $newtext, $flags ) {
 -              global $wgContLang;
 +              # NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
  
 -              # Decide what kind of autosummary is needed.
 +              wfDeprecated( __METHOD__, '1.WD' );
  
 -              # 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 );
 -              }
 +              $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
 +              $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
 +              $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
  
 -              # New page autosummaries
 -              if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
 -                      # If they're making a new article, give its text, truncated, in the summary.
 -
 -                      $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.WD, use ContentHandler::getAutoDeleteReason() instead
         */
        public function getAutoDeleteReason( &$hasHistory ) {
 +              #NOTE: stub for backwards-compatibility.
 +
 +              wfDeprecated( __METHOD__, '1.WD' );
 +
 +              $handler = ContentHandler::getForTitle( $this->getTitle() );
 +              $handler->getAutoDeleteReason( $this->getTitle(), $hasHistory );
                global $wgContLang;
  
                // Get the last revision
  
                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.
+                       # Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output.
                        $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
                        $u->doUpdate();
                }
                global $wgUser;
                return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
        }
+       public function getDeletionUpdates() {
+               $updates = array(
+                       new LinksDeletionUpdate( $this ),
+               );
+               //@todo: make a hook to add update objects
+               //NOTE: deletion updates will be determined by the ContentHandler in the future
+               return $updates;
+       }
  }
  
  class PoolWorkArticleView extends PoolCounterWork {
         * @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
 +       * @param $context IContextSource context for parsing
         */
 -      function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
 +      function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null, IContextSource $context = null ) {
 +              if ( is_string($content) ) { #BC: old style call
 +                      $modelId = $page->getRevision()->getContentModel();
 +                      $format = $page->getRevision()->getContentFormat();
 +                      $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
 +              }
 +
 +              if ( is_null( $context ) ) {
 +                      $context = RequestContext::getMain();
 +                      #XXX: clone and then set title?
 +              }
 +
                $this->page = $page;
                $this->revid = $revid;
 +              $this->context = $context;
                $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 );
        }
         * @return bool
         */
        function doWork() {
 -              global $wgParser, $wgUseFileCache;
 +              global $wgUseFileCache;
 +
 +              // @todo: several of the methods called on $this->page are not declared in Page, but present in WikiPage and delegated by Article.
  
                $isCurrent = $this->revid === $this->page->getLatest();
  
 -              if ( $this->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 = - microtime( true );
 -              $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
 -                      $this->parserOptions, true, true, $this->revid );
 +              // TODO: page might not have this method? Hard to tell what page is supposed to be here...
 +              $this->parserOutput = $content->getParserOutput( $this->context, $this->revid, $this->parserOptions );
                $time += microtime( true );
  
                # Timing hack
                return false;
        }
  }
 +
@@@ -91,11 -91,11 +91,11 @@@ class ApiPurge extends ApiBase 
                                        $popts = ParserOptions::newFromContext( $this->getContext() );
                                        $popts->setTidy( true );
                                        $p_result = $wgParser->parse( $page->getRawText(), $title, $popts,
 -                                              true, true, $page->getLatest() );
 +                                              true, true, $page->getLatest() ); #FIXME: content!
  
                                        # Update the links tables
-                     $updates = $p_result->getSecondaryDataUpdates( $title );
-                     SecondaryDataUpdate::runUpdates( $updates );
+                                       $updates = $p_result->getSecondaryDataUpdates( $title );
+                                       DataUpdate::runUpdates( $updates );
  
                                        $r['linkupdate'] = '';
  
@@@ -44,7 -44,7 +44,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,
@@@ -155,27 -155,9 +155,15 @@@ 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 $mSecondaryDataUpdates = array(); # List of instances of SecondaryDataObject(), used to cause some information extracted from the page in a custom place.
+               private $mIndexPolicy = '';       # 'index' or 'noindex'?  Any other value will result in no change.
+               private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
-        * 'index' or 'noindex'?  Any other value will result in no change.
-        * 
-        * @var string
-        */
-       protected $mIndexPolicy = '';
-       /**
-        * List of ParserOptions (stored in the keys)
-        *
-        * @var array
-        */
-       protected $mAccessedOptions = array();
-       /**
-        * List of instances of SecondaryDataObject(), used to cause some information extracted from the page in a custom place.
 +
 +      /**
-        * @var array of SecondaryDataObject
++       * List of instances of DataUpdate(), used to cause some information extracted from the page in a custom place.
 +       * @since WD.1
++       * @var array of DataUpdate
 +       */
 +      protected $mSecondaryDataUpdates = array();
  
        const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
  
                 $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.
-      *
-        * @since WD.1
+       /**
+        * 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, includingt a LinksUpdate object for all links stopred in
-      * this ParserOutput object.
-      *
-        * @since WD.1
+        * @param StorageUpdate $update
+        */
+       public function addSecondaryDataUpdate( DataUpdate $update ) {
+               $this->mSecondaryDataUpdates[] = $update;
+       }
+       /**
+        * Returns any DataUpdate jobs to be executed in order to store secondary information
+        * extracted from the page's content, including a LinksUpdate object for all links stored 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 getSecondaryDataUpdates( Title $title = null, $recursive = true ) {
-         if ( is_null( $title ) ) {
-             $title = Title::newFromText( $this->getTitleText() );
-         }
-         return array_merge(
-                       $this->mSecondaryDataUpdates,
-                       array( new LinksUpdate( $title, $this, $recursive ) )
-               );
-     }
+        * @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 DataUpdate
+        */
+       public function getSecondaryDataUpdates( Title $title = null, $recursive = true ) {
+               if ( !$title ) {
+                       $title = Title::newFromText( $this->getTitleText() );
+               }
+               $linksUpdate = new LinksUpdate( $title, $this, $recursive );
+               if ( !$this->mSecondaryDataUpdates ) {
+                       return array( $linksUpdate );
+               } else {
+                       $updates = array_merge( $this->mSecondaryDataUpdates, array( $linksUpdate ) );
+               }
+               return $updates;
+        }
  }
@@@ -97,8 -97,8 +97,8 @@@ class PageArchive 
                                $condition,
                                __METHOD__,
                                array(
-                                       'GROUP BY' => 'ar_namespace,ar_title',
-                                       'ORDER BY' => 'ar_namespace,ar_title',
+                                       'GROUP BY' => array( 'ar_namespace', 'ar_title' ),
+                                       'ORDER BY' => array( 'ar_namespace', 'ar_title' ),
                                        'LIMIT' => 100,
                                )
                        )
         * @return ResultWrapper
         */
        function listRevisions() {
 +              global $wgContentHandlerNoDB;
 +
                $dbr = wfGetDB( DB_SLAVE );
 +
 +              $fields = array(
 +                      'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
 +                      'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
 +              );
 +
 +              if ( !$wgContentHandlerNoDB ) {
 +                      $fields[] = 'ar_content_format';
 +                      $fields[] = 'ar_content_model';
 +              }
 +
                $res = $dbr->select( 'archive',
 -                      array(
 -                              'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
 -                              'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
 -                      ),
 +                      $fields,
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                   'ar_title' => $this->title->getDBkey() ),
                        'PageArchive::listRevisions',
         * @return Revision
         */
        function getRevision( $timestamp ) {
 +              global $wgContentHandlerNoDB;
 +
                $dbr = wfGetDB( DB_SLAVE );
 +
 +              $fields = array(
 +                      'ar_rev_id',
 +                      'ar_text',
 +                      'ar_comment',
 +                      'ar_user',
 +                      'ar_user_text',
 +                      'ar_timestamp',
 +                      'ar_minor_edit',
 +                      'ar_flags',
 +                      'ar_text_id',
 +                      'ar_deleted',
 +                      'ar_len',
 +                      'ar_sha1',
 +              );
 +
 +              if ( !$wgContentHandlerNoDB ) {
 +                      $fields[] = 'ar_content_format';
 +                      $fields[] = 'ar_content_model';
 +              }
 +
                $row = $dbr->selectRow( 'archive',
 -                      array(
 -                              'ar_rev_id',
 -                              'ar_text',
 -                              'ar_comment',
 -                              'ar_user',
 -                              'ar_user_text',
 -                              'ar_timestamp',
 -                              'ar_minor_edit',
 -                              'ar_flags',
 -                              'ar_text_id',
 -                              'ar_deleted',
 -                              'ar_len',
 -                              'ar_sha1',
 -                      ),
 +                      $fields,
                        array( 'ar_namespace' => $this->title->getNamespace(),
                                        'ar_title' => $this->title->getDBkey(),
                                        'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
         * @return Mixed: number of revisions restored or false on failure
         */
        private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
 +              global $wgContentHandlerNoDB;
 +
                if ( wfReadOnly() ) {
                        return false;
                }
                        $oldones = "ar_timestamp IN ( {$oldts} )";
                }
  
 +              $fields = array(
 +                      'ar_rev_id',
 +                      'ar_text',
 +                      'ar_comment',
 +                      'ar_user',
 +                      'ar_user_text',
 +                      'ar_timestamp',
 +                      'ar_minor_edit',
 +                      'ar_flags',
 +                      'ar_text_id',
 +                      'ar_deleted',
 +                      'ar_page_id',
 +                      'ar_len',
 +                      'ar_sha1');
 +
 +              if ( !$wgContentHandlerNoDB ) {
 +                      $fields[] = 'ar_content_format';
 +                      $fields[] = 'ar_content_model';
 +              }
 +
                /**
                 * Select each archived revision...
                 */
                $result = $dbw->select( 'archive',
 -                      /* fields */ array(
 -                              'ar_rev_id',
 -                              'ar_text',
 -                              'ar_comment',
 -                              'ar_user',
 -                              'ar_user_text',
 -                              'ar_timestamp',
 -                              'ar_minor_edit',
 -                              'ar_flags',
 -                              'ar_text_id',
 -                              'ar_deleted',
 -                              'ar_page_id',
 -                              'ar_len',
 -                              'ar_sha1' ),
 +                      $fields,
                        /* WHERE */ array(
                                'ar_namespace' => $this->title->getNamespace(),
                                'ar_title'     => $this->title->getDBkey(),
@@@ -921,8 -892,7 +921,8 @@@ class SpecialUndelete extends SpecialPa
         * @return String: HTML
         */
        function showDiff( $previousRev, $currentRev ) {
 -              $diffEngine = new DifferenceEngine( $this->getContext() );
 +              $contentHandler = ContentHandler::getForTitle( $this->getTitle() );
 +              $diffEngine = $contentHandler->createDifferenceEngine( $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"
                );