merged master
[lhc/web/wiklou.git] / includes / WikiPage.php
index b5f4c1d..c2278df 100644 (file)
@@ -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() );
        }
 
        /**
@@ -265,7 +279,9 @@ class WikiPage extends Page {
         * @return array
         */
        public static function selectFields() {
-               return array(
+               global $wgContentHandlerUseDB;
+
+               $fields = array(
                        'page_id',
                        'page_namespace',
                        'page_title',
@@ -278,6 +294,12 @@ class WikiPage extends Page {
                        'page_latest',
                        'page_len',
                );
+
+               if ( $wgContentHandlerUseDB ) {
+                       $fields[] = 'page_content_model';
+               }
+
+               return $fields;
        }
 
        /**
@@ -450,15 +472,38 @@ class WikiPage extends Page {
         * @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();
        }
 
        /**
@@ -574,35 +619,56 @@ class WikiPage extends Page {
        }
 
        /**
-        * 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
         */
@@ -611,7 +677,7 @@ class WikiPage extends Page {
                if ( !$this->mTimestamp ) {
                        $this->loadLastEdit();
                }
-               
+
                return wfTimestamp( TS_MW, $this->mTimestamp );
        }
 
@@ -742,32 +808,34 @@ class WikiPage extends Page {
                        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 );
        }
 
        /**
@@ -813,7 +881,8 @@ class WikiPage extends Page {
         */
        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;
                }
@@ -1009,7 +1078,7 @@ class WikiPage extends Page {
                        && $parserOptions->getStubThreshold() == 0
                        && $this->mTitle->exists()
                        && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
-                       && $this->mTitle->isWikitextPage();
+                       && $this->getContentHandler()->isParserCacheSupported();
        }
 
        /**
@@ -1020,6 +1089,7 @@ class WikiPage extends Page {
         * @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)
+        *
         * @return ParserOutput or false if the revision was not found
         */
        public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
@@ -1043,7 +1113,7 @@ class WikiPage extends Page {
                        $oldid = $this->getLatest();
                }
 
-               $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
+               $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache, null );
                $pool->execute();
 
                wfProfileOut( __METHOD__ );
@@ -1098,7 +1168,7 @@ class WikiPage extends Page {
 
                if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
                        if ( $this->mTitle->exists() ) {
-                               $text = $this->getRawText();
+                               $text = ContentHandler::getContentText( $this->getContent() );
                        } else {
                                $text = false;
                        }
@@ -1163,11 +1233,13 @@ class WikiPage extends Page {
         * @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() );
 
@@ -1177,14 +1249,20 @@ class WikiPage extends Page {
                }
 
                $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__ );
 
@@ -1286,27 +1364,29 @@ class WikiPage extends Page {
         * @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;
        }
 
        /**
@@ -1314,18 +1394,41 @@ class WikiPage extends Page {
         * @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;
@@ -1341,27 +1444,14 @@ class WikiPage extends Page {
                                        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;
        }
 
        /**
@@ -1426,8 +1516,66 @@ class WikiPage extends Page {
         *     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
@@ -1447,10 +1595,25 @@ class WikiPage extends Page {
 
                $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' );
@@ -1464,20 +1627,25 @@ class WikiPage extends Page {
                $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();
@@ -1505,16 +1673,24 @@ class WikiPage extends Page {
                                '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 );
 
@@ -1576,9 +1752,17 @@ class WikiPage extends Page {
                                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' );
@@ -1610,10 +1794,13 @@ class WikiPage extends Page {
                                '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 );
 
@@ -1631,7 +1818,7 @@ class WikiPage extends Page {
                                        $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 ) {
@@ -1644,7 +1831,10 @@ class WikiPage extends Page {
                        # 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 ) );
                }
 
@@ -1656,7 +1846,10 @@ class WikiPage extends Page {
                // 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
@@ -1686,15 +1879,39 @@ class WikiPage extends Page {
        /**
         * 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;
@@ -1705,11 +1922,20 @@ class WikiPage extends Page {
 
                $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;
 
@@ -1722,7 +1948,6 @@ class WikiPage extends Page {
         * 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:
@@ -1739,13 +1964,13 @@ class WikiPage extends Page {
                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;
@@ -1758,7 +1983,7 @@ class WikiPage extends Page {
                }
 
                # Update the links tables and other secondary data
-               $updates = $editInfo->output->getSecondaryDataUpdates( $this->mTitle );
+               $updates = $editInfo->output->getSecondaryDataUpdates( $this->getTitle() );
                DataUpdate::runUpdates( $updates );
 
                wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
@@ -1803,7 +2028,7 @@ class WikiPage extends Page {
                }
 
                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
@@ -1829,7 +2054,10 @@ class WikiPage extends Page {
                }
 
                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'] ) {
@@ -1850,14 +2078,37 @@ class WikiPage extends Page {
         * @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,
                ) );
@@ -2143,7 +2394,7 @@ class WikiPage extends Page {
        public function doDeleteArticleReal(
                $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
        ) {
-               global $wgUser;
+               global $wgUser, $wgContentHandlerUseDB;
 
                wfDebug( __METHOD__ . "\n" );
 
@@ -2188,25 +2439,34 @@ class WikiPage extends Page {
                //
                // 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__
@@ -2264,16 +2524,6 @@ class WikiPage extends Page {
                $this->mTitle->resetArticleID( 0 );
        }
 
-       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;
-       }
-
        /**
         * Roll back the most recent consecutive set of edits to a page
         * from the same user; fails if there are no eligible edits to
@@ -2439,7 +2689,7 @@ class WikiPage extends Page {
                }
 
                # 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 {
@@ -2585,57 +2835,22 @@ class WikiPage extends Page {
 
        /**
        * 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;
-
-               # 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 );
-               }
-
-               # 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 );
-               }
+               # NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
 
-               # 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
+               wfDeprecated( __METHOD__, '1.WD' );
 
-                       $truncatedtext = $wgContLang->truncate(
-                               $newtext,
-                               max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
+               $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
+               $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
+               $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
 
-                       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 );
        }
 
        /**
@@ -2644,8 +2859,15 @@ class WikiPage extends Page {
         * @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
@@ -2971,6 +3193,14 @@ class WikiPage extends Page {
                global $wgUser;
                return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
        }
+
+       public function getDeletionUpdates() {
+               $updates = $this->getContentHandler()->getDeletionUpdates( $this );
+
+               wfRunHooks( 'WikiPageDeletionUpdates', array( $this, &$updates ) );
+               return $updates;
+       }
+
 }
 
 class PoolWorkArticleView extends PoolCounterWork {
@@ -2996,9 +3226,9 @@ class PoolWorkArticleView extends PoolCounterWork {
        private $parserOptions;
 
        /**
-        * @var string|null
+        * @var Content|null
         */
-       private $text;
+       private $content = null;
 
        /**
         * @var ParserOutput|bool
@@ -3022,14 +3252,20 @@ 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
         */
-       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
+                       $modelId = $page->getRevision()->getContentModel();
+                       $format = $page->getRevision()->getContentFormat();
+                       $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $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 );
        }
@@ -3065,25 +3301,27 @@ class PoolWorkArticleView extends PoolCounterWork {
         * @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->page->getTitle(), $this->revid, $this->parserOptions );
                $time += microtime( true );
 
                # Timing hack
@@ -3152,3 +3390,4 @@ class PoolWorkArticleView extends PoolCounterWork {
                return false;
        }
 }
+