From 989f0685ade5c4cd9c895b38d8824eec861c2a26 Mon Sep 17 00:00:00 2001 From: Daniel Kinzler Date: Mon, 19 Mar 2012 17:10:15 +0000 Subject: [PATCH] messing with replaceSection() --- includes/Content.php | 262 +++++++++++++++++------------------- includes/ContentHandler.php | 51 ++++--- includes/EditPage.php | 6 +- includes/WikiPage.php | 34 ++--- 4 files changed, 165 insertions(+), 188 deletions(-) diff --git a/includes/Content.php b/includes/Content.php index 9872868214..859bb0c7c8 100644 --- a/includes/Content.php +++ b/includes/Content.php @@ -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 .= "
\n";
-        $html .= htmlspecialchars( $this->getRawData() );
-        $html .= "\n
\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 .= "
\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 .= "
\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
diff --git a/includes/ContentHandler.php b/includes/ContentHandler.php
index a80eaeb06a..07df8a5e21 100644
--- a/includes/ContentHandler.php
+++ b/includes/ContentHandler.php
@@ -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("");
     }
 
 }
diff --git a/includes/EditPage.php b/includes/EditPage.php
index e09b595571..bf166be9a2 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -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" );
diff --git a/includes/WikiPage.php b/includes/WikiPage.php
index d6b20e5063..749f19b62d 100644
--- a/includes/WikiPage.php
+++ b/includes/WikiPage.php
@@ -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;
-- 
2.20.1