From: daniel Date: Mon, 24 Sep 2012 20:51:53 +0000 (+0200) Subject: Split Content.php into one file per class. X-Git-Tag: 1.31.0-rc.0~22097^2^2~15 X-Git-Url: https://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/exercices/journal.php?a=commitdiff_plain;h=2b1016eb9dad68170af78c78479a7268a8522675;p=lhc%2Fweb%2Fwiklou.git Split Content.php into one file per class. Change-Id: Ib49d9ec729613dcc43d38d46cb133ff9df459d79 --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 788e3f2fb4..c687a74cdb 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -290,17 +290,17 @@ $wgAutoloadLocalClasses = array( 'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php', # content handler - 'Content' => 'includes/Content.php', - 'AbstractContent' => 'includes/Content.php', - 'ContentHandler' => 'includes/ContentHandler.php', - 'CssContent' => 'includes/Content.php', - 'TextContentHandler' => 'includes/ContentHandler.php', - 'CssContentHandler' => 'includes/ContentHandler.php', - 'JavaScriptContent' => 'includes/Content.php', - 'JavaScriptContentHandler' => 'includes/ContentHandler.php', - 'MessageContent' => 'includes/Content.php', - 'TextContent' => 'includes/Content.php', - 'WikitextContent' => 'includes/Content.php', + 'Content' => 'includes/content/Content.php', + 'AbstractContent' => 'includes/content/AbstractContent.php', + 'ContentHandler' => 'includes/content/ContentHandler.php', + 'CssContent' => 'includes/content/CssContent.php', + 'TextContentHandler' => 'includes/content/ContentHandler.php', + 'CssContentHandler' => 'includes/content/ContentHandler.php', + 'JavaScriptContent' => 'includes/content/JavaScriptContent.php', + 'JavaScriptContentHandler' => 'includes/content/ContentHandler.php', + 'MessageContent' => 'includes/content/MessageContent.php', + 'TextContent' => 'includes/content/TextContent.php', + 'WikitextContent' => 'includes/content/WikitextContent.php', 'WikitextContentHandler' => 'includes/ContentHandler.php', # includes/actions diff --git a/includes/Content.php b/includes/Content.php deleted file mode 100644 index da3b8ffc77..0000000000 --- a/includes/Content.php +++ /dev/null @@ -1,1471 +0,0 @@ -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: ImagePage and CategoryPage interfere with per-content action handlers - # TODO: make sure WikiSearch extension still works - # TODO: make sure ReplaceTemplates extension 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 -} - - -/** - * 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. - * - * @since 1.WD - */ -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. - * - * TextContent instances are immutable - * - * @since WD.1 - */ -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 ); - } - - /** - * @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() ); - } -} - -/** - * 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 ); - } - - /** - * 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 ); - } - - /** - * 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; - } -} diff --git a/includes/ContentHandler.php b/includes/ContentHandler.php deleted file mode 100644 index bf1349adc8..0000000000 --- a/includes/ContentHandler.php +++ /dev/null @@ -1,1136 +0,0 @@ -getNativeData(). - * - * If $content is not a TextContent object, the behavior of this method - * depends on the global $wgContentHandlerTextFallback: - * - If $wgContentHandlerTextFallback is 'fail' and $content is not a - * TextContent object, an MWException is thrown. - * - If $wgContentHandlerTextFallback is 'serialize' and $content is not a - * TextContent object, $content->serialize() is called to get a string - * form of the content. - * - If $wgContentHandlerTextFallback is 'ignore' and $content is not a - * TextContent object, this method returns null. - * - otherwise, the behaviour is undefined. - * - * @since WD.1 - * @deprecated since WD.1. Always try to use the content object. - * - * @static - * @param $content Content|null - * @return null|string the textual form of $content, if available - * @throws MWException if $content is not an instance of TextContent and - * $wgContentHandlerTextFallback was set to 'fail'. - */ - public static function getContentText( Content $content = null ) { - global $wgContentHandlerTextFallback; - - if ( is_null( $content ) ) { - return ''; - } - - if ( $content instanceof TextContent ) { - return $content->getNativeData(); - } - - if ( $wgContentHandlerTextFallback == 'fail' ) { - throw new MWException( - "Attempt to get text from Content with model " . - $content->getModel() - ); - } - - if ( $wgContentHandlerTextFallback == 'serialize' ) { - return $content->serialize(); - } - - return null; - } - - /** - * Convenience function for creating a Content object from a given textual - * representation. - * - * $text will be deserialized into a Content object of the model specified - * by $modelId (or, if that is not given, $title->getContentModel()) using - * the given format. - * - * @since WD.1 - * - * @static - * - * @param $text string the textual representation, will be - * unserialized to create the Content object - * @param $title null|Title the title of the page this text belongs to. - * Required if $modelId is not provided. - * @param $modelId null|string the model to deserialize to. If not provided, - * $title->getContentModel() is used. - * @param $format null|string the format to use for deserialization. If not - * given, the model's default format is used. - * - * @return Content a Content object representing $text - * - * @throw MWException if $model or $format is not supported or if $text can - * not be unserialized using $format. - */ - public static function makeContent( $text, Title $title = null, - $modelId = null, $format = null ) - { - if ( is_null( $modelId ) ) { - if ( is_null( $title ) ) { - throw new MWException( "Must provide a Title object or a content model ID." ); - } - - $modelId = $title->getContentModel(); - } - - $handler = ContentHandler::getForModelID( $modelId ); - return $handler->unserializeContent( $text, $format ); - } - - /** - * Returns the name of the default content model to be used for the page - * with the given title. - * - * Note: There should rarely be need to call this method directly. - * To determine the actual content model for a given page, use - * Title::getContentModel(). - * - * Which model is to be used by default for the page is determined based - * on several factors: - * - The global setting $wgNamespaceContentModels specifies a content model - * per namespace. - * - The hook DefaultModelFor may be used to override the page's default - * model. - * - Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript - * model if they end in .js or .css, respectively. - * - Pages in NS_MEDIAWIKI default to the wikitext model otherwise. - * - The hook TitleIsCssOrJsPage may be used to force a page to use the CSS - * or JavaScript model if they end in .js or .css, respectively. - * - The hook TitleIsWikitextPage may be used to force a page to use the - * wikitext model. - * - * If none of the above applies, the wikitext model is used. - * - * Note: this is used by, and may thus not use, Title::getContentModel() - * - * @since WD.1 - * - * @static - * @param $title Title - * @return null|string default model name for the page given by $title - */ - public static function getDefaultModelFor( Title $title ) { - global $wgNamespaceContentModels; - - // NOTE: this method must not rely on $title->getContentModel() directly or indirectly, - // because it is used to initialize the mContentModel member. - - $ns = $title->getNamespace(); - - $ext = false; - $m = null; - $model = null; - - if ( !empty( $wgNamespaceContentModels[ $ns ] ) ) { - $model = $wgNamespaceContentModels[ $ns ]; - } - - // Hook can determine default model - if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) { - if ( !is_null( $model ) ) { - return $model; - } - } - - // Could this page contain custom CSS or JavaScript, based on the title? - $isCssOrJsPage = NS_MEDIAWIKI == $ns && preg_match( '!\.(css|js)$!u', $title->getText(), $m ); - if ( $isCssOrJsPage ) { - $ext = $m[1]; - } - - // Hook can force JS/CSS - wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) ); - - // Is this a .css subpage of a user page? - $isJsCssSubpage = NS_USER == $ns - && !$isCssOrJsPage - && preg_match( "/\\/.*\\.(js|css)$/", $title->getText(), $m ); - if ( $isJsCssSubpage ) { - $ext = $m[1]; - } - - // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook? - $isWikitext = is_null( $model ) || $model == CONTENT_MODEL_WIKITEXT; - $isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage; - - // Hook can override $isWikitext - wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) ); - - if ( !$isWikitext ) { - switch ( $ext ) { - case 'js': - return CONTENT_MODEL_JAVASCRIPT; - case 'css': - return CONTENT_MODEL_CSS; - default: - return is_null( $model ) ? CONTENT_MODEL_TEXT : $model; - } - } - - // We established that it must be wikitext - - return CONTENT_MODEL_WIKITEXT; - } - - /** - * Returns the appropriate ContentHandler singleton for the given title. - * - * @since WD.1 - * - * @static - * @param $title Title - * @return ContentHandler - */ - public static function getForTitle( Title $title ) { - $modelId = $title->getContentModel(); - return ContentHandler::getForModelID( $modelId ); - } - - /** - * Returns the appropriate ContentHandler singleton for the given Content - * object. - * - * @since WD.1 - * - * @static - * @param $content Content - * @return ContentHandler - */ - public static function getForContent( Content $content ) { - $modelId = $content->getModel(); - return ContentHandler::getForModelID( $modelId ); - } - - /** - * @var Array A Cache of ContentHandler instances by model id - */ - static $handlers; - - /** - * Returns the ContentHandler singleton for the given model ID. Use the - * CONTENT_MODEL_XXX constants to identify the desired content model. - * - * ContentHandler singletons are taken from the global $wgContentHandlers - * array. Keys in that array are model names, the values are either - * ContentHandler singleton objects, or strings specifying the appropriate - * subclass of ContentHandler. - * - * If a class name is encountered when looking up the singleton for a given - * model name, the class is instantiated and the class name is replaced by - * the resulting singleton in $wgContentHandlers. - * - * If no ContentHandler is defined for the desired $modelId, the - * ContentHandler may be provided by the ContentHandlerForModelID hook. - * If no ContentHandler can be determined, an MWException is raised. - * - * @since WD.1 - * - * @static - * @param $modelId String The ID of the content model for which to get a - * handler. Use CONTENT_MODEL_XXX constants. - * @return ContentHandler The ContentHandler singleton for handling the - * model given by $modelId - * @throws MWException if no handler is known for $modelId. - */ - public static function getForModelID( $modelId ) { - global $wgContentHandlers; - - if ( isset( ContentHandler::$handlers[$modelId] ) ) { - return ContentHandler::$handlers[$modelId]; - } - - if ( empty( $wgContentHandlers[$modelId] ) ) { - $handler = null; - - wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) ); - - if ( $handler === null ) { - throw new MWException( "No handler for model #$modelId registered in \$wgContentHandlers" ); - } - - if ( !( $handler instanceof ContentHandler ) ) { - throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" ); - } - } else { - $class = $wgContentHandlers[$modelId]; - $handler = new $class( $modelId ); - - if ( !( $handler instanceof ContentHandler ) ) { - throw new MWException( "$class from \$wgContentHandlers is not compatible with ContentHandler" ); - } - } - - ContentHandler::$handlers[$modelId] = $handler; - return ContentHandler::$handlers[$modelId]; - } - - /** - * Returns the localized name for a given content model. - * - * Model names are localized using system messages. Message keys - * have the form content-model-$name, where $name is getContentModelName( $id ). - * - * @static - * @param $name String The content model ID, as given by a CONTENT_MODEL_XXX - * constant or returned by Revision::getContentModel(). - * - * @return string The content format's localized name. - * @throws MWException if the model id isn't known. - */ - public static function getLocalizedName( $name ) { - $key = "content-model-$name"; - - if ( wfEmptyMsg( $key ) ) return $name; - else return wfMsg( $key ); - } - - public static function getContentModels() { - global $wgContentHandlers; - - return array_keys( $wgContentHandlers ); - } - - public static function getAllContentFormats() { - global $wgContentHandlers; - - $formats = array(); - - foreach ( $wgContentHandlers as $model => $class ) { - $handler = ContentHandler::getForModelID( $model ); - $formats = array_merge( $formats, $handler->getSupportedFormats() ); - } - - $formats = array_unique( $formats ); - return $formats; - } - - // ------------------------------------------------------------------------ - - protected $mModelID; - protected $mSupportedFormats; - - /** - * Constructor, initializing the ContentHandler instance with its model ID - * and a list of supported formats. Values for the parameters are typically - * provided as literals by subclass's constructors. - * - * @param $modelId String (use CONTENT_MODEL_XXX constants). - * @param $formats array List for supported serialization formats - * (typically as MIME types) - */ - public function __construct( $modelId, $formats ) { - $this->mModelID = $modelId; - $this->mSupportedFormats = $formats; - - $this->mModelName = preg_replace( '/(Content)?Handler$/', '', get_class( $this ) ); - $this->mModelName = preg_replace( '/[_\\\\]/', '', $this->mModelName ); - $this->mModelName = strtolower( $this->mModelName ); - } - - /** - * Serializes a Content object of the type supported by this ContentHandler. - * - * @since WD.1 - * - * @abstract - * @param $content Content The Content object to serialize - * @param $format null|String The desired serialization format - * @return string Serialized form of the content - */ - public abstract function serializeContent( Content $content, $format = null ); - - /** - * Unserializes a Content object of the type supported by this ContentHandler. - * - * @since WD.1 - * - * @abstract - * @param $blob string serialized form of the content - * @param $format null|String the format used for serialization - * @return Content the Content object created by deserializing $blob - */ - public abstract function unserializeContent( $blob, $format = null ); - - /** - * Creates an empty Content object of the type supported by this - * ContentHandler. - * - * @since WD.1 - * - * @return Content - */ - public abstract function makeEmptyContent(); - - /** - * Returns the model id that identifies the content model this - * ContentHandler can handle. Use with the CONTENT_MODEL_XXX constants. - * - * @since WD.1 - * - * @return String The model ID - */ - public function getModelID() { - return $this->mModelID; - } - - /** - * Throws an MWException if $model_id is not the ID of the content model - * supported by this ContentHandler. - * - * @since WD.1 - * - * @param String $model_id The model to check - * - * @throws MWException - */ - protected function checkModelID( $model_id ) { - if ( $model_id !== $this->mModelID ) { - throw new MWException( "Bad content model: " . - "expected {$this->mModelID} " . - "but got $model_id." ); - } - } - - /** - * Returns a list of serialization formats supported by the - * serializeContent() and unserializeContent() methods of this - * ContentHandler. - * - * @since WD.1 - * - * @return array of serialization formats as MIME type like strings - */ - public function getSupportedFormats() { - return $this->mSupportedFormats; - } - - /** - * The format used for serialization/deserialization by default by this - * ContentHandler. - * - * This default implementation will return the first element of the array - * of formats that was passed to the constructor. - * - * @since WD.1 - * - * @return string the name of the default serialization format as a MIME type - */ - public function getDefaultFormat() { - return $this->mSupportedFormats[0]; - } - - /** - * Returns true if $format is a serialization format supported by this - * ContentHandler, and false otherwise. - * - * Note that if $format is null, this method always returns true, because - * null means "use the default format". - * - * @since WD.1 - * - * @param $format string the serialization format to check - * @return bool - */ - public function isSupportedFormat( $format ) { - - if ( !$format ) { - return true; // this means "use the default" - } - - return in_array( $format, $this->mSupportedFormats ); - } - - /** - * Throws an MWException if isSupportedFormat( $format ) is not true. - * Convenient for checking whether a format provided as a parameter is - * actually supported. - * - * @param $format string the serialization format to check - * - * @throws MWException - */ - protected function checkFormat( $format ) { - if ( !$this->isSupportedFormat( $format ) ) { - throw new MWException( - "Format $format is not supported for content model " - . $this->getModelID() - ); - } - } - - /** - * Returns overrides for action handlers. - * Classes listed here will be used instead of the default one when - * (and only when) $wgActions[$action] === true. This allows subclasses - * to override the default action handlers. - * - * @since WD.1 - * - * @return Array - */ - public function getActionOverrides() { - return array(); - } - - /** - * Factory for creating an appropriate DifferenceEngine for this content model. - * - * @since WD.1 - * - * @param $context IContextSource context to use, anything else will be - * ignored - * @param $old Integer Old ID we want to show and diff with. - * @param $new int|string String either 'prev' or 'next'. - * @param $rcid Integer ??? FIXME (default 0) - * @param $refreshCache boolean If set, refreshes the diff cache - * @param $unhide boolean If set, allow viewing deleted revs - * - * @return DifferenceEngine - */ - public function createDifferenceEngine( IContextSource $context, - $old = 0, $new = 0, - $rcid = 0, # FIXME: use everywhere! - $refreshCache = false, $unhide = false - ) { - $this->checkModelID( $context->getTitle()->getContentModel() ); - - $diffEngineClass = $this->getDiffEngineClass(); - - return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide ); - } - - /** - * Get the language in which the content of the given page is written. - * - * This default implementation just returns $wgContLang (except for pages in the MediaWiki namespace) - * - * Note that the pages language is not cacheable, since it may in some cases depend on user settings. - * - * Also note that the page language may or may not depend on the actual content of the page, - * that is, this method may load the content in order to determine the language. - * - * @since 1.WD - * - * @param Title $title the page to determine the language for. - * @param Content|null $content the page's content, if you have it handy, to avoid reloading it. - * - * @return Language the page's language - */ - public function getPageLanguage( Title $title, Content $content = null ) { - global $wgContLang; - - if ( $title->getNamespace() == NS_MEDIAWIKI ) { - // Parse mediawiki messages with correct target language - list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $title->getText() ); - return wfGetLangObj( $lang ); - } - - return $wgContLang; - } - - /** - * Get the language in which the content of this page is written when - * viewed by user. Defaults to $this->getPageLanguage(), but if the user - * specified a preferred variant, the variant will be used. - * - * This default implementation just returns $this->getPageLanguage( $title, $content ) unless - * the user specified a preferred variant. - * - * Note that the pages view language is not cacheable, since it depends on user settings. - * - * Also note that the page language may or may not depend on the actual content of the page, - * that is, this method may load the content in order to determine the language. - * - * @since 1.WD - * - * @param Title $title the page to determine the language for. - * @param Content|null $content the page's content, if you have it handy, to avoid reloading it. - * - * @return Language the page's language for viewing - */ - public function getPageViewLanguage( Title $title, Content $content = null ) { - $pageLang = $this->getPageLanguage( $title, $content ); - - if ( $title->getNamespace() !== NS_MEDIAWIKI ) { - // If the user chooses a variant, the content is actually - // in a language whose code is the variant code. - $variant = $pageLang->getPreferredVariant(); - if ( $pageLang->getCode() !== $variant ) { - $pageLang = Language::factory( $variant ); - } - } - - return $pageLang; - } - - /** - * Determines whether the content type handled by this ContentHandler - * can be used on the given page. - * - * This default implementation always returns true. - * Subclasses may override this to restrict the use of this content model to specific locations, - * typically based on the namespace or some other aspect of the title, such as a special suffix - * (e.g. ".svg" for SVG content). - * - * @param Title $title the page's title. - * - * @return bool true if content of this kind can be used on the given page, false otherwise. - */ - public function canBeUsedOn( Title $title ) { - return true; - } - - /** - * Returns the name of the diff engine to use. - * - * @since WD.1 - * - * @return string - */ - protected function getDiffEngineClass() { - return 'DifferenceEngine'; - } - - /** - * Attempts to merge differences between three versions. - * Returns a new Content object for a clean merge and false for failure or - * a conflict. - * - * This default implementation always returns false. - * - * @since WD.1 - * - * @param $oldContent Content|string String - * @param $myContent Content|string String - * @param $yourContent Content|string String - * - * @return Content|Bool - */ - public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) { - return false; - } - - /** - * Return an applicable auto-summary if one exists for the given edit. - * - * @since WD.1 - * - * @param $oldContent Content|null: the previous text of the page. - * @param $newContent Content|null: The submitted text of the page. - * @param $flags int Bit mask: a bit mask of flags submitted for the edit. - * - * @return string An appropriate auto-summary, or an empty string. - */ - public function getAutosummary( Content $oldContent = null, Content $newContent = null, $flags ) { - global $wgContLang; - - // Decide what kind of auto-summary is needed. - - // Redirect auto-summaries - - /** - * @var $ot Title - * @var $rt Title - */ - - $ot = !is_null( $oldContent ) ? $oldContent->getRedirectTarget() : null; - $rt = !is_null( $newContent ) ? $newContent->getRedirectTarget() : null; - - if ( is_object( $rt ) ) { - if ( !is_object( $ot ) - || !$rt->equals( $ot ) - || $ot->getFragment() != $rt->getFragment() ) - { - $truncatedtext = $newContent->getTextForSummary( - 250 - - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() ) - - strlen( $rt->getFullText() ) ); - - return wfMessage( 'autoredircomment', $rt->getFullText() ) - ->rawParams( $truncatedtext )->inContentLanguage()->text(); - } - } - - // New page auto-summaries - if ( $flags & EDIT_NEW && $newContent->getSize() > 0 ) { - // If they're making a new article, give its text, truncated, in - // the summary. - - $truncatedtext = $newContent->getTextForSummary( - 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ); - - return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext ) - ->inContentLanguage()->text(); - } - - // Blanking auto-summaries - if ( !empty( $oldContent ) && $oldContent->getSize() > 0 && $newContent->getSize() == 0 ) { - return wfMessage( 'autosumm-blank' )->inContentLanguage()->text(); - } elseif ( !empty( $oldContent ) - && $oldContent->getSize() > 10 * $newContent->getSize() - && $newContent->getSize() < 500 ) - { - // Removing more than 90% of the article - - $truncatedtext = $newContent->getTextForSummary( - 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) ); - - return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext ) - ->inContentLanguage()->text(); - } - - // If we reach this point, there's no applicable auto-summary for our - // case, so our auto-summary is empty. - return ''; - } - - /** - * Auto-generates a deletion reason - * - * @since WD.1 - * - * @param $title Title: the page's title - * @param &$hasHistory Boolean: whether the page has a history - * @return mixed String containing deletion reason or empty string, or - * boolean false if no revision occurred - * - * @XXX &$hasHistory is extremely ugly, it's here because - * WikiPage::getAutoDeleteReason() and Article::getReason() - * have it / want it. - */ - public function getAutoDeleteReason( Title $title, &$hasHistory ) { - $dbw = wfGetDB( DB_MASTER ); - - // Get the last revision - $rev = Revision::newFromTitle( $title ); - - if ( is_null( $rev ) ) { - return false; - } - - // Get the article's contents - $content = $rev->getContent(); - $blank = false; - - $this->checkModelID( $content->getModel() ); - - // If the page is blank, use the text from the previous revision, - // which can only be blank if there's a move/import/protect dummy - // revision involved - if ( $content->getSize() == 0 ) { - $prev = $rev->getPrevious(); - - if ( $prev ) { - $content = $prev->getContent(); - $blank = true; - } - } - - // Find out if there was only one contributor - // Only scan the last 20 revisions - $res = $dbw->select( 'revision', 'rev_user_text', - array( - 'rev_page' => $title->getArticleID(), - $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' - ), - __METHOD__, - array( 'LIMIT' => 20 ) - ); - - if ( $res === false ) { - // This page has no revisions, which is very weird - return false; - } - - $hasHistory = ( $res->numRows() > 1 ); - $row = $dbw->fetchObject( $res ); - - if ( $row ) { // $row is false if the only contributor is hidden - $onlyAuthor = $row->rev_user_text; - // Try to find a second contributor - foreach ( $res as $row ) { - if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999 - $onlyAuthor = false; - break; - } - } - } else { - $onlyAuthor = false; - } - - // Generate the summary with a '$1' placeholder - if ( $blank ) { - // The current revision is blank and the one before is also - // blank. It's just not our lucky day - $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text(); - } else { - if ( $onlyAuthor ) { - $reason = wfMessage( - 'excontentauthor', - '$1', - $onlyAuthor - )->inContentLanguage()->text(); - } else { - $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text(); - } - } - - if ( $reason == '-' ) { - // Allow these UI messages to be blanked out cleanly - return ''; - } - - // Max content length = max comment length - length of the comment (excl. $1) - $text = $content->getTextForSummary( 255 - ( strlen( $reason ) - 2 ) ); - - // Now replace the '$1' placeholder - $reason = str_replace( '$1', $text, $reason ); - - return $reason; - } - - /** - * Get the Content object that needs to be saved in order to undo all revisions - * between $undo and $undoafter. Revisions must belong to the same page, - * must exist and must not be deleted. - * - * @since WD.1 - * - * @param $current Revision The current text - * @param $undo Revision The revision to undo - * @param $undoafter Revision Must be an earlier revision than $undo - * - * @return mixed String on success, false on failure - */ - public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) { - $cur_content = $current->getContent(); - - if ( empty( $cur_content ) ) { - return false; // no page - } - - $undo_content = $undo->getContent(); - $undoafter_content = $undoafter->getContent(); - - $this->checkModelID( $cur_content->getModel() ); - $this->checkModelID( $undo_content->getModel() ); - $this->checkModelID( $undoafter_content->getModel() ); - - if ( $cur_content->equals( $undo_content ) ) { - // No use doing a merge if it's just a straight revert. - return $undoafter_content; - } - - $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content ); - - return $undone_content; - } - - /** - * Returns true for content models that support caching using the - * ParserCache mechanism. See WikiPage::isParserCacheUser(). - * - * @since WD.1 - * - * @return bool - */ - public function isParserCacheSupported() { - return true; - } - - /** - * Returns true if this content model supports sections. - * - * This default implementation returns false. - * - * @return boolean whether sections are supported. - */ - public function supportsSections() { - return false; - } - - /** - * Call a legacy hook that uses text instead of Content objects. - * Will log a warning when a matching hook function is registered. - * If the textual representation of the content is changed by the - * hook function, a new Content object is constructed from the new - * text. - * - * @param $event String: event name - * @param $args Array: parameters passed to hook functions - * @param $warn bool: whether to log a warning (default: true). Should generally be true, - * may be set to false for testing. - * - * @return Boolean True if no handler aborted the hook - */ - public static function runLegacyHooks( $event, $args = array(), $warn = true ) { - if ( !Hooks::isRegistered( $event ) ) { - return true; // nothing to do here - } - - if ( $warn ) { - wfWarn( "Using obsolete hook $event" ); - } - - // convert Content objects to text - $contentObjects = array(); - $contentTexts = array(); - - foreach ( $args as $k => $v ) { - if ( $v instanceof Content ) { - /* @var Content $v */ - - $contentObjects[$k] = $v; - - $v = $v->serialize(); - $contentTexts[ $k ] = $v; - $args[ $k ] = $v; - } - } - - // call the hook functions - $ok = wfRunHooks( $event, $args ); - - // see if the hook changed the text - foreach ( $contentTexts as $k => $orig ) { - /* @var Content $content */ - - $modified = $args[ $k ]; - $content = $contentObjects[$k]; - - if ( $modified !== $orig ) { - // text was changed, create updated Content object - $content = $content->getContentHandler()->unserializeContent( $modified ); - } - - $args[ $k ] = $content; - } - - return $ok; - } -} - -/** - * @since WD.1 - */ -abstract class TextContentHandler extends ContentHandler { - - public function __construct( $modelId, $formats ) { - parent::__construct( $modelId, $formats ); - } - - /** - * Returns the content's text as-is. - * - * @param $content Content - * @param $format string|null - * @return mixed - */ - public function serializeContent( Content $content, $format = null ) { - $this->checkFormat( $format ); - return $content->getNativeData(); - } - - /** - * Attempts to merge differences between three versions. Returns a new - * Content object for a clean merge and false for failure or a conflict. - * - * All three Content objects passed as parameters must have the same - * content model. - * - * This text-based implementation uses wfMerge(). - * - * @param $oldContent \Content|string String - * @param $myContent \Content|string String - * @param $yourContent \Content|string String - * - * @return Content|Bool - */ - public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) { - $this->checkModelID( $oldContent->getModel() ); - $this->checkModelID( $myContent->getModel() ); - $this->checkModelID( $yourContent->getModel() ); - - $format = $this->getDefaultFormat(); - - $old = $this->serializeContent( $oldContent, $format ); - $mine = $this->serializeContent( $myContent, $format ); - $yours = $this->serializeContent( $yourContent, $format ); - - $ok = wfMerge( $old, $mine, $yours, $result ); - - if ( !$ok ) { - return false; - } - - if ( !$result ) { - return $this->makeEmptyContent(); - } - - $mergedContent = $this->unserializeContent( $result, $format ); - return $mergedContent; - } - -} - -/** - * @since WD.1 - */ -class WikitextContentHandler extends TextContentHandler { - - public function __construct( $modelId = CONTENT_MODEL_WIKITEXT ) { - parent::__construct( $modelId, array( CONTENT_FORMAT_WIKITEXT ) ); - } - - public function unserializeContent( $text, $format = null ) { - $this->checkFormat( $format ); - - return new WikitextContent( $text ); - } - - public function makeEmptyContent() { - return new WikitextContent( '' ); - } - - /** - * Returns true because wikitext supports sections. - * - * @return boolean whether sections are supported. - */ - public function supportsSections() { - return true; - } -} - -# XXX: make ScriptContentHandler base class, do highlighting stuff there? - -/** - * @since WD.1 - */ -class JavaScriptContentHandler extends TextContentHandler { - - public function __construct( $modelId = CONTENT_MODEL_JAVASCRIPT ) { - parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) ); - } - - public function unserializeContent( $text, $format = null ) { - $this->checkFormat( $format ); - - return new JavaScriptContent( $text ); - } - - public function makeEmptyContent() { - return new JavaScriptContent( '' ); - } - - /** - * Returns the english language, because JS is english, and should be handled as such. - * - * @return Language wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageLanguage() - */ - public function getPageLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); - } - - /** - * Returns the english language, because CSS is english, and should be handled as such. - * - * @return Language wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageViewLanguage() - */ - public function getPageViewLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); - } -} - -/** - * @since WD.1 - */ -class CssContentHandler extends TextContentHandler { - - public function __construct( $modelId = CONTENT_MODEL_CSS ) { - parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) ); - } - - public function unserializeContent( $text, $format = null ) { - $this->checkFormat( $format ); - - return new CssContent( $text ); - } - - public function makeEmptyContent() { - return new CssContent( '' ); - } - - /** - * Returns the english language, because CSS is english, and should be handled as such. - * - * @return Language wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageLanguage() - */ - public function getPageLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); - } - - /** - * Returns the english language, because CSS is english, and should be handled as such. - * - * @return Language wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageViewLanguage() - */ - public function getPageViewLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); - } -} diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php new file mode 100644 index 0000000000..c65f11c389 --- /dev/null +++ b/includes/content/AbstractContent.php @@ -0,0 +1,326 @@ +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; + } +} \ No newline at end of file diff --git a/includes/content/Content.php b/includes/content/Content.php new file mode 100644 index 0000000000..323b40a790 --- /dev/null +++ b/includes/content/Content.php @@ -0,0 +1,468 @@ +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: ImagePage and CategoryPage interfere with per-content action handlers + # TODO: make sure WikiSearch extension still works + # TODO: make sure ReplaceTemplates extension 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 +} \ No newline at end of file diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php new file mode 100644 index 0000000000..bf1349adc8 --- /dev/null +++ b/includes/content/ContentHandler.php @@ -0,0 +1,1136 @@ +getNativeData(). + * + * If $content is not a TextContent object, the behavior of this method + * depends on the global $wgContentHandlerTextFallback: + * - If $wgContentHandlerTextFallback is 'fail' and $content is not a + * TextContent object, an MWException is thrown. + * - If $wgContentHandlerTextFallback is 'serialize' and $content is not a + * TextContent object, $content->serialize() is called to get a string + * form of the content. + * - If $wgContentHandlerTextFallback is 'ignore' and $content is not a + * TextContent object, this method returns null. + * - otherwise, the behaviour is undefined. + * + * @since WD.1 + * @deprecated since WD.1. Always try to use the content object. + * + * @static + * @param $content Content|null + * @return null|string the textual form of $content, if available + * @throws MWException if $content is not an instance of TextContent and + * $wgContentHandlerTextFallback was set to 'fail'. + */ + public static function getContentText( Content $content = null ) { + global $wgContentHandlerTextFallback; + + if ( is_null( $content ) ) { + return ''; + } + + if ( $content instanceof TextContent ) { + return $content->getNativeData(); + } + + if ( $wgContentHandlerTextFallback == 'fail' ) { + throw new MWException( + "Attempt to get text from Content with model " . + $content->getModel() + ); + } + + if ( $wgContentHandlerTextFallback == 'serialize' ) { + return $content->serialize(); + } + + return null; + } + + /** + * Convenience function for creating a Content object from a given textual + * representation. + * + * $text will be deserialized into a Content object of the model specified + * by $modelId (or, if that is not given, $title->getContentModel()) using + * the given format. + * + * @since WD.1 + * + * @static + * + * @param $text string the textual representation, will be + * unserialized to create the Content object + * @param $title null|Title the title of the page this text belongs to. + * Required if $modelId is not provided. + * @param $modelId null|string the model to deserialize to. If not provided, + * $title->getContentModel() is used. + * @param $format null|string the format to use for deserialization. If not + * given, the model's default format is used. + * + * @return Content a Content object representing $text + * + * @throw MWException if $model or $format is not supported or if $text can + * not be unserialized using $format. + */ + public static function makeContent( $text, Title $title = null, + $modelId = null, $format = null ) + { + if ( is_null( $modelId ) ) { + if ( is_null( $title ) ) { + throw new MWException( "Must provide a Title object or a content model ID." ); + } + + $modelId = $title->getContentModel(); + } + + $handler = ContentHandler::getForModelID( $modelId ); + return $handler->unserializeContent( $text, $format ); + } + + /** + * Returns the name of the default content model to be used for the page + * with the given title. + * + * Note: There should rarely be need to call this method directly. + * To determine the actual content model for a given page, use + * Title::getContentModel(). + * + * Which model is to be used by default for the page is determined based + * on several factors: + * - The global setting $wgNamespaceContentModels specifies a content model + * per namespace. + * - The hook DefaultModelFor may be used to override the page's default + * model. + * - Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript + * model if they end in .js or .css, respectively. + * - Pages in NS_MEDIAWIKI default to the wikitext model otherwise. + * - The hook TitleIsCssOrJsPage may be used to force a page to use the CSS + * or JavaScript model if they end in .js or .css, respectively. + * - The hook TitleIsWikitextPage may be used to force a page to use the + * wikitext model. + * + * If none of the above applies, the wikitext model is used. + * + * Note: this is used by, and may thus not use, Title::getContentModel() + * + * @since WD.1 + * + * @static + * @param $title Title + * @return null|string default model name for the page given by $title + */ + public static function getDefaultModelFor( Title $title ) { + global $wgNamespaceContentModels; + + // NOTE: this method must not rely on $title->getContentModel() directly or indirectly, + // because it is used to initialize the mContentModel member. + + $ns = $title->getNamespace(); + + $ext = false; + $m = null; + $model = null; + + if ( !empty( $wgNamespaceContentModels[ $ns ] ) ) { + $model = $wgNamespaceContentModels[ $ns ]; + } + + // Hook can determine default model + if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) { + if ( !is_null( $model ) ) { + return $model; + } + } + + // Could this page contain custom CSS or JavaScript, based on the title? + $isCssOrJsPage = NS_MEDIAWIKI == $ns && preg_match( '!\.(css|js)$!u', $title->getText(), $m ); + if ( $isCssOrJsPage ) { + $ext = $m[1]; + } + + // Hook can force JS/CSS + wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) ); + + // Is this a .css subpage of a user page? + $isJsCssSubpage = NS_USER == $ns + && !$isCssOrJsPage + && preg_match( "/\\/.*\\.(js|css)$/", $title->getText(), $m ); + if ( $isJsCssSubpage ) { + $ext = $m[1]; + } + + // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook? + $isWikitext = is_null( $model ) || $model == CONTENT_MODEL_WIKITEXT; + $isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage; + + // Hook can override $isWikitext + wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) ); + + if ( !$isWikitext ) { + switch ( $ext ) { + case 'js': + return CONTENT_MODEL_JAVASCRIPT; + case 'css': + return CONTENT_MODEL_CSS; + default: + return is_null( $model ) ? CONTENT_MODEL_TEXT : $model; + } + } + + // We established that it must be wikitext + + return CONTENT_MODEL_WIKITEXT; + } + + /** + * Returns the appropriate ContentHandler singleton for the given title. + * + * @since WD.1 + * + * @static + * @param $title Title + * @return ContentHandler + */ + public static function getForTitle( Title $title ) { + $modelId = $title->getContentModel(); + return ContentHandler::getForModelID( $modelId ); + } + + /** + * Returns the appropriate ContentHandler singleton for the given Content + * object. + * + * @since WD.1 + * + * @static + * @param $content Content + * @return ContentHandler + */ + public static function getForContent( Content $content ) { + $modelId = $content->getModel(); + return ContentHandler::getForModelID( $modelId ); + } + + /** + * @var Array A Cache of ContentHandler instances by model id + */ + static $handlers; + + /** + * Returns the ContentHandler singleton for the given model ID. Use the + * CONTENT_MODEL_XXX constants to identify the desired content model. + * + * ContentHandler singletons are taken from the global $wgContentHandlers + * array. Keys in that array are model names, the values are either + * ContentHandler singleton objects, or strings specifying the appropriate + * subclass of ContentHandler. + * + * If a class name is encountered when looking up the singleton for a given + * model name, the class is instantiated and the class name is replaced by + * the resulting singleton in $wgContentHandlers. + * + * If no ContentHandler is defined for the desired $modelId, the + * ContentHandler may be provided by the ContentHandlerForModelID hook. + * If no ContentHandler can be determined, an MWException is raised. + * + * @since WD.1 + * + * @static + * @param $modelId String The ID of the content model for which to get a + * handler. Use CONTENT_MODEL_XXX constants. + * @return ContentHandler The ContentHandler singleton for handling the + * model given by $modelId + * @throws MWException if no handler is known for $modelId. + */ + public static function getForModelID( $modelId ) { + global $wgContentHandlers; + + if ( isset( ContentHandler::$handlers[$modelId] ) ) { + return ContentHandler::$handlers[$modelId]; + } + + if ( empty( $wgContentHandlers[$modelId] ) ) { + $handler = null; + + wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) ); + + if ( $handler === null ) { + throw new MWException( "No handler for model #$modelId registered in \$wgContentHandlers" ); + } + + if ( !( $handler instanceof ContentHandler ) ) { + throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" ); + } + } else { + $class = $wgContentHandlers[$modelId]; + $handler = new $class( $modelId ); + + if ( !( $handler instanceof ContentHandler ) ) { + throw new MWException( "$class from \$wgContentHandlers is not compatible with ContentHandler" ); + } + } + + ContentHandler::$handlers[$modelId] = $handler; + return ContentHandler::$handlers[$modelId]; + } + + /** + * Returns the localized name for a given content model. + * + * Model names are localized using system messages. Message keys + * have the form content-model-$name, where $name is getContentModelName( $id ). + * + * @static + * @param $name String The content model ID, as given by a CONTENT_MODEL_XXX + * constant or returned by Revision::getContentModel(). + * + * @return string The content format's localized name. + * @throws MWException if the model id isn't known. + */ + public static function getLocalizedName( $name ) { + $key = "content-model-$name"; + + if ( wfEmptyMsg( $key ) ) return $name; + else return wfMsg( $key ); + } + + public static function getContentModels() { + global $wgContentHandlers; + + return array_keys( $wgContentHandlers ); + } + + public static function getAllContentFormats() { + global $wgContentHandlers; + + $formats = array(); + + foreach ( $wgContentHandlers as $model => $class ) { + $handler = ContentHandler::getForModelID( $model ); + $formats = array_merge( $formats, $handler->getSupportedFormats() ); + } + + $formats = array_unique( $formats ); + return $formats; + } + + // ------------------------------------------------------------------------ + + protected $mModelID; + protected $mSupportedFormats; + + /** + * Constructor, initializing the ContentHandler instance with its model ID + * and a list of supported formats. Values for the parameters are typically + * provided as literals by subclass's constructors. + * + * @param $modelId String (use CONTENT_MODEL_XXX constants). + * @param $formats array List for supported serialization formats + * (typically as MIME types) + */ + public function __construct( $modelId, $formats ) { + $this->mModelID = $modelId; + $this->mSupportedFormats = $formats; + + $this->mModelName = preg_replace( '/(Content)?Handler$/', '', get_class( $this ) ); + $this->mModelName = preg_replace( '/[_\\\\]/', '', $this->mModelName ); + $this->mModelName = strtolower( $this->mModelName ); + } + + /** + * Serializes a Content object of the type supported by this ContentHandler. + * + * @since WD.1 + * + * @abstract + * @param $content Content The Content object to serialize + * @param $format null|String The desired serialization format + * @return string Serialized form of the content + */ + public abstract function serializeContent( Content $content, $format = null ); + + /** + * Unserializes a Content object of the type supported by this ContentHandler. + * + * @since WD.1 + * + * @abstract + * @param $blob string serialized form of the content + * @param $format null|String the format used for serialization + * @return Content the Content object created by deserializing $blob + */ + public abstract function unserializeContent( $blob, $format = null ); + + /** + * Creates an empty Content object of the type supported by this + * ContentHandler. + * + * @since WD.1 + * + * @return Content + */ + public abstract function makeEmptyContent(); + + /** + * Returns the model id that identifies the content model this + * ContentHandler can handle. Use with the CONTENT_MODEL_XXX constants. + * + * @since WD.1 + * + * @return String The model ID + */ + public function getModelID() { + return $this->mModelID; + } + + /** + * Throws an MWException if $model_id is not the ID of the content model + * supported by this ContentHandler. + * + * @since WD.1 + * + * @param String $model_id The model to check + * + * @throws MWException + */ + protected function checkModelID( $model_id ) { + if ( $model_id !== $this->mModelID ) { + throw new MWException( "Bad content model: " . + "expected {$this->mModelID} " . + "but got $model_id." ); + } + } + + /** + * Returns a list of serialization formats supported by the + * serializeContent() and unserializeContent() methods of this + * ContentHandler. + * + * @since WD.1 + * + * @return array of serialization formats as MIME type like strings + */ + public function getSupportedFormats() { + return $this->mSupportedFormats; + } + + /** + * The format used for serialization/deserialization by default by this + * ContentHandler. + * + * This default implementation will return the first element of the array + * of formats that was passed to the constructor. + * + * @since WD.1 + * + * @return string the name of the default serialization format as a MIME type + */ + public function getDefaultFormat() { + return $this->mSupportedFormats[0]; + } + + /** + * Returns true if $format is a serialization format supported by this + * ContentHandler, and false otherwise. + * + * Note that if $format is null, this method always returns true, because + * null means "use the default format". + * + * @since WD.1 + * + * @param $format string the serialization format to check + * @return bool + */ + public function isSupportedFormat( $format ) { + + if ( !$format ) { + return true; // this means "use the default" + } + + return in_array( $format, $this->mSupportedFormats ); + } + + /** + * Throws an MWException if isSupportedFormat( $format ) is not true. + * Convenient for checking whether a format provided as a parameter is + * actually supported. + * + * @param $format string the serialization format to check + * + * @throws MWException + */ + protected function checkFormat( $format ) { + if ( !$this->isSupportedFormat( $format ) ) { + throw new MWException( + "Format $format is not supported for content model " + . $this->getModelID() + ); + } + } + + /** + * Returns overrides for action handlers. + * Classes listed here will be used instead of the default one when + * (and only when) $wgActions[$action] === true. This allows subclasses + * to override the default action handlers. + * + * @since WD.1 + * + * @return Array + */ + public function getActionOverrides() { + return array(); + } + + /** + * Factory for creating an appropriate DifferenceEngine for this content model. + * + * @since WD.1 + * + * @param $context IContextSource context to use, anything else will be + * ignored + * @param $old Integer Old ID we want to show and diff with. + * @param $new int|string String either 'prev' or 'next'. + * @param $rcid Integer ??? FIXME (default 0) + * @param $refreshCache boolean If set, refreshes the diff cache + * @param $unhide boolean If set, allow viewing deleted revs + * + * @return DifferenceEngine + */ + public function createDifferenceEngine( IContextSource $context, + $old = 0, $new = 0, + $rcid = 0, # FIXME: use everywhere! + $refreshCache = false, $unhide = false + ) { + $this->checkModelID( $context->getTitle()->getContentModel() ); + + $diffEngineClass = $this->getDiffEngineClass(); + + return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide ); + } + + /** + * Get the language in which the content of the given page is written. + * + * This default implementation just returns $wgContLang (except for pages in the MediaWiki namespace) + * + * Note that the pages language is not cacheable, since it may in some cases depend on user settings. + * + * Also note that the page language may or may not depend on the actual content of the page, + * that is, this method may load the content in order to determine the language. + * + * @since 1.WD + * + * @param Title $title the page to determine the language for. + * @param Content|null $content the page's content, if you have it handy, to avoid reloading it. + * + * @return Language the page's language + */ + public function getPageLanguage( Title $title, Content $content = null ) { + global $wgContLang; + + if ( $title->getNamespace() == NS_MEDIAWIKI ) { + // Parse mediawiki messages with correct target language + list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $title->getText() ); + return wfGetLangObj( $lang ); + } + + return $wgContLang; + } + + /** + * Get the language in which the content of this page is written when + * viewed by user. Defaults to $this->getPageLanguage(), but if the user + * specified a preferred variant, the variant will be used. + * + * This default implementation just returns $this->getPageLanguage( $title, $content ) unless + * the user specified a preferred variant. + * + * Note that the pages view language is not cacheable, since it depends on user settings. + * + * Also note that the page language may or may not depend on the actual content of the page, + * that is, this method may load the content in order to determine the language. + * + * @since 1.WD + * + * @param Title $title the page to determine the language for. + * @param Content|null $content the page's content, if you have it handy, to avoid reloading it. + * + * @return Language the page's language for viewing + */ + public function getPageViewLanguage( Title $title, Content $content = null ) { + $pageLang = $this->getPageLanguage( $title, $content ); + + if ( $title->getNamespace() !== NS_MEDIAWIKI ) { + // If the user chooses a variant, the content is actually + // in a language whose code is the variant code. + $variant = $pageLang->getPreferredVariant(); + if ( $pageLang->getCode() !== $variant ) { + $pageLang = Language::factory( $variant ); + } + } + + return $pageLang; + } + + /** + * Determines whether the content type handled by this ContentHandler + * can be used on the given page. + * + * This default implementation always returns true. + * Subclasses may override this to restrict the use of this content model to specific locations, + * typically based on the namespace or some other aspect of the title, such as a special suffix + * (e.g. ".svg" for SVG content). + * + * @param Title $title the page's title. + * + * @return bool true if content of this kind can be used on the given page, false otherwise. + */ + public function canBeUsedOn( Title $title ) { + return true; + } + + /** + * Returns the name of the diff engine to use. + * + * @since WD.1 + * + * @return string + */ + protected function getDiffEngineClass() { + return 'DifferenceEngine'; + } + + /** + * Attempts to merge differences between three versions. + * Returns a new Content object for a clean merge and false for failure or + * a conflict. + * + * This default implementation always returns false. + * + * @since WD.1 + * + * @param $oldContent Content|string String + * @param $myContent Content|string String + * @param $yourContent Content|string String + * + * @return Content|Bool + */ + public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) { + return false; + } + + /** + * Return an applicable auto-summary if one exists for the given edit. + * + * @since WD.1 + * + * @param $oldContent Content|null: the previous text of the page. + * @param $newContent Content|null: The submitted text of the page. + * @param $flags int Bit mask: a bit mask of flags submitted for the edit. + * + * @return string An appropriate auto-summary, or an empty string. + */ + public function getAutosummary( Content $oldContent = null, Content $newContent = null, $flags ) { + global $wgContLang; + + // Decide what kind of auto-summary is needed. + + // Redirect auto-summaries + + /** + * @var $ot Title + * @var $rt Title + */ + + $ot = !is_null( $oldContent ) ? $oldContent->getRedirectTarget() : null; + $rt = !is_null( $newContent ) ? $newContent->getRedirectTarget() : null; + + if ( is_object( $rt ) ) { + if ( !is_object( $ot ) + || !$rt->equals( $ot ) + || $ot->getFragment() != $rt->getFragment() ) + { + $truncatedtext = $newContent->getTextForSummary( + 250 + - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() ) + - strlen( $rt->getFullText() ) ); + + return wfMessage( 'autoredircomment', $rt->getFullText() ) + ->rawParams( $truncatedtext )->inContentLanguage()->text(); + } + } + + // New page auto-summaries + if ( $flags & EDIT_NEW && $newContent->getSize() > 0 ) { + // If they're making a new article, give its text, truncated, in + // the summary. + + $truncatedtext = $newContent->getTextForSummary( + 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ); + + return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext ) + ->inContentLanguage()->text(); + } + + // Blanking auto-summaries + if ( !empty( $oldContent ) && $oldContent->getSize() > 0 && $newContent->getSize() == 0 ) { + return wfMessage( 'autosumm-blank' )->inContentLanguage()->text(); + } elseif ( !empty( $oldContent ) + && $oldContent->getSize() > 10 * $newContent->getSize() + && $newContent->getSize() < 500 ) + { + // Removing more than 90% of the article + + $truncatedtext = $newContent->getTextForSummary( + 200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) ); + + return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext ) + ->inContentLanguage()->text(); + } + + // If we reach this point, there's no applicable auto-summary for our + // case, so our auto-summary is empty. + return ''; + } + + /** + * Auto-generates a deletion reason + * + * @since WD.1 + * + * @param $title Title: the page's title + * @param &$hasHistory Boolean: whether the page has a history + * @return mixed String containing deletion reason or empty string, or + * boolean false if no revision occurred + * + * @XXX &$hasHistory is extremely ugly, it's here because + * WikiPage::getAutoDeleteReason() and Article::getReason() + * have it / want it. + */ + public function getAutoDeleteReason( Title $title, &$hasHistory ) { + $dbw = wfGetDB( DB_MASTER ); + + // Get the last revision + $rev = Revision::newFromTitle( $title ); + + if ( is_null( $rev ) ) { + return false; + } + + // Get the article's contents + $content = $rev->getContent(); + $blank = false; + + $this->checkModelID( $content->getModel() ); + + // If the page is blank, use the text from the previous revision, + // which can only be blank if there's a move/import/protect dummy + // revision involved + if ( $content->getSize() == 0 ) { + $prev = $rev->getPrevious(); + + if ( $prev ) { + $content = $prev->getContent(); + $blank = true; + } + } + + // Find out if there was only one contributor + // Only scan the last 20 revisions + $res = $dbw->select( 'revision', 'rev_user_text', + array( + 'rev_page' => $title->getArticleID(), + $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' + ), + __METHOD__, + array( 'LIMIT' => 20 ) + ); + + if ( $res === false ) { + // This page has no revisions, which is very weird + return false; + } + + $hasHistory = ( $res->numRows() > 1 ); + $row = $dbw->fetchObject( $res ); + + if ( $row ) { // $row is false if the only contributor is hidden + $onlyAuthor = $row->rev_user_text; + // Try to find a second contributor + foreach ( $res as $row ) { + if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999 + $onlyAuthor = false; + break; + } + } + } else { + $onlyAuthor = false; + } + + // Generate the summary with a '$1' placeholder + if ( $blank ) { + // The current revision is blank and the one before is also + // blank. It's just not our lucky day + $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text(); + } else { + if ( $onlyAuthor ) { + $reason = wfMessage( + 'excontentauthor', + '$1', + $onlyAuthor + )->inContentLanguage()->text(); + } else { + $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text(); + } + } + + if ( $reason == '-' ) { + // Allow these UI messages to be blanked out cleanly + return ''; + } + + // Max content length = max comment length - length of the comment (excl. $1) + $text = $content->getTextForSummary( 255 - ( strlen( $reason ) - 2 ) ); + + // Now replace the '$1' placeholder + $reason = str_replace( '$1', $text, $reason ); + + return $reason; + } + + /** + * Get the Content object that needs to be saved in order to undo all revisions + * between $undo and $undoafter. Revisions must belong to the same page, + * must exist and must not be deleted. + * + * @since WD.1 + * + * @param $current Revision The current text + * @param $undo Revision The revision to undo + * @param $undoafter Revision Must be an earlier revision than $undo + * + * @return mixed String on success, false on failure + */ + public function getUndoContent( Revision $current, Revision $undo, Revision $undoafter ) { + $cur_content = $current->getContent(); + + if ( empty( $cur_content ) ) { + return false; // no page + } + + $undo_content = $undo->getContent(); + $undoafter_content = $undoafter->getContent(); + + $this->checkModelID( $cur_content->getModel() ); + $this->checkModelID( $undo_content->getModel() ); + $this->checkModelID( $undoafter_content->getModel() ); + + if ( $cur_content->equals( $undo_content ) ) { + // No use doing a merge if it's just a straight revert. + return $undoafter_content; + } + + $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content ); + + return $undone_content; + } + + /** + * Returns true for content models that support caching using the + * ParserCache mechanism. See WikiPage::isParserCacheUser(). + * + * @since WD.1 + * + * @return bool + */ + public function isParserCacheSupported() { + return true; + } + + /** + * Returns true if this content model supports sections. + * + * This default implementation returns false. + * + * @return boolean whether sections are supported. + */ + public function supportsSections() { + return false; + } + + /** + * Call a legacy hook that uses text instead of Content objects. + * Will log a warning when a matching hook function is registered. + * If the textual representation of the content is changed by the + * hook function, a new Content object is constructed from the new + * text. + * + * @param $event String: event name + * @param $args Array: parameters passed to hook functions + * @param $warn bool: whether to log a warning (default: true). Should generally be true, + * may be set to false for testing. + * + * @return Boolean True if no handler aborted the hook + */ + public static function runLegacyHooks( $event, $args = array(), $warn = true ) { + if ( !Hooks::isRegistered( $event ) ) { + return true; // nothing to do here + } + + if ( $warn ) { + wfWarn( "Using obsolete hook $event" ); + } + + // convert Content objects to text + $contentObjects = array(); + $contentTexts = array(); + + foreach ( $args as $k => $v ) { + if ( $v instanceof Content ) { + /* @var Content $v */ + + $contentObjects[$k] = $v; + + $v = $v->serialize(); + $contentTexts[ $k ] = $v; + $args[ $k ] = $v; + } + } + + // call the hook functions + $ok = wfRunHooks( $event, $args ); + + // see if the hook changed the text + foreach ( $contentTexts as $k => $orig ) { + /* @var Content $content */ + + $modified = $args[ $k ]; + $content = $contentObjects[$k]; + + if ( $modified !== $orig ) { + // text was changed, create updated Content object + $content = $content->getContentHandler()->unserializeContent( $modified ); + } + + $args[ $k ] = $content; + } + + return $ok; + } +} + +/** + * @since WD.1 + */ +abstract class TextContentHandler extends ContentHandler { + + public function __construct( $modelId, $formats ) { + parent::__construct( $modelId, $formats ); + } + + /** + * Returns the content's text as-is. + * + * @param $content Content + * @param $format string|null + * @return mixed + */ + public function serializeContent( Content $content, $format = null ) { + $this->checkFormat( $format ); + return $content->getNativeData(); + } + + /** + * Attempts to merge differences between three versions. Returns a new + * Content object for a clean merge and false for failure or a conflict. + * + * All three Content objects passed as parameters must have the same + * content model. + * + * This text-based implementation uses wfMerge(). + * + * @param $oldContent \Content|string String + * @param $myContent \Content|string String + * @param $yourContent \Content|string String + * + * @return Content|Bool + */ + public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) { + $this->checkModelID( $oldContent->getModel() ); + $this->checkModelID( $myContent->getModel() ); + $this->checkModelID( $yourContent->getModel() ); + + $format = $this->getDefaultFormat(); + + $old = $this->serializeContent( $oldContent, $format ); + $mine = $this->serializeContent( $myContent, $format ); + $yours = $this->serializeContent( $yourContent, $format ); + + $ok = wfMerge( $old, $mine, $yours, $result ); + + if ( !$ok ) { + return false; + } + + if ( !$result ) { + return $this->makeEmptyContent(); + } + + $mergedContent = $this->unserializeContent( $result, $format ); + return $mergedContent; + } + +} + +/** + * @since WD.1 + */ +class WikitextContentHandler extends TextContentHandler { + + public function __construct( $modelId = CONTENT_MODEL_WIKITEXT ) { + parent::__construct( $modelId, array( CONTENT_FORMAT_WIKITEXT ) ); + } + + public function unserializeContent( $text, $format = null ) { + $this->checkFormat( $format ); + + return new WikitextContent( $text ); + } + + public function makeEmptyContent() { + return new WikitextContent( '' ); + } + + /** + * Returns true because wikitext supports sections. + * + * @return boolean whether sections are supported. + */ + public function supportsSections() { + return true; + } +} + +# XXX: make ScriptContentHandler base class, do highlighting stuff there? + +/** + * @since WD.1 + */ +class JavaScriptContentHandler extends TextContentHandler { + + public function __construct( $modelId = CONTENT_MODEL_JAVASCRIPT ) { + parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) ); + } + + public function unserializeContent( $text, $format = null ) { + $this->checkFormat( $format ); + + return new JavaScriptContent( $text ); + } + + public function makeEmptyContent() { + return new JavaScriptContent( '' ); + } + + /** + * Returns the english language, because JS is english, and should be handled as such. + * + * @return Language wfGetLangObj( 'en' ) + * + * @see ContentHandler::getPageLanguage() + */ + public function getPageLanguage( Title $title, Content $content = null ) { + return wfGetLangObj( 'en' ); + } + + /** + * Returns the english language, because CSS is english, and should be handled as such. + * + * @return Language wfGetLangObj( 'en' ) + * + * @see ContentHandler::getPageViewLanguage() + */ + public function getPageViewLanguage( Title $title, Content $content = null ) { + return wfGetLangObj( 'en' ); + } +} + +/** + * @since WD.1 + */ +class CssContentHandler extends TextContentHandler { + + public function __construct( $modelId = CONTENT_MODEL_CSS ) { + parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) ); + } + + public function unserializeContent( $text, $format = null ) { + $this->checkFormat( $format ); + + return new CssContent( $text ); + } + + public function makeEmptyContent() { + return new CssContent( '' ); + } + + /** + * Returns the english language, because CSS is english, and should be handled as such. + * + * @return Language wfGetLangObj( 'en' ) + * + * @see ContentHandler::getPageLanguage() + */ + public function getPageLanguage( Title $title, Content $content = null ) { + return wfGetLangObj( 'en' ); + } + + /** + * Returns the english language, because CSS is english, and should be handled as such. + * + * @return Language wfGetLangObj( 'en' ) + * + * @see ContentHandler::getPageViewLanguage() + */ + public function getPageViewLanguage( Title $title, Content $content = null ) { + return wfGetLangObj( 'en' ); + } +} diff --git a/includes/content/CssContent.php b/includes/content/CssContent.php new file mode 100644 index 0000000000..799600271f --- /dev/null +++ b/includes/content/CssContent.php @@ -0,0 +1,38 @@ +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; + } +} diff --git a/includes/content/JavaScriptContent.php b/includes/content/JavaScriptContent.php new file mode 100644 index 0000000000..f1df40c8a6 --- /dev/null +++ b/includes/content/JavaScriptContent.php @@ -0,0 +1,40 @@ +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; + } +} \ No newline at end of file diff --git a/includes/content/MessageContent.php b/includes/content/MessageContent.php new file mode 100644 index 0000000000..4ece253080 --- /dev/null +++ b/includes/content/MessageContent.php @@ -0,0 +1,132 @@ +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; + } +} \ No newline at end of file diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php new file mode 100644 index 0000000000..135adf570d --- /dev/null +++ b/includes/content/TextContent.php @@ -0,0 +1,180 @@ +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() ); + } +} \ No newline at end of file diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php new file mode 100644 index 0000000000..8e72889917 --- /dev/null +++ b/includes/content/WikitextContent.php @@ -0,0 +1,289 @@ +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() ); + } +} \ No newline at end of file