X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22sites_tous%22%29%20.%20%22?a=blobdiff_plain;f=includes%2FContent.php;h=3bce48e710b7be829617b95c2bcc10082315c13f;hb=12166f46b4d63d3fd3cab68cf4c7090a1646dd09;hp=30e84006061d64a08209c4a078a650dd5fb58cd5;hpb=52abc24967c01d9f037ae108841d063ca722bf94;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Content.php b/includes/Content.php index 30e8400606..3bce48e710 100644 --- a/includes/Content.php +++ b/includes/Content.php @@ -1,618 +1,1482 @@ getContentHandler()->getDefaultFormat() + * + * @since WD.1 + * + * @return String + */ + public function getDefaultFormat(); + + /** + * Convenience method that returns the list of serialization formats + * supported for the content model that this Content object uses. + * + * Shorthand for $this->getContentHandler()->getSupportedFormats() + * + * @since WD.1 + * + * @return Array of supported serialization formats + */ + public function getSupportedFormats(); + + /** + * Returns true if $format is a supported serialization format for this + * Content object, false if it isn't. + * + * Note that this should always return true if $format is null, because null + * stands for the default serialization. + * + * Shorthand for $this->getContentHandler()->isSupportedFormat( $format ) + * + * @since WD.1 + * + * @param $format string The format to check + * @return bool Whether the format is supported + */ + public function isSupportedFormat( $format ); + + /** + * Convenience method for serializing this Content object. + * + * Shorthand for $this->getContentHandler()->serializeContent( $this, $format ) + * + * @since WD.1 + * + * @param $format null|string The desired serialization format (or null for + * the default format). + * @return string Serialized form of this Content object + */ + public function serialize( $format = null ); + + /** + * Returns true if this Content object represents empty content. + * + * @since WD.1 + * + * @return bool Whether this Content object is empty + */ + public function isEmpty(); + + /** + * Returns whether the content is valid. This is intended for local validity + * checks, not considering global consistency. + * + * Content needs to be valid before it can be saved. + * + * This default implementation always returns true. + * + * @since WD.1 + * + * @return boolean + */ + public function isValid(); + + /** + * Returns true if this Content objects is conceptually equivalent to the + * given Content object. + * + * Contract: + * + * - Will return false if $that is null. + * - Will return true if $that === $this. + * - Will return false if $that->getModelName() != $this->getModel(). + * - Will return false if $that->getNativeData() is not equal to $this->getNativeData(), + * where the meaning of "equal" depends on the actual data model. + * + * Implementations should be careful to make equals() transitive and reflexive: + * + * - $a->equals( $b ) <=> $b->equals( $a ) + * - $a->equals( $b ) && $b->equals( $c ) ==> $a->equals( $c ) + * + * @since WD.1 + * + * @param $that Content The Content object to compare to + * @return bool True if this Content object is equal to $that, false otherwise. + */ + public function equals( Content $that = null ); + + /** + * Return a copy of this Content object. The following must be true for the + * object returned: + * + * if $copy = $original->copy() + * + * - get_class($original) === get_class($copy) + * - $original->getModel() === $copy->getModel() + * - $original->equals( $copy ) + * + * If and only if the Content object is immutable, the copy() method can and + * should return $this. That is, $copy === $original may be true, but only + * for immutable content objects. + * + * @since WD.1 + * + * @return Content. A copy of this object + */ + public function copy( ); + + /** + * Returns true if this content is countable as a "real" wiki page, provided + * that it's also in a countable location (e.g. a current revision in the + * main namespace). + * + * @since WD.1 + * + * @param $hasLinks Bool: If it is known whether this content contains + * links, provide this information here, to avoid redundant parsing to + * find out. + * @return boolean + */ + public function isCountable( $hasLinks = null ) ; + + + /** + * Parse the Content object and generate a ParserOutput from the result. + * $result->getText() can be used to obtain the generated HTML. If no HTML + * is needed, $generateHtml can be set to false; in that case, + * $result->getText() may return null. + * + * @param $title Title The page title to use as a context for rendering + * @param $revId null|int The revision being rendered (optional) + * @param $options null|ParserOptions Any parser options + * @param $generateHtml Boolean Whether to generate HTML (default: true). If false, + * the result of calling getText() on the ParserOutput object returned by + * this method is undefined. + * + * @since WD.1 + * + * @return ParserOutput + */ + public function getParserOutput( Title $title, + $revId = null, + ParserOptions $options = null, $generateHtml = true ); + # TODO: make RenderOutput and RenderOptions base classes + + /** + * Returns a list of DataUpdate objects for recording information about this + * Content in some secondary data store. If the optional second argument, + * $old, is given, the updates may model only the changes that need to be + * made to replace information about the old content with information about + * the new content. + * + * This default implementation calls + * $this->getParserOutput( $content, $title, null, null, false ), + * and then calls getSecondaryDataUpdates( $title, $recursive ) on the + * resulting ParserOutput object. + * + * Subclasses may implement this to determine the necessary updates more + * efficiently, or make use of information about the old content. + * + * @param $title Title The context for determining the necessary updates + * @param $old Content|null An optional Content object representing the + * previous content, i.e. the content being replaced by this Content + * object. + * @param $recursive boolean Whether to include recursive updates (default: + * false). + * @param $parserOutput ParserOutput|null Optional ParserOutput object. + * Provide if you have one handy, to avoid re-parsing of the content. + * + * @return Array. A list of DataUpdate objects for putting information + * about this content object somewhere. + * + * @since WD.1 + */ + public function getSecondaryDataUpdates( Title $title, + Content $old = null, + $recursive = true, ParserOutput $parserOutput = null + ); + + /** + * Construct the redirect destination from this content and return an + * array of Titles, or null if this content doesn't represent a redirect. + * The last element in the array is the final destination after all redirects + * have been resolved (up to $wgMaxRedirects times). + * + * @since WD.1 + * + * @return Array of Titles, with the destination last + */ + public function getRedirectChain(); + + /** + * Construct the redirect destination from this content and return a Title, + * or null if this content doesn't represent a redirect. + * This will only return the immediate redirect target, useful for + * the redirect table and other checks that don't need full recursion. + * + * @since WD.1 + * + * @return Title: The corresponding Title + */ + public function getRedirectTarget(); + + /** + * Construct the redirect destination from this content and return the + * Title, or null if this content doesn't represent a redirect. + * + * This will recurse down $wgMaxRedirects times or until a non-redirect + * target is hit in order to provide (hopefully) the Title of the final + * destination instead of another redirect. + * + * There is usually no need to override the default behaviour, subclasses that + * want to implement redirects should override getRedirectTarget(). + * + * @since WD.1 + * + * @return Title + */ + public function getUltimateRedirectTarget(); + + /** + * Returns whether this Content represents a redirect. + * Shorthand for getRedirectTarget() !== null. + * + * @since WD.1 + * + * @return bool + */ + public function isRedirect(); + + /** + * If this Content object is a redirect, this method updates the redirect target. + * Otherwise, it does nothing. + * + * @since WD.1 + * + * @param Title $target the new redirect target + * + * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect) + */ + public function updateRedirect( Title $target ); + + /** + * Returns the section with the given ID. + * + * @since WD.1 + * + * @param $sectionId string The section's ID, given as a numeric string. + * The ID "0" retrieves the section before the first heading, "1" the + * text between the first heading (included) and the second heading + * (excluded), etc. + * @return Content|Boolean|null The section, or false if no such section + * exist, or null if sections are not supported. + */ + public function getSection( $sectionId ); + + /** + * Replaces a section of the content and returns a Content object with the + * section replaced. + * + * @since WD.1 + * + * @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( $section, Content $with, $sectionTitle = '' ); + + /** + * Returns a Content object with pre-save transformations applied (or this + * object if no transformations apply). + * + * @since WD.1 + * + * @param $title Title + * @param $user User + * @param $popts null|ParserOptions + * @return Content + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ); + + /** + * Returns a new WikitextContent object with the given section heading + * prepended, if supported. The default implementation just returns this + * Content object unmodified, ignoring the section header. + * + * @since WD.1 + * + * @param $header string + * @return Content + */ + public function addSectionHeader( $header ); + + /** + * Returns a Content object with preload transformations applied (or this + * object if no transformations apply). + * + * @since WD.1 + * + * @param $title Title + * @param $popts null|ParserOptions + * @return Content + */ + public function preloadTransform( Title $title, ParserOptions $popts ); + + /** + * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in + * similar places. + * + * This may be used to check the content's consistency with global state. This function should + * NOT write any information to the database. + * + * Note that this method will usually be called inside the same transaction bracket that will be used + * to save the new revision. + * + * Note that this method is called before any update to the page table is performed. This means that + * $page may not yet know a page ID. + * + * @param WikiPage $page The page to be saved. + * @param int $flags bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent() + * @param int $baseRevId the ID of the current revision + * @param User $user + * + * @return Status A status object indicating whether the content was successfully prepared for saving. + * If the returned status indicates an error, a rollback will be performed and the + * transaction aborted. + * + * @see see WikiPage::doEditContent() + */ + public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ); + + /** + * Returns a list of updates to perform when this content is deleted. + * The necessary updates may be taken from the Content object, or depend on + * the current state of the database. + * + * @since WD.1 + * + * @param $page \WikiPage the deleted page + * @param $parserOutput null|\ParserOutput optional parser output object + * for efficient access to meta-information about the content object. + * Provide if you have one handy. + * + * @return array A list of DataUpdate instances that will clean up the + * database after deletion. + */ + public function getDeletionUpdates( WikiPage $page, + ParserOutput $parserOutput = null ); + + /** + * Returns true if this Content object matches the given magic word. + * + * @param MagicWord $word the magic word to match + * + * @return bool whether this Content object matches the given magic word. + */ + public function matchMagicWord( MagicWord $word ); + + # TODO: handle ImagePage and CategoryPage + # TODO: make sure we cover lucene search / wikisearch. + # TODO: make sure ReplaceTemplates still works + # FUTURE: nice&sane integration of GeSHi syntax highlighting + # [11:59] Hooks are ugly; make CodeHighlighter interface and a + # config to set the class which handles syntax highlighting + # [12:00] And default it to a DummyHighlighter + + # TODO: make sure we cover the external editor interface (does anyone actually use that?!) + + # TODO: tie into API to provide contentModel for Revisions + # TODO: tie into API to provide serialized version and contentFormat for Revisions + # TODO: tie into API edit interface + # FUTURE: make EditForm plugin for EditPage + + # FUTURE: special type for redirects?! + # FUTURE: MultipartMultipart < WikipageContent (Main + Links + X) + # FUTURE: LinksContent < LanguageLinksContent, CategoriesContent +} + /** * 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. * + * @since 1.WD */ -abstract class Content { - - public function __construct( $modelName = null ) { - $this->mModelName = $modelName; - } - - public function getModelName() { - return $this->mModelName; - } - - protected function checkModelName( $modelName ) { - if ( $modelName !== $this->mModelName ) { - throw new MWException( "Bad content model: expected " . $this->mModelName . " but got found " . $modelName ); - } - } - - public function getContentHandler() { - return ContentHandler::getForContent( $this ); - } - - public function getDefaultFormat() { - return $this->getContentHandler()->getDefaultFormat(); - } - - public function getSupportedFormats() { - return $this->getContentHandler()->getSupportedFormats(); - } - - public function isSupportedFormat( $format ) { - if ( !$format ) return true; # this means "use the default" - - return $this->getContentHandler()->isSupportedFormat( $format ); - } - - protected function checkFormat( $format ) { - if ( !$this->isSupportedFormat( $format ) ) { - throw new MWException( "Format $format is not supported for content model " . $this->getModelName() ); - } - } - - public function serialize( $format = null ) { - return $this->getContentHandler()->serialize( $this, $format ); - } - - /** - * @return String a string representing the content in a way useful for building a full text search index. - * If no useful representation exists, this method returns an empty string. - */ - public abstract function getTextForSearchIndex( ); - - /** - * @return String the wikitext to include when another page includes this content, or false if the content is not - * includable in a wikitext page. - */ - #TODO: allow native handling, bypassing wikitext representation, like for includable special pages. - public abstract function getWikitextForTransclusion( ); #FIXME: use in parser, etc! - - /** - * Returns a textual representation of the content suitable for use in edit summaries and log messages. - * - * @param int $maxlength maximum length of the summary text - * @return String the summary text - */ - public abstract function getTextForSummary( $maxlength = 250 ); - - /** - * Returns native represenation of the data. Interpretation depends on the data model used, - * as given by getDataModel(). - * - * @return mixed the native representation of the content. Could be a string, a nested array - * structure, an object, a binary blob... anything, really. - */ - public abstract function getNativeData( ); #FIXME: review all calls carefully, caller must be aware of content model! - - /** - * returns the content's nominal size in bogo-bytes. - * - * @return int - */ - public abstract function getSize( ); - - public function isEmpty() { - return $this->getSize() == 0; - } - - public function equals( Content $that ) { - if ( empty( $that ) ) return false; - if ( $that === $this ) return true; - if ( $that->getModelName() !== $this->getModelName() ) return false; - - return $this->getNativeData() == $that->getNativeData(); - } - - /** - * Returns true if this content is countable as a "real" wiki page, provided - * that it's also in a countable location (e.g. a current revision in the main namespace). - * - * @param $hasLinks Bool: if it is known whether this content contains links, provide this information here, - * to avoid redundant parsing to find out. - */ - public abstract function isCountable( $hasLinks = null ) ; - - /** - * @param null|Title $title - * @param null $revId - * @param null|ParserOptions $options - * @param Boolean $generateHtml whether to generate Html (default: true). If false, - * the result of calling getText() on the ParserOutput object returned by - * this method is undefined. - * - * @return ParserOutput - */ - public abstract function getParserOutput( Title $title = null, $revId = null, ParserOptions $options = NULL, $generateHtml = true ); - - /** - * Construct the redirect destination from this content and return an - * array of Titles, or null if this content doesn't represent a redirect. - * The last element in the array is the final destination after all redirects - * have been resolved (up to $wgMaxRedirects times). - * - * @return Array of Titles, with the destination last - */ - public function getRedirectChain() { - return null; - } - - /** - * Construct the redirect destination from this content and return an - * array of Titles, or null if this content doesn't represent a redirect. - * This will only return the immediate redirect target, useful for - * the redirect table and other checks that don't need full recursion. - * - * @return Title: The corresponding Title - */ - public function getRedirectTarget() { - return null; - } - - /** - * Construct the redirect destination from this content and return the - * Title, or null if this content doesn't represent a redirect. - * This will recurse down $wgMaxRedirects times or until a non-redirect target is hit - * in order to provide (hopefully) the Title of the final destination instead of another redirect. - * - * @return Title - */ - public function getUltimateRedirectTarget() { - return null; - } - - public function isRedirect() { - return $this->getRedirectTarget() != null; - } - - /** - * Returns the section with the given id. - * - * The default implementation returns null. - * - * @param String $sectionId the section's id - * @return Content|Boolean|null the section, or false if no such section exist, or null if sections are not supported - */ - public function getSection( $sectionId ) { - return null; - } - - /** - * Replaces a section of the content and returns a Content object with the section 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( $section, Content $with, $sectionTitle = '' ) { - return $this; - } - - /** - * Returns a Content object with pre-save transformations applied (or this object if no transformations apply). - * - * @param Title $title - * @param User $user - * @param null|ParserOptions $popts - * @return Content - */ - public function preSaveTransform( Title $title, User $user, ParserOptions $popts = null ) { - return $this; - } - - /** - * Returns a new WikitextContent object with the given section heading prepended, if supported. - * The default implementation just returns this Content object unmodified, ignoring the section header. - * - * @param $header String - * @return Content - */ - public function addSectionHeader( $header ) { - return $this; - } - - /** - * Returns a Content object with preload transformations applied (or this object if no transformations apply). - * - * @param Title $title - * @param null|ParserOptions $popts - * @return Content - */ - public function preloadTransform( Title $title, ParserOptions $popts = null ) { - return $this; - } - - # TODO: minimize special cases for CSS/JS; how to handle extra message for JS/CSS previews?? - # TODO: handle ImagePage and CategoryPage - # TODO: hook into dump generation to serialize and record model and format! - - # TODO: make sure we cover lucene search / wikisearch. - # TODO: make sure ReplaceTemplates still works - # TODO: nice&sane integration of GeSHi syntax highlighting - # [11:59] Hooks are ugly; make CodeHighlighter interface and a config to set the class which handles syntax highlighting - # [12:00] And default it to a DummyHighlighter - - # TODO: make sure we cover the external editor interface (does anyone actually use that?!) - - # TODO: tie into API to provide contentModel for Revisions - # TODO: tie into API to provide serialized version and contentFormat for Revisions - # TODO: tie into API edit interface - # TODO: make EditForm plugin for EditPage - - # XXX: isCacheable( ) # can/should we do this here? +abstract class AbstractContent implements Content { + + /** + * Name of the content model this Content object represents. + * Use with CONTENT_MODEL_XXX constants + * + * @var string $model_id + */ + protected $model_id; + + /** + * @param String $model_id + */ + public function __construct( $model_id = null ) { + $this->model_id = $model_id; + } + + /** + * @see Content::getModel() + */ + public function getModel() { + return $this->model_id; + } + + /** + * Throws an MWException if $model_id is not the id of the content model + * supported by this Content object. + * + * @param $model_id int the model to check + * + * @throws MWException + */ + protected function checkModelID( $model_id ) { + if ( $model_id !== $this->model_id ) { + throw new MWException( "Bad content model: " . + "expected {$this->model_id} " . + "but got $model_id." ); + } + } + + /** + * @see Content::getContentHandler() + */ + public function getContentHandler() { + return ContentHandler::getForContent( $this ); + } + + /** + * @see Content::getDefaultFormat() + */ + public function getDefaultFormat() { + return $this->getContentHandler()->getDefaultFormat(); + } + + /** + * @see Content::getSupportedFormats() + */ + public function getSupportedFormats() { + return $this->getContentHandler()->getSupportedFormats(); + } + + /** + * @see Content::isSupportedFormat() + */ + public function isSupportedFormat( $format ) { + if ( !$format ) { + return true; // this means "use the default" + } + + return $this->getContentHandler()->isSupportedFormat( $format ); + } + + /** + * Throws an MWException if $this->isSupportedFormat( $format ) doesn't + * return true. + * + * @param $format + * @throws MWException + */ + protected function checkFormat( $format ) { + if ( !$this->isSupportedFormat( $format ) ) { + throw new MWException( "Format $format is not supported for content model " . + $this->getModel() ); + } + } + + /** + * @see Content::serialize + */ + public function serialize( $format = null ) { + return $this->getContentHandler()->serializeContent( $this, $format ); + } + + /** + * @see Content::isEmpty() + */ + public function isEmpty() { + return $this->getSize() == 0; + } + + /** + * @see Content::isValid() + */ + public function isValid() { + return true; + } + + /** + * @see Content::equals() + */ + public function equals( Content $that = null ) { + if ( is_null( $that ) ) { + return false; + } + + if ( $that === $this ) { + return true; + } + + if ( $that->getModel() !== $this->getModel() ) { + return false; + } + + return $this->getNativeData() === $that->getNativeData(); + } + + + /** + * Returns a list of DataUpdate objects for recording information about this + * Content in some secondary data store. + * + * This default implementation calls + * $this->getParserOutput( $content, $title, null, null, false ), + * and then calls getSecondaryDataUpdates( $title, $recursive ) on the + * resulting ParserOutput object. + * + * Subclasses may override this to determine the secondary data updates more + * efficiently, preferrably without the need to generate a parser output object. + * + * @see Content::getSecondaryDataUpdates() + * + * @param $title Title The context for determining the necessary updates + * @param $old Content|null An optional Content object representing the + * previous content, i.e. the content being replaced by this Content + * object. + * @param $recursive boolean Whether to include recursive updates (default: + * false). + * @param $parserOutput ParserOutput|null Optional ParserOutput object. + * Provide if you have one handy, to avoid re-parsing of the content. + * + * @return Array. A list of DataUpdate objects for putting information + * about this content object somewhere. + * + * @since WD.1 + */ + public function getSecondaryDataUpdates( Title $title, + Content $old = null, + $recursive = true, ParserOutput $parserOutput = null + ) { + if ( !$parserOutput ) { + $parserOutput = $this->getParserOutput( $title, null, null, false ); + } + + return $parserOutput->getSecondaryDataUpdates( $title, $recursive ); + } + + + /** + * @see Content::getRedirectChain() + */ + public function getRedirectChain() { + global $wgMaxRedirects; + $title = $this->getRedirectTarget(); + if ( is_null( $title ) ) { + return null; + } + // recursive check to follow double redirects + $recurse = $wgMaxRedirects; + $titles = array( $title ); + while ( --$recurse > 0 ) { + if ( $title->isRedirect() ) { + $page = WikiPage::factory( $title ); + $newtitle = $page->getRedirectTarget(); + } else { + break; + } + // Redirects to some special pages are not permitted + if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) { + // The new title passes the checks, so make that our current + // title so that further recursion can be checked + $title = $newtitle; + $titles[] = $newtitle; + } else { + break; + } + } + return $titles; + } + + /** + * @see Content::getRedirectTarget() + */ + public function getRedirectTarget() { + return null; + } + + /** + * @see Content::getUltimateRedirectTarget() + * @note: migrated here from Title::newFromRedirectRecurse + */ + public function getUltimateRedirectTarget() { + $titles = $this->getRedirectChain(); + return $titles ? array_pop( $titles ) : null; + } + + /** + * @see Content::isRedirect() + * + * @since WD.1 + * + * @return bool + */ + public function isRedirect() { + return $this->getRedirectTarget() !== null; + } + + /** + * @see Content::updateRedirect() + * + * This default implementation always returns $this. + * + * @since WD.1 + * + * @return Content $this + */ + public function updateRedirect( Title $target ) { + return $this; + } + + /** + * @see Content::getSection() + */ + public function getSection( $sectionId ) { + return null; + } + + /** + * @see Content::replaceSection() + */ + public function replaceSection( $section, Content $with, $sectionTitle = '' ) { + return null; + } + + /** + * @see Content::preSaveTransform() + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { + return $this; + } + + /** + * @see Content::addSectionHeader() + */ + public function addSectionHeader( $header ) { + return $this; + } + + /** + * @see Content::preloadTransform() + */ + public function preloadTransform( Title $title, ParserOptions $popts ) { + return $this; + } + + /** + * @see Content::prepareSave() + */ + public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) { + if ( $this->isValid() ) { + return Status::newGood(); + } else { + return Status::newFatal( "invalid-content-data" ); + } + } + + /** + * @see Content::getDeletionUpdates() + * + * @since WD.1 + * + * @param $page \WikiPage the deleted page + * @param $parserOutput null|\ParserOutput optional parser output object + * for efficient access to meta-information about the content object. + * Provide if you have one handy. + * + * @return array A list of DataUpdate instances that will clean up the + * database after deletion. + */ + public function getDeletionUpdates( WikiPage $page, + ParserOutput $parserOutput = null ) + { + return array( + new LinksDeletionUpdate( $page ), + ); + } + + /** + * @see Content::matchMagicWord() + * + * This default implementation always returns false. Subclasses may override this to supply matching logic. + * + * @param MagicWord $word + * + * @return bool + */ + public function matchMagicWord( MagicWord $word ) { + return false; + } } /** - * Content object implementation for representing flat text. The + * Content object implementation for representing flat text. + * + * TextContent instances are immutable + * + * @since WD.1 */ -abstract class TextContent extends Content { - public function __construct( $text, $modelName = null ) { - parent::__construct($modelName); - - $this->mText = $text; - } - - public function getTextForSummary( $maxlength = 250 ) { - global $wgContLang; - - $text = $this->getNativeData(); - - $truncatedtext = $wgContLang->truncate( - preg_replace( "/[\n\r]/", ' ', $text ), - max( 0, $maxlength ) ); - - return $truncatedtext; - } - - /** - * returns the content's nominal size in bogo-bytes. - */ - public function getSize( ) { #FIXME: use! replace strlen in WikiPage. - $text = $this->getNativeData( ); - return strlen( $text ); - } - - /** - * Returns true if this content is not a redirect, and $wgArticleCountMethod is "any". - * - * @param $hasLinks Bool: if it is known whether this content contains links, provide this information here, - * to avoid redundant parsing to find out. - */ - public function isCountable( $hasLinks = null ) { - global $wgArticleCountMethod; - - if ( $this->isRedirect( ) ) { - return false; - } - - if ( $wgArticleCountMethod === 'any' ) { - return true; - } - - return false; - } - - /** - * Returns the text represented by this Content object, as a string. - * - * @return String the raw text - */ - public function getNativeData( ) { - $text = $this->mText; - return $text; - } - - /** - * Returns the text represented by this Content object, as a string. - * - * @return String the raw text - */ - public function getTextForSearchIndex( ) { #FIXME: use! - return $this->getNativeData(); - } - - /** - * Returns the text represented by this Content object, as a string. - * - * @return String the raw text - */ - public function getWikitextForTransclusion( ) { #FIXME: use! - return $this->getNativeData(); - } - - /** - * 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, $generateHtml = true ) { - # generic implementation, relying on $this->getHtml() - - if ( $generateHtml ) $html = $this->getHtml( $options ); - else $html = ''; - - $po = new ParserOutput( $html ); - - return $po; - } - - protected abstract function getHtml( ); - +abstract class TextContent extends AbstractContent { + + public function __construct( $text, $model_id = null ) { + parent::__construct( $model_id ); + + $this->mText = $text; + } + + public function copy() { + return $this; # NOTE: this is ok since TextContent are immutable. + } + + public function getTextForSummary( $maxlength = 250 ) { + global $wgContLang; + + $text = $this->getNativeData(); + + $truncatedtext = $wgContLang->truncate( + preg_replace( "/[\n\r]/", ' ', $text ), + max( 0, $maxlength ) ); + + return $truncatedtext; + } + + /** + * returns the text's size in bytes. + * + * @return int The size + */ + public function getSize( ) { + $text = $this->getNativeData( ); + return strlen( $text ); + } + + /** + * Returns true if this content is not a redirect, and $wgArticleCountMethod + * is "any". + * + * @param $hasLinks Bool: if it is known whether this content contains links, + * provide this information here, to avoid redundant parsing to find out. + * + * @return bool True if the content is countable + */ + public function isCountable( $hasLinks = null ) { + global $wgArticleCountMethod; + + if ( $this->isRedirect( ) ) { + return false; + } + + if ( $wgArticleCountMethod === 'any' ) { + return true; + } + + return false; + } + + /** + * Returns the text represented by this Content object, as a string. + * + * @param the raw text + */ + public function getNativeData( ) { + $text = $this->mText; + return $text; + } + + /** + * Returns the text represented by this Content object, as a string. + * + * @param the raw text + */ + public function getTextForSearchIndex( ) { + return $this->getNativeData(); + } + + /** + * Returns the text represented by this Content object, as a string. + * + * @param the raw text + */ + public function getWikitextForTransclusion( ) { + return $this->getNativeData(); + } + + /** + * Diff this content object with another content object.. + * + * @since WD.diff + * + * @param $that Content the other content object to compare this content object to + * @param $lang Language the language object to use for text segmentation. + * If not given, $wgContentLang is used. + * + * @return DiffResult a diff representing the changes that would have to be + * made to this content object to make it equal to $that. + */ + public function diff( Content $that, Language $lang = null ) { + global $wgContLang; + + $this->checkModelID( $that->getModel() ); + + # @todo: could implement this in DifferenceEngine and just delegate here? + + if ( !$lang ) $lang = $wgContLang; + + $otext = $this->getNativeData(); + $ntext = $this->getNativeData(); + + # Note: Use native PHP diff, external engines don't give us abstract output + $ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) ); + $nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) ); + + $diff = new Diff( $ota, $nta ); + return $diff; + } + + + /** + * Returns a generic ParserOutput object, wrapping the HTML returned by + * getHtml(). + * + * @param $title Title Context title for parsing + * @param $revId int|null Revision ID (for {{REVISIONID}}) + * @param $options ParserOptions|null Parser options + * @param $generateHtml bool Whether or not to generate HTML + * + * @return ParserOutput representing the HTML form of the text + */ + public function getParserOutput( Title $title, + $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + # Generic implementation, relying on $this->getHtml() + + if ( $generateHtml ) { + $html = $this->getHtml(); + } else { + $html = ''; + } + + $po = new ParserOutput( $html ); + return $po; + } + + /** + * Generates an HTML version of the content, for display. Used by + * getParserOutput() to construct a ParserOutput object. + * + * This default implementation just calls getHighlightHtml(). Content + * models that have another mapping to HTML (as is the case for markup + * languages like wikitext) should override this method to generate the + * appropriate HTML. + * + * @return string An HTML representation of the content + */ + protected function getHtml() { + return $this->getHighlightHtml(); + } + + /** + * Generates a syntax-highlighted version of the content, as HTML. + * Used by the default implementation of getHtml(). + * + * @return string an HTML representation of the content's markup + */ + protected function getHighlightHtml( ) { + # TODO: make Highlighter interface, use highlighter here, if available + return htmlspecialchars( $this->getNativeData() ); + } } +/** + * @since WD.1 + */ class WikitextContent extends TextContent { - public function __construct( $text ) { - parent::__construct($text, CONTENT_MODEL_WIKITEXT); - - $this->mDefaultParserOptions = null; #TODO: use per-class static member?! - } - - 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 per-class static member?! - $this->mDefaultParserOptions = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); - } - - return $this->mDefaultParserOptions; - } - - /** - * 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, $generateHtml = true ) { - global $wgParser; - - if ( !$options ) { - $options = $this->getDefaultParserOptions(); - } - - $po = $wgParser->parse( $this->mText, $title, $options, true, true, $revId ); - - return $po; - } - - /** - * Returns the section with the given id. - * - * @param String $sectionId the section's id - * @return Content|false|null the section, or false if no such section exist, or null if sections are not supported - */ - public function getSection( $section ) { - global $wgParser; - - $text = $this->getNativeData(); - $sect = $wgParser->getSection( $text, $section, false ); - - return new WikitextContent( $sect ); - } - - /** - * Replaces a section in the wikitext - * - * @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( $section, Content $with, $sectionTitle = '' ) { - global $wgParser; - - wfProfileIn( __METHOD__ ); - - $myModelName = $this->getModelName(); - $sectionModelName = $with->getModelName(); - - if ( $sectionModelName != $myModelName ) { - throw new MWException( "Incompatible content model for section: document uses $myModelName, section uses $sectionModelName." ); - } - - $oldtext = $this->getNativeData(); - $text = $with->getNativeData(); - - 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 $newContent; - } - - /** - * Returns a new WikitextContent object with the given section heading prepended. - * - * @param $header String - * @return Content - */ - public function addSectionHeader( $header ) { - $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $this->getNativeData(); - - return new WikitextContent( $text ); - } - - /** - * Returns a Content object with pre-save transformations applied (or this object if no transformations apply). - * - * @param Title $title - * @param User $user - * @param null|ParserOptions $popts - * @return Content - */ - public function preSaveTransform( Title $title, User $user, ParserOptions $popts = null ) { - global $wgParser; - - if ( $popts == null ) $popts = $this->getDefaultParserOptions(); - - $text = $this->getNativeData(); - $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); - - return new WikitextContent( $pst ); - } - - /** - * Returns a Content object with preload transformations applied (or this object if no transformations apply). - * - * @param Title $title - * @param null|ParserOptions $popts - * @return Content - */ - public function preloadTransform( Title $title, ParserOptions $popts = null ) { - global $wgParser; - - if ( $popts == null ) $popts = $this->getDefaultParserOptions(); - - $text = $this->getNativeData(); - $plt = $wgParser->getPreloadText( $text, $title, $popts ); - - return new WikitextContent( $plt ); - } - - public function getRedirectChain() { - $text = $this->getNativeData(); - return Title::newFromRedirectArray( $text ); - } - - public function getRedirectTarget() { - $text = $this->getNativeData(); - return Title::newFromRedirect( $text ); - } - - public function getUltimateRedirectTarget() { - $text = $this->getNativeData(); - return Title::newFromRedirectRecurse( $text ); - } - - /** - * Returns true if this content is not a redirect, and this content's text is countable according to - * the criteria defiend by $wgArticleCountMethod. - * - * @param $hasLinks Bool: if it is known whether this content contains links, provide this information here, - * to avoid redundant parsing to find out. - */ - public function isCountable( $hasLinks = null ) { - global $wgArticleCountMethod; - - if ( $this->isRedirect( ) ) { - return false; - } - - $text = $this->getNativeData(); - - switch ( $wgArticleCountMethod ) { - case 'any': - return true; - case 'comma': - if ( $text === false ) { - $text = $this->getRawText(); - } - return strpos( $text, ',' ) !== false; - case 'link': - if ( $hasLinks === null ) { # not know, find out - $po = $this->getParserOutput(); - $links = $po->getLinks(); - $hasLinks = !empty( $links ); - } - - return $hasLinks; - } - } - - public function getTextForSummary( $maxlength = 250 ) { - $truncatedtext = parent::getTextForSummary( $maxlength ); - - #clean up unfinished links - #XXX: make this optional? wasn't there in autosummary, but required for deletion summary. - $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext ); - - return $truncatedtext; - } + public function __construct( $text ) { + parent::__construct( $text, CONTENT_MODEL_WIKITEXT ); + } + + /** + * @see Content::getSection() + */ + public function getSection( $section ) { + global $wgParser; + + $text = $this->getNativeData(); + $sect = $wgParser->getSection( $text, $section, false ); + + return new WikitextContent( $sect ); + } + + /** + * @see Content::replaceSection() + */ + public function replaceSection( $section, Content $with, $sectionTitle = '' ) { + wfProfileIn( __METHOD__ ); + + $myModelId = $this->getModel(); + $sectionModelId = $with->getModel(); + + if ( $sectionModelId != $myModelId ) { + throw new MWException( "Incompatible content model for section: " . + "document uses $myModelId but " . + "section uses $sectionModelId." ); + } + + $oldtext = $this->getNativeData(); + $text = $with->getNativeData(); + + if ( $section === '' ) { + return $with; # XXX: copy first? + } if ( $section == 'new' ) { + # Inserting a new section + $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' ) + ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\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 $newContent; + } + + /** + * Returns a new WikitextContent object with the given section heading + * prepended. + * + * @param $header string + * @return Content + */ + public function addSectionHeader( $header ) { + $text = wfMessage( 'newsectionheaderdefaultlevel' ) + ->inContentLanguage()->params( $header )->text(); + $text .= "\n\n"; + $text .= $this->getNativeData(); + + return new WikitextContent( $text ); + } + + /** + * Returns a Content object with pre-save transformations applied using + * Parser::preSaveTransform(). + * + * @param $title Title + * @param $user User + * @param $popts ParserOptions + * @return Content + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { + global $wgParser; + + $text = $this->getNativeData(); + $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); + + return new WikitextContent( $pst ); + } + + /** + * Returns a Content object with preload transformations applied (or this + * object if no transformations apply). + * + * @param $title Title + * @param $popts ParserOptions + * @return Content + */ + public function preloadTransform( Title $title, ParserOptions $popts ) { + global $wgParser; + + $text = $this->getNativeData(); + $plt = $wgParser->getPreloadText( $text, $title, $popts ); + + return new WikitextContent( $plt ); + } + + /** + * Implement redirect extraction for wikitext. + * + * @return null|Title + * + * @note: migrated here from Title::newFromRedirectInternal() + * + * @see Content::getRedirectTarget + * @see AbstractContent::getRedirectTarget + */ + public function getRedirectTarget() { + global $wgMaxRedirects; + if ( $wgMaxRedirects < 1 ) { + // redirects are disabled, so quit early + return null; + } + $redir = MagicWord::get( 'redirect' ); + $text = trim( $this->getNativeData() ); + if ( $redir->matchStartAndRemove( $text ) ) { + // Extract the first link and see if it's usable + // Ensure that it really does come directly after #REDIRECT + // Some older redirects included a colon, so don't freak about that! + $m = array(); + if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) { + // Strip preceding colon used to "escape" categories, etc. + // and URL-decode links + if ( strpos( $m[1], '%' ) !== false ) { + // Match behavior of inline link parsing here; + $m[1] = rawurldecode( ltrim( $m[1], ':' ) ); + } + $title = Title::newFromText( $m[1] ); + // If the title is a redirect to bad special pages or is invalid, return null + if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) { + return null; + } + return $title; + } + } + return null; + } + + /** + * @see Content::updateRedirect() + * + * This implementation replaces the first link on the page with the given new target + * if this Content object is a redirect. Otherwise, this method returns $this. + * + * @since WD.1 + * + * @param Title $target + * + * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect) + */ + public function updateRedirect( Title $target ) { + if ( !$this->isRedirect() ) { + return $this; + } + + # Fix the text + # Remember that redirect pages can have categories, templates, etc., + # so the regex has to be fairly general + $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x', + '[[' . $target->getFullText() . ']]', + $this->getNativeData(), 1 ); + + return new WikitextContent( $newText ); + } + + /** + * Returns true if this content is not a redirect, and this content's text + * is countable according to the criteria defined by $wgArticleCountMethod. + * + * @param $hasLinks Bool if it is known whether this content contains + * links, provide this information here, to avoid redundant parsing to + * find out. + * @param $title null|\Title + * + * @internal param \IContextSource $context context for parsing if necessary + * + * @return bool True if the content is countable + */ + public function isCountable( $hasLinks = null, Title $title = null ) { + global $wgArticleCountMethod; + + if ( $this->isRedirect( ) ) { + return false; + } + + $text = $this->getNativeData(); + + switch ( $wgArticleCountMethod ) { + case 'any': + return true; + case 'comma': + return strpos( $text, ',' ) !== false; + case 'link': + if ( $hasLinks === null ) { # not known, find out + if ( !$title ) { + $context = RequestContext::getMain(); + $title = $context->getTitle(); + } + + $po = $this->getParserOutput( $title, null, null, false ); + $links = $po->getLinks(); + $hasLinks = !empty( $links ); + } + + return $hasLinks; + } + + return false; + } + + public function getTextForSummary( $maxlength = 250 ) { + $truncatedtext = parent::getTextForSummary( $maxlength ); + + # clean up unfinished links + # XXX: make this optional? wasn't there in autosummary, but required for + # deletion summary. + $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext ); + + return $truncatedtext; + } + + + /** + * Returns a ParserOutput object resulting from parsing the content's text + * using $wgParser. + * + * @since WD.1 + * + * @param $content Content the content to render + * @param $title \Title + * @param $revId null + * @param $options null|ParserOptions + * @param $generateHtml bool + * + * @internal param \IContextSource|null $context + * @return ParserOutput representing the HTML form of the text + */ + public function getParserOutput( Title $title, + $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + global $wgParser; + + if ( !$options ) { + $options = new ParserOptions(); + } + + $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId ); + return $po; + } + + protected function getHtml() { + throw new MWException( + "getHtml() not implemented for wikitext. " + . "Use getParserOutput()->getText()." + ); + } + + /** + * @see Content::matchMagicWord() + * + * This implementation calls $word->match() on the this TextContent object's text. + * + * @param MagicWord $word + * + * @return bool whether this Content object matches the given magic word. + */ + public function matchMagicWord( MagicWord $word ) { + return $word->match( $this->getNativeData() ); + } } -class MessageContent extends TextContent { - public function __construct( $msg_key, $params = null, $options = null ) { - parent::__construct(null, CONTENT_MODEL_WIKITEXT); #XXX: messages may be wikitext, html or plain text! and maybe even something else entirely. - - $this->mMessageKey = $msg_key; - - $this->mParameters = $params; - - if ( !$options ) $options = array(); - $this->mOptions = $options; - - $this->mHtmlOptions = null; - } - - /** - * 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 getNativeData( ) { - $opt = array_diff( $this->mOptions, array('parse', 'parseinline') ); - - return wfMsgExt( $this->mMessageKey, $this->mParameters, $opt ); - } - +/** + * Wrapper allowing us to handle a system message as a Content object. Note that this is generally *not* used + * to represent content from the MediaWiki namespace, and that there is no MessageContentHandler. MessageContent + * is just intended as glue for wrapping a message programatically. + * + * @since WD.1 + */ +class MessageContent extends AbstractContent { + + /** + * @var Message + */ + protected $mMessage; + + /** + * @param Message|String $msg A Message object, or a message key + * @param array|null $params An optional array of message parameters + */ + public function __construct( $msg, $params = null ) { + # XXX: messages may be wikitext, html or plain text! and maybe even something else entirely. + parent::__construct( CONTENT_MODEL_WIKITEXT ); + + if ( is_string( $msg ) ) { + $this->mMessage = wfMessage( $msg ); + } else { + $this->mMessage = clone $msg; + } + + if ( $params ) { + $this->mMessage = $this->mMessage->params( $params ); + } + } + + /** + * Returns the message as rendered HTML + * + * @return string The message text, parsed into html + */ + public function getHtml() { + return $this->mMessage->parse(); + } + + /** + * Returns the message as rendered HTML + * + * @return string The message text, parsed into html + */ + public function getWikitext() { + return $this->mMessage->text(); + } + + /** + * Returns the message object, with any parameters already substituted. + * + * @return Message The message object. + */ + public function getNativeData() { + //NOTE: Message objects are mutable. Cloning here makes MessageContent immutable. + return clone $this->mMessage; + } + + /** + * @see Content::getTextForSearchIndex + */ + public function getTextForSearchIndex() { + return $this->mMessage->plain(); + } + + /** + * @see Content::getWikitextForTransclusion + */ + public function getWikitextForTransclusion() { + return $this->getWikitext(); + } + + /** + * @see Content::getTextForSummary + */ + public function getTextForSummary( $maxlength = 250 ) { + return substr( $this->mMessage->plain(), 0, $maxlength ); + } + + /** + * @see Content::getSize + * + * @return int + */ + public function getSize() { + return strlen( $this->mMessage->plain() ); + } + + /** + * @see Content::copy + * + * @return Content. A copy of this object + */ + public function copy() { + // MessageContent is immutable (because getNativeData() returns a clone of the Message object) + return $this; + } + + /** + * @see Content::isCountable + * + * @return bool false + */ + public function isCountable( $hasLinks = null ) { + return false; + } + + /** + * @see Content::getParserOutput + * + * @return ParserOutput + */ + public function getParserOutput( + Title $title, $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + + if ( $generateHtml ) { + $html = $this->getHtml(); + } else { + $html = ''; + } + + $po = new ParserOutput( $html ); + return $po; + } } - +/** + * @since WD.1 + */ class JavaScriptContent extends TextContent { - public function __construct( $text ) { - parent::__construct($text, CONTENT_MODEL_JAVASCRIPT); - } - - protected function getHtml( ) { - $html = ""; - $html .= "
\n";
-        $html .= htmlspecialchars( $this->getNativeData() );
-        $html .= "\n
\n"; - - return $html; - } - + public function __construct( $text ) { + parent::__construct( $text, CONTENT_MODEL_JAVASCRIPT ); + } + + /** + * Returns a Content object with pre-save transformations applied using + * Parser::preSaveTransform(). + * + * @param Title $title + * @param User $user + * @param ParserOptions $popts + * @return Content + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { + global $wgParser; + // @todo: make pre-save transformation optional for script pages + // See bug #32858 + + $text = $this->getNativeData(); + $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); + + return new JavaScriptContent( $pst ); + } + + + protected function getHtml( ) { + $html = ""; + $html .= "
\n";
+		$html .= $this->getHighlightHtml( );
+		$html .= "\n
\n"; + + return $html; + } } +/** + * @since WD.1 + */ class CssContent extends TextContent { - public function __construct( $text ) { - parent::__construct($text, CONTENT_MODEL_CSS); - } - - protected function getHtml( ) { - $html = ""; - $html .= "
\n";
-        $html .= htmlspecialchars( $this->getNativeData() );
-        $html .= "\n
\n"; - - return $html; - } + public function __construct( $text ) { + parent::__construct( $text, CONTENT_MODEL_CSS ); + } + + /** + * Returns a Content object with pre-save transformations applied using + * Parser::preSaveTransform(). + * + * @param $title Title + * @param $user User + * @param $popts ParserOptions + * @return Content + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { + global $wgParser; + // @todo: make pre-save transformation optional for script pages + + $text = $this->getNativeData(); + $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); + + return new CssContent( $pst ); + } + + + protected function getHtml( ) { + $html = ""; + $html .= "
\n";
+		$html .= $this->getHighlightHtml( );
+		$html .= "\n
\n"; + + return $html; + } } - -#FUTURE: special type for redirects?! -#FUTURE: MultipartMultipart < WikipageContent (Main + Links + X) -#FUTURE: LinksContent < LanguageLinksContent, CategoriesContent -#EXAMPLE: CoordinatesContent -#EXAMPLE: WikidataContent