messing with replaceSection()
authorDaniel Kinzler <daniel.kinzler@wikimedia.de>
Mon, 19 Mar 2012 17:10:15 +0000 (17:10 +0000)
committerDaniel Kinzler <daniel.kinzler@wikimedia.de>
Wed, 4 Apr 2012 17:55:39 +0000 (19:55 +0200)
includes/Content.php
includes/ContentHandler.php
includes/EditPage.php
includes/WikiPage.php

index 9872868..859bb0c 100644 (file)
@@ -2,46 +2,32 @@
 
 /**
  * A content object represents page content, e.g. the text to show on a page.
+ * Content objects have no knowledge about how they relate to Wiki pages.
+ * Content objects are imutable.
  *
  */
 abstract class Content {
     
-    public function __construct( Title $title = null, $revId = null, $modelName = null ) { #FIXME: really need revId? annoying! #FIXME: really $title? or just when parsing, every time?
+    public function __construct( $modelName = null ) { #FIXME: really need revId? annoying! #FIXME: really $title? or just when parsing, every time?
         $this->mModelName = $modelName;
-        $this->mTitle = $title;
-        $this->mRevId = $revId;
     }
 
     public function getModelName() {
         return $this->mModelName;
     }
 
-    public function getTitle() {
-        return $this->mTitle;
-    }
-
-    public function getRevId() {
-        return $this->mRevId;
-    }
+    public abstract function getSearchText( );
 
-    public abstract function getSearchText( $obj );
-
-    public abstract function getWikitextForTransclusion( $obj );
-
-    public abstract function getParserOutput( ParserOptions $options = NULL );
+    public abstract function getWikitextForTransclusion( );
 
+    /**
+     * Returns native represenation of the data. Interpretation depends on the data model used,
+     * as given by getDataModel().
+     *
+     */
     public abstract function getRawData( );
 
-    public function getHtml( ParserOptions $options ) {
-        $po = $this->getParserOutput( $options );
-        return $po->getText();
-    }
-
-    public function getIndexUpdateJobs( ParserOptions $options , $recursive = true ) {
-        $po = $this->getParserOutput( $options );
-        $update = new LinksUpdate( $this->mTitle, $po, $recursive );
-        return $update;
-    }
+    public abstract function getParserOutput( Title $title = null, $revId = null, ParserOptions $options = NULL );
 
     public function getRedirectChain() {
         return null;
@@ -60,19 +46,19 @@ abstract class Content {
     }
 
     /**
-     * Replaces the section with the given id.
+     * Replaces a section of the content.
      *
-     * The default implementation returns $this.
-     *
-     * @param String $sectionId the section's id
-     * @param Content $with the section's new content
-     * @return Content a new content object with the section replaced, or this content object if the section couldn't be replaced.
+     * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...), or "new"
+     * @param $with Content: new content of the section
+     * @param $sectionTitle String: new section's subject, only if $section is 'new'
+     * @return string Complete article text, or null if error
      */
-    public function replaceSection( $sectionId ) {
+    public function replaceSection( $section, Content $with, $sectionTitle = ''  ) {
+        return $this;
     }
 
-    #XXX: is the native model for wikitext a string or the parser output? parse early or parse late?
-
+    #TODO: implement specialized ParserOutput for Wikidata model
+    #TODO: provide addToParserOutput fule Multipart... somehow.
 
     # TODO: EditPage::mergeChanges( Content $a, Content $b )
     # TODO: Wikipage::isCountable(Content $a)
@@ -82,7 +68,6 @@ abstract class Content {
     # TODO: getSize( )
 
     # TODO: WikiPage::getUndoText( Revision $undo, Revision $undoafter = null )
-    # TODO: WikiPage::replaceSection( $section, $text, $sectionTitle = '', $edittime = null )
     # TODO: WikiPage::getAutosummary( $oldtext, $text, $flags )
 
     # TODO: EditPage::getPreloadedText( $preload ) // $wgParser->getPreloadText
@@ -94,23 +79,50 @@ abstract class Content {
 
 }
 
-class TextContent extends Content {
-    public function __construct( $text, Title $title = null, $revId = null, $modelName = null ) {
-        parent::__construct($title, $revId, $modelName);
+/**
+ * Content object implementation for representing flat text. The
+ */
+abstract class TextContent extends Content {
+    public function __construct( $text, $modelName = null ) {
+        parent::__construct($modelName);
 
         $this->mText = $text;
     }
 
-    public function getSearchText( $obj ) {
-        return $this->getRawData();
+    /**
+     * Returns the text represented by this Content object, as a string.
+     *
+     * @return String the raw text
+     */
+    public function getRawData( ) {
+        $text = $this->mText;
+        return $text;
     }
 
-    public function getWikitextForTransclusion( $obj ) {
+    /**
+     * Returns the text represented by this Content object, as a string.
+     *
+     * @return String the raw text
+     */
+    public function getSearchText( ) { #FIXME: use!
         return $this->getRawData();
     }
 
+    /**
+     * Returns the text represented by this Content object, as a string.
+     *
+     * @return String the raw text
+     */
+    public function getWikitextForTransclusion( ) { #FIXME: use!
+        return $this->getRawData();
+    }
 
-    public function getParserOutput( ParserOptions $options = null ) {
+    /**
+     * Returns a generic ParserOutput object, wrapping the HTML returned by getHtml().
+     *
+     * @return ParserOutput representing the HTML form of the text
+     */
+    public function getParserOutput( Title $title = null, $revId = null, ParserOptions $options = null ) {
         # generic implementation, relying on $this->getHtml()
 
         $html = $this->getHtml( $options );
@@ -123,53 +135,39 @@ class TextContent extends Content {
         return $po;
     }
 
-    public function getHtml( ParserOptions $options ) {
-        $html = "";
-        $html .= "<pre class=\"mw-code\" dir=\"ltr\">\n";
-        $html .= htmlspecialchars( $this->getRawData() );
-        $html .= "\n</pre>\n";
-
-        return $html;
-    }
-
+    protected abstract function getHtml( );
 
-    public function getRawData( ) {
-        $text = $this->mText;
-        return $text;
-    }
-
-    public function getRedirectChain() {
-        #XXX: really do this for all text, or just in WikitextContent?
-        $text = $this->getRawData();
-        return Title::newFromRedirectArray( $text );
-    }
 }
 
 class WikitextContent extends TextContent {
-    public function __construct( $text, Title $title, $revId = null) {
-        parent::__construct($text, $title, $revId, CONTENT_MODEL_WIKITEXT);
+    public function __construct( $text ) {
+        parent::__construct($text, CONTENT_MODEL_WIKITEXT);
+
+        $this->mDefaultParserOptions = null; #TODO: use per-class static member?!
+    }
 
-        $this->mDefaultParserOptions = null;
+    protected function getHtml( ) {
+        throw new MWException( "getHtml() not implemented for wikitext. Use getParserOutput()->getText()." );
     }
 
     public function getDefaultParserOptions() {
         global $wgUser, $wgContLang;
 
-        if ( !$this->mDefaultParserOptions ) {
-            #TODO: use static member?!
+        if ( !$this->mDefaultParserOptions ) { #TODO: use per-class static member?!
             $this->mDefaultParserOptions = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
         }
 
         return $this->mDefaultParserOptions;
     }
 
-    public function getParserOutput( ParserOptions $options = null ) {
+    /**
+     * Returns a ParserOutput object reesulting from parsing the content's text using $wgParser
+     *
+     * @return ParserOutput representing the HTML form of the text
+     */
+    public function getParserOutput( Title $title = null, $revId = null, ParserOptions $options = null ) {
         global $wgParser;
 
-        #TODO: quick local cache: if $options is NULL, use ->mParserOutput!
-        #FIXME: need setParserOutput, so we can use stuff from the parser cache??
-        #FIXME: ...or we somehow need to know the parser cache key??
-
         if ( !$options ) {
             $options = $this->getDefaultParserOptions();
         }
@@ -190,80 +188,64 @@ class WikitextContent extends TextContent {
 
         $text = $this->getRawData();
         $sect = $wgParser->getSection( $text, $section, false );
-        $title = Title::newFromDBkey( $this->mTitle->getText() . '#' . $section, $this->mTitle->getNamespace() ); #FIXME: get rid of titles here
 
-        return  new WikitextContent( $sect, $title );
+        return  new WikitextContent( $sect );
     }
 
     /**
-     * Replaces the section with the given id.
+     * Replaces a section in the wikitext
      *
-     * @param String $sectionId the section's id
-     * @param Content $with the section's new content
-     * @return Boolean true if te section was replaced sucessfully, false otherwise
-     */
-    #FIXME: implement replaceSection(), use in WikiPage
-
-    /**
-     * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...)
-     * @param $text String: new text of the section
+     * @param $section empty/null/false or a section number (0, 1, 2, T1, T2...), or "new"
+     * @param $with 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 string Complete article text, or null if error
      */
-    /*public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) { #FIXME: adopt this!
+    public function replaceSection( $section, Content $with, $sectionTitle = '' ) {
+        global $wgParser;
+
         wfProfileIn( __METHOD__ );
 
-        if ( strval( $section ) == '' ) {
-            // Whole-page edit; let the whole text through
-        } else {
-            // Bug 30711: always use current version when adding a new section
-            if ( is_null( $edittime ) || $section == 'new' ) {
-                $oldtext = $this->getRawText();
-                if ( $oldtext === false ) {
-                    wfDebug( __METHOD__ . ": no page text\n" );
-                    wfProfileOut( __METHOD__ );
-                    return null;
-                }
-            } else {
-                $dbw = wfGetDB( DB_MASTER );
-                $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
-
-                if ( !$rev ) {
-                    wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
-                        $this->getId() . "; section: $section; edittime: $edittime)\n" );
-                    wfProfileOut( __METHOD__ );
-                    return null;
-                }
-
-                $oldtext = $rev->getText();
-            }
+        $myModelName = $this->getModelName();
+        $sectionModelName = $with->getModelName();
+
+        if ( $sectionModelName != $myModelName  ) {
+            throw new MWException( "Incompatible content model for section: document uses $myModelName, section uses $sectionModelName." );
+        }
 
-            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 );
+        $oldtext = $this->getRawData();
+        $text = $with->getRawData();
+
+        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 = new WikitextContent( $text );
+
         wfProfileOut( __METHOD__ );
-        return $text;
-    } */
+        return $newContent;
+    }
+
+    public function getRedirectChain() {
+        $text = $this->getRawData();
+        return Title::newFromRedirectArray( $text );
+    }
 
 }
 
 class MessageContent extends TextContent {
     public function __construct( $msg_key, $params = null, $options = null ) {
-        parent::__construct(null, null, null, CONTENT_MODEL_WIKITEXT);
+        parent::__construct(null, CONTENT_MODEL_WIKITEXT);
 
         $this->mMessageKey = $msg_key;
 
@@ -275,13 +257,19 @@ class MessageContent extends TextContent {
         $this->mHtmlOptions = null;
     }
 
-
-    public function getHtml( ParserOptions $options ) {
+    /**
+     * Returns the message as rendered HTML, using the options supplied to the constructor plus "parse".
+     */
+    protected function getHtml(  ) {
         $opt = array_merge( $this->mOptions, array('parse') );
+
         return wfMsgExt( $this->mMessageKey, $this->mParameters, $opt );
     }
 
 
+    /**
+     * Returns the message as raw text, using the options supplied to the constructor minus "parse" and "parseinline".
+     */
     public function getRawData( ) {
         $opt = array_diff( $this->mOptions, array('parse', 'parseinline') );
 
@@ -292,11 +280,11 @@ class MessageContent extends TextContent {
 
 
 class JavaScriptContent extends TextContent {
-    public function __construct( $text, Title $title, $revId = null ) {
-        parent::__construct($text, $title, $revId, CONTENT_MODEL_JAVASCRIPT);
+    public function __construct( $text ) {
+        parent::__construct($text, CONTENT_MODEL_JAVASCRIPT);
     }
 
-    public function getHtml( ParserOptions $options ) {
+    protected function getHtml( ) {
         $html = "";
         $html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
         $html .= htmlspecialchars( $this->getRawData() );
@@ -308,11 +296,11 @@ class JavaScriptContent extends TextContent {
 }
 
 class CssContent extends TextContent {
-    public function __construct( $text, Title $title, $revId = null ) {
-        parent::__construct($text, $title, $revId, CONTENT_MODEL_CSS);
+    public function __construct( $text ) {
+        parent::__construct($text, CONTENT_MODEL_CSS);
     }
 
-    public function getHtml( ParserOptions $options ) {
+    protected function getHtml( ) {
         $html = "";
         $html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
         $html .= htmlspecialchars( $this->getRawData() );
@@ -322,10 +310,8 @@ class CssContent extends TextContent {
     }
 }
 
-#FIXME: special type for redirects?!
-#FIXME: special type for message-based pseudo-content? with raw html?
-
-#TODO: MultipartMultipart < WikipageContent (Main + Links + X)
-#TODO: LinksContent < LanguageLinksContent, CategoriesContent
+#FUTURE: special type for redirects?!
+#FUTURE: MultipartMultipart < WikipageContent (Main + Links + X)
+#FUTURE: LinksContent < LanguageLinksContent, CategoriesContent
 #EXAMPLE: CoordinatesContent
 #EXAMPLE: WikidataContent
index a80eaeb..07df8a5 100644 (file)
@@ -29,11 +29,13 @@ abstract class ContentHandler {
         return null;
     }
 
-    public static function makeContent( $text, Title $title, $format = null, $revId = null ) {
-        $handler = ContentHandler::getForTitle( $title );
+    public static function makeContent( $text, Title $title, $modelName = null, $format = null ) {
+        if ( !$modelName ) {
+            $modelName = $title->getContentModelName();
+        }
 
-        #FIXME: pass revid?
-        return $handler->unserialize( $text, $title, $format );
+        $handler = ContentHandler::getForModelName( $modelName );
+        return $handler->unserialize( $text, $format );
     }
 
     public static function getDefaultModelFor( Title $title ) {
@@ -141,11 +143,11 @@ abstract class ContentHandler {
         return $this->mSupportedFormats[0];
     }
 
-    public abstract function serialize( Content $content, Title $title, $format = null );
+    public abstract function serialize( Content $content, $format = null );
 
-    public abstract function unserialize( $blob, Title $title, $format = null ); #FIXME: ...and revId?
+    public abstract function unserialize( $blob, $format = null );
 
-    public abstract function newContent( Title $title );
+    public abstract function emptyContent();
 
     # public abstract function doPreSaveTransform( $title, $obj ); #TODO...
 
@@ -199,13 +201,6 @@ abstract class ContentHandler {
         return $de;
     }
 
-    public function getIndexUpdateJobs( Title $title, ParserOutput $parserOutput, $recursive = true ) {
-            # for wikitext, create a LinksUpdate object
-            # for wikidata: serialize arrays to json
-        $update = new LinksUpdate( $title, $parserOutput, $recursive );
-        return $update;
-    }
-
     #XXX: is the native model for wikitext a string or the parser output? parse early or parse late?
 
     #TODO: how to handle extra message for JS/CSS previews??
@@ -221,7 +216,8 @@ abstract class TextContentHandler extends ContentHandler {
         parent::__construct( $modelName, $formats );
     }
 
-    public function serialize( Content $content, Title $title, $format = null ) {
+    public function serialize( Content $content, $format = null ) {
+        #FIXME: assert format
         return $content->getRawData();
     }
 
@@ -232,12 +228,13 @@ class WikitextContentHandler extends TextContentHandler {
         parent::__construct( $modelName, array( 'application/x-wikitext' ) ); #FIXME: mime
     }
 
-    public function unserialize( $text, Title $title, $format = null ) {
-        return new WikitextContent($text, $title);
+    public function unserialize( $text, $format = null ) {
+        #FIXME: assert format
+        return new WikitextContent($text);
     }
 
-    public function newContent( Title $title) {
-        return new WikitextContent("", $title);
+    public function emptyContent() {
+        return new WikitextContent("");
     }
 
 }
@@ -248,12 +245,12 @@ class JavaScriptContentHandler extends TextContentHandler {
         parent::__construct( $modelName, array( 'text/javascript' ) );
     }
 
-    public function unserialize( $text, Title $title, $format = null ) {
-        return new JavaScriptContent($text, $title);
+    public function unserialize( $text, $format = null ) {
+        return new JavaScriptContent($text);
     }
 
-    public function newContent( Title $title) {
-        return new JavaScriptContent("", $title);
+    public function emptyContent() {
+        return new JavaScriptContent("");
     }
 }
 
@@ -263,12 +260,12 @@ class CssContentHandler extends TextContentHandler {
         parent::__construct( $modelName, array( 'text/css' ) );
     }
 
-    public function unserialize( $text, Title $title, $format = null ) {
-        return new CssContent($text, $title);
+    public function unserialize( $text, $format = null ) {
+        return new CssContent($text);
     }
 
-    public function newContent( Title $title) {
-        return new CssContent("", $title);
+    public function emptyContent() {
+        return new CssContent("");
     }
 
 }
index e09b595..bf166be 100644 (file)
@@ -1299,10 +1299,12 @@ class EditPage {
                        
                        if ( $this->isConflict ) {
                                wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
-                               $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
+                               $cnt = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
+                $text = ContentHandler::getContentText($cnt); #FIXME: use Content object throughout, make edit form aware of content model and serialization format
                        } else {
                                wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
-                               $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+                $cnt = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+                $text = ContentHandler::getContentText($cnt); #FIXME: use Content object throughout, make edit form aware of content model and serialization format
                        }
                        if ( is_null( $text ) ) {
                                wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
index d6b20e5..749f19b 100644 (file)
@@ -1127,18 +1127,21 @@ 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 Content new complete article content, or null if error
         */
-       public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) { #FIXME: move to Content object!
+       public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
                wfProfileIn( __METHOD__ );
 
+        $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() ); #XXX: could make section title, but that's not required.
+
                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;
@@ -1154,27 +1157,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;
        }
 
        /**
@@ -2831,7 +2821,9 @@ class PoolWorkArticleView extends PoolCounterWork {
         */
        function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
         if ( is_string($content) ) { #BC: old style call
-            $content = ContentHandler::makeContent( $content, $page->getTitle(), null, $this->revid ); #FIXME: format? from revision?
+            $modelName = $page->getRevision()->getContentModelName();
+            $format = $page->getRevision()->getContentFormat();
+            $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelName, $format );
         }
 
                $this->page = $page;