From c1f9b1f7b1b77776192048005dcc66dcf3df2bfb Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 27 Dec 2014 15:41:37 +0100 Subject: Update to MediaWiki 1.24.1 --- includes/content/AbstractContent.php | 277 +++++++++++++++--------- includes/content/CodeContentHandler.php | 65 ++++++ includes/content/Content.php | 163 +++++++------- includes/content/ContentHandler.php | 298 +++++++++++++++----------- includes/content/CssContent.php | 28 ++- includes/content/CssContentHandler.php | 37 +--- includes/content/JavaScriptContent.php | 20 +- includes/content/JavaScriptContentHandler.php | 37 +--- includes/content/JsonContent.php | 120 +++++++++++ includes/content/JsonContentHandler.php | 26 +++ includes/content/MessageContent.php | 54 +++-- includes/content/TextContent.php | 137 +++++++----- includes/content/TextContentHandler.php | 48 +++-- includes/content/WikitextContent.php | 175 +++++++++------ includes/content/WikitextContentHandler.php | 44 ++-- 15 files changed, 987 insertions(+), 542 deletions(-) create mode 100644 includes/content/CodeContentHandler.php create mode 100644 includes/content/JsonContent.php create mode 100644 includes/content/JsonContentHandler.php (limited to 'includes/content') diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php index 137efb8a..9d257a6a 100644 --- a/includes/content/AbstractContent.php +++ b/includes/content/AbstractContent.php @@ -32,7 +32,6 @@ * @ingroup Content */ abstract class AbstractContent implements Content { - /** * Name of the content model this Content object represents. * Use with CONTENT_MODEL_XXX constants @@ -44,7 +43,7 @@ abstract class AbstractContent implements Content { protected $model_id; /** - * @param string|null $modelId + * @param string $modelId * * @since 1.21 */ @@ -53,23 +52,21 @@ abstract class AbstractContent implements Content { } /** - * @see Content::getModel - * * @since 1.21 + * + * @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. - * * @since 1.21 * * @param string $modelId The model to check * - * @throws MWException + * @throws MWException If the provided ID is not the ID of the content model supported by this + * Content object. */ protected function checkModelID( $modelId ) { if ( $modelId !== $this->model_id ) { @@ -82,40 +79,40 @@ abstract class AbstractContent implements Content { } /** - * @see Content::getContentHandler - * * @since 1.21 + * + * @see Content::getContentHandler */ public function getContentHandler() { return ContentHandler::getForContent( $this ); } /** - * @see Content::getDefaultFormat - * * @since 1.21 + * + * @see Content::getDefaultFormat */ public function getDefaultFormat() { return $this->getContentHandler()->getDefaultFormat(); } /** - * @see Content::getSupportedFormats - * * @since 1.21 + * + * @see Content::getSupportedFormats */ public function getSupportedFormats() { return $this->getContentHandler()->getSupportedFormats(); } /** - * @see Content::isSupportedFormat + * @since 1.21 * * @param string $format * - * @since 1.21 + * @return bool * - * @return boolean + * @see Content::isSupportedFormat */ public function isSupportedFormat( $format ) { if ( !$format ) { @@ -126,13 +123,11 @@ abstract class AbstractContent implements Content { } /** - * Throws an MWException if $this->isSupportedFormat( $format ) does not - * return true. - * * @since 1.21 * - * @param string $format - * @throws MWException + * @param string $format The serialization format to check. + * + * @throws MWException If the format is not supported by this content handler. */ protected function checkFormat( $format ) { if ( !$this->isSupportedFormat( $format ) ) { @@ -144,48 +139,50 @@ abstract class AbstractContent implements Content { } /** - * @see Content::serialize - * - * @param string|null $format - * * @since 1.21 * + * @param string $format + * * @return string + * + * @see Content::serialize */ public function serialize( $format = null ) { return $this->getContentHandler()->serializeContent( $this, $format ); } /** - * @see Content::isEmpty - * * @since 1.21 * - * @return boolean + * @return bool + * + * @see Content::isEmpty */ public function isEmpty() { return $this->getSize() === 0; } /** - * @see Content::isValid + * Subclasses may override this to implement (light weight) validation. * * @since 1.21 * - * @return boolean + * @return bool Always true. + * + * @see Content::isValid */ public function isValid() { return true; } /** - * @see Content::equals - * * @since 1.21 * - * @param Content|null $that + * @param Content $that + * + * @return bool * - * @return boolean + * @see Content::equals */ public function equals( Content $that = null ) { if ( is_null( $that ) ) { @@ -215,26 +212,19 @@ abstract class AbstractContent implements Content { * Subclasses may override this to determine the secondary data updates more * efficiently, preferably without the need to generate a parser output object. * - * @see Content::getSecondaryDataUpdates() + * @since 1.21 * - * @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. + * @param Title $title + * @param Content $old + * @param bool $recursive + * @param ParserOutput $parserOutput * - * @return Array. A list of DataUpdate objects for putting information - * about this content object somewhere. + * @return DataUpdate[] * - * @since 1.21 + * @see Content::getSecondaryDataUpdates() */ - public function getSecondaryDataUpdates( Title $title, - Content $old = null, - $recursive = true, ParserOutput $parserOutput = null - ) { + public function getSecondaryDataUpdates( Title $title, Content $old = null, + $recursive = true, ParserOutput $parserOutput = null ) { if ( $parserOutput === null ) { $parserOutput = $this->getParserOutput( $title, null, null, false ); } @@ -243,9 +233,11 @@ abstract class AbstractContent implements Content { } /** - * @see Content::getRedirectChain - * * @since 1.21 + * + * @return Title[]|null + * + * @see Content::getRedirectChain */ public function getRedirectChain() { global $wgMaxRedirects; @@ -264,7 +256,7 @@ abstract class AbstractContent implements Content { break; } // Redirects to some special pages are not permitted - if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) { + 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; @@ -273,104 +265,126 @@ abstract class AbstractContent implements Content { break; } } + return $titles; } /** - * @see Content::getRedirectTarget + * Subclasses that implement redirects should override this. * * @since 1.21 + * + * @return null + * + * @see Content::getRedirectTarget */ public function getRedirectTarget() { return null; } /** - * @see Content::getUltimateRedirectTarget - * @note: migrated here from Title::newFromRedirectRecurse + * @note Migrated here from Title::newFromRedirectRecurse. * * @since 1.21 + * + * @return Title|null + * + * @see Content::getUltimateRedirectTarget */ public function getUltimateRedirectTarget() { $titles = $this->getRedirectChain(); + return $titles ? array_pop( $titles ) : null; } /** - * @see Content::isRedirect - * * @since 1.21 * * @return bool + * + * @see Content::isRedirect */ public function isRedirect() { return $this->getRedirectTarget() !== null; } /** - * @see Content::updateRedirect - * * This default implementation always returns $this. - * - * @param Title $target + * Subclasses that implement redirects should override this. * * @since 1.21 * + * @param Title $target + * * @return Content $this + * + * @see Content::updateRedirect */ public function updateRedirect( Title $target ) { return $this; } /** - * @see Content::getSection - * * @since 1.21 + * + * @return null + * + * @see Content::getSection */ public function getSection( $sectionId ) { return null; } /** - * @see Content::replaceSection - * * @since 1.21 + * + * @return null + * + * @see Content::replaceSection */ - public function replaceSection( $section, Content $with, $sectionTitle = '' ) { + public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) { return null; } /** - * @see Content::preSaveTransform - * * @since 1.21 + * + * @return Content $this + * + * @see Content::preSaveTransform */ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { return $this; } /** - * @see Content::addSectionHeader - * * @since 1.21 + * + * @return Content $this + * + * @see Content::addSectionHeader */ public function addSectionHeader( $header ) { return $this; } /** - * @see Content::preloadTransform - * * @since 1.21 + * + * @return Content $this + * + * @see Content::preloadTransform */ - public function preloadTransform( Title $title, ParserOptions $popts ) { + public function preloadTransform( Title $title, ParserOptions $popts, $params = array() ) { return $this; } /** - * @see Content::prepareSave - * * @since 1.21 + * + * @return Status + * + * @see Content::prepareSave */ public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) { if ( $this->isValid() ) { @@ -381,53 +395,47 @@ abstract class AbstractContent implements Content { } /** - * @see Content::getDeletionUpdates - * * @since 1.21 * - * @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. + * @param WikiPage $page + * @param ParserOutput $parserOutput + * + * @return LinksDeletionUpdate[] * - * @return array A list of DataUpdate instances that will clean up the - * database after deletion. + * @see Content::getDeletionUpdates */ - public function getDeletionUpdates( WikiPage $page, - ParserOutput $parserOutput = null ) - { + public function getDeletionUpdates( WikiPage $page, ParserOutput $parserOutput = null ) { return array( new LinksDeletionUpdate( $page ), ); } /** - * This default implementation always returns false. Subclasses may override this to supply matching logic. - * - * @see Content::matchMagicWord + * This default implementation always returns false. Subclasses may override + * this to supply matching logic. * * @since 1.21 * * @param MagicWord $word * - * @return bool + * @return bool Always false. + * + * @see Content::matchMagicWord */ public function matchMagicWord( MagicWord $word ) { return false; } /** - * @see Content::convert() - * * This base implementation calls the hook ConvertContent to enable custom conversions. * Subclasses may override this to implement conversion for "their" content model. * - * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags. - * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is - * not allowed, full round-trip conversion is expected to work without losing information. + * @param string $toModel + * @param string $lossy * - * @return Content|bool A content object with the content model $toModel, or false if - * that conversion is not supported. + * @return Content|bool + * + * @see Content::convert() */ public function convert( $toModel, $lossy = '' ) { if ( $this->getModel() === $toModel ) { @@ -439,6 +447,77 @@ abstract class AbstractContent implements Content { $result = false; wfRunHooks( 'ConvertContent', array( $this, $toModel, $lossy, &$result ) ); + return $result; } + + /** + * Returns a ParserOutput object containing information derived from this content. + * Most importantly, unless $generateHtml was false, the return value contains an + * HTML representation of the content. + * + * Subclasses that want to control the parser output may override this, but it is + * preferred to override fillParserOutput() instead. + * + * Subclasses that override getParserOutput() itself should take care to call the + * ContentGetParserOutput hook. + * + * @since 1.24 + * + * @param Title $title Context title for parsing + * @param int|null $revId Revision ID (for {{REVISIONID}}) + * @param ParserOptions|null $options Parser options + * @param bool $generateHtml Whether or not to generate HTML + * + * @return ParserOutput Containing information derived from this content. + */ + public function getParserOutput( Title $title, $revId = null, + ParserOptions $options = null, $generateHtml = true + ) { + if ( $options === null ) { + $options = $this->getContentHandler()->makeParserOptions( 'canonical' ); + } + + $po = new ParserOutput(); + + if ( wfRunHooks( 'ContentGetParserOutput', + array( $this, $title, $revId, $options, $generateHtml, &$po ) ) ) { + + // Save and restore the old value, just in case something is reusing + // the ParserOptions object in some weird way. + $oldRedir = $options->getRedirectTarget(); + $options->setRedirectTarget( $this->getRedirectTarget() ); + $this->fillParserOutput( $title, $revId, $options, $generateHtml, $po ); + $options->setRedirectTarget( $oldRedir ); + } + + return $po; + } + + /** + * Fills the provided ParserOutput with information derived from the content. + * Unless $generateHtml was false, this includes an HTML representation of the content. + * + * This is called by getParserOutput() after consulting the ContentGetParserOutput hook. + * Subclasses are expected to override this method (or getParserOutput(), if need be). + * Subclasses of TextContent should generally override getHtml() instead. + * + * This placeholder implementation always throws an exception. + * + * @since 1.24 + * + * @param Title $title Context title for parsing + * @param int|null $revId Revision ID (for {{REVISIONID}}) + * @param ParserOptions $options Parser options + * @param bool $generateHtml Whether or not to generate HTML + * @param ParserOutput &$output The output object to fill (reference). + * + * @throws MWException + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output + ) { + // Don't make abstract, so subclasses that override getParserOutput() directly don't fail. + throw new MWException( 'Subclasses of AbstractContent must override fillParserOutput!' ); + } } diff --git a/includes/content/CodeContentHandler.php b/includes/content/CodeContentHandler.php new file mode 100644 index 00000000..447a2a73 --- /dev/null +++ b/includes/content/CodeContentHandler.php @@ -0,0 +1,65 @@ +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, + * @note To control which options are used in the cache key for the + * generated parser output, implementations of this method + * may call ParserOutput::recordOption() on the output object. + * + * @param Title $title The page title to use as a context for rendering. + * @param int $revId Optional revision ID being rendered. + * @param ParserOptions $options Any parser options. + * @param bool $generateHtml Whether to generate HTML (default: true). If false, * the result of calling getText() on the ParserOutput object returned by * this method is undefined. * @@ -264,9 +272,9 @@ interface Content { * * @return ParserOutput */ - public function getParserOutput( Title $title, - $revId = null, + public function getParserOutput( Title $title, $revId = null, ParserOptions $options = null, $generateHtml = true ); + // TODO: make RenderOutput and RenderOptions base classes /** @@ -284,24 +292,22 @@ interface Content { * 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 + * @param Title $title The context for determining the necessary updates + * @param Content $old 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: + * @param bool $recursive Whether to include recursive updates (default: * false). - * @param $parserOutput ParserOutput|null Optional ParserOutput object. + * @param ParserOutput $parserOutput 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 + * @return DataUpdate[] A list of DataUpdate objects for putting information * about this content object somewhere. * * @since 1.21 */ - public function getSecondaryDataUpdates( Title $title, - Content $old = null, - $recursive = true, ParserOutput $parserOutput = null - ); + public function getSecondaryDataUpdates( Title $title, Content $old = null, + $recursive = true, ParserOutput $parserOutput = null ); /** * Construct the redirect destination from this content and return an @@ -311,7 +317,7 @@ interface Content { * * @since 1.21 * - * @return Array of Titles, with the destination last + * @return Title[]|null List of Titles, with the destination last. */ public function getRedirectChain(); @@ -323,7 +329,7 @@ interface Content { * * @since 1.21 * - * @return Title: The corresponding Title + * @return Title|null The corresponding Title. */ public function getRedirectTarget(); @@ -340,7 +346,7 @@ interface Content { * * @since 1.21 * - * @return Title + * @return Title|null */ public function getUltimateRedirectTarget(); @@ -360,9 +366,10 @@ interface Content { * * @since 1.21 * - * @param Title $target the new redirect target + * @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) + * @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 ); @@ -371,11 +378,11 @@ interface Content { * * @since 1.21 * - * @param string $sectionId 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 + * @param string|number $sectionId Section identifier as a number or string + * (e.g. 0, 1 or 'T-1'). 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|bool|null The section, or false if no such section * exist, or null if sections are not supported. */ public function getSection( $sectionId ); @@ -386,12 +393,15 @@ interface Content { * * @since 1.21 * - * @param $section null/false or a section number (0, 1, 2, T1, T2...), or "new" - * @param $with Content: new content of the section - * @param string $sectionTitle new section's subject, only if $section is 'new' - * @return string Complete article text, or null if error + * @param string|number|null|bool $sectionId Section identifier as a number or string + * (e.g. 0, 1 or 'T-1'), null/false or an empty string for the whole page + * or 'new' for a new section. + * @param Content $with New content of the section + * @param string $sectionTitle New section's subject, only if $section is 'new' + * + * @return string|null Complete article text, or null if error */ - public function replaceSection( $section, Content $with, $sectionTitle = '' ); + public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ); /** * Returns a Content object with pre-save transformations applied (or this @@ -399,9 +409,10 @@ interface Content { * * @since 1.21 * - * @param $title Title - * @param $user User - * @param $parserOptions null|ParserOptions + * @param Title $title + * @param User $user + * @param ParserOptions $parserOptions + * * @return Content */ public function preSaveTransform( Title $title, User $user, ParserOptions $parserOptions ); @@ -413,7 +424,8 @@ interface Content { * * @since 1.21 * - * @param $header string + * @param string $header + * * @return Content */ public function addSectionHeader( $header ); @@ -424,11 +436,13 @@ interface Content { * * @since 1.21 * - * @param $title Title - * @param $parserOptions null|ParserOptions + * @param Title $title + * @param ParserOptions $parserOptions + * @param array $params + * * @return Content */ - public function preloadTransform( Title $title, ParserOptions $parserOptions ); + public function preloadTransform( Title $title, ParserOptions $parserOptions, $params = array() ); /** * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in @@ -437,24 +451,24 @@ interface Content { * 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 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. + * 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. * * @since 1.21 * * @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 + * @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. + * @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() + * @see WikiPage::doEditContent() */ public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ); @@ -465,12 +479,12 @@ interface Content { * * @since 1.21 * - * @param $page WikiPage the deleted page - * @param $parserOutput null|ParserOutput optional parser output object + * @param WikiPage $page The deleted page + * @param ParserOutput $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 + * @return DataUpdate[] A list of DataUpdate instances that will clean up the * database after deletion. */ public function getDeletionUpdates( WikiPage $page, @@ -481,9 +495,9 @@ interface Content { * * @since 1.21 * - * @param MagicWord $word the magic word to match + * @param MagicWord $word The magic word to match * - * @return bool whether this Content object matches the given magic word. + * @return bool Whether this Content object matches the given magic word. */ public function matchMagicWord( MagicWord $word ); @@ -491,18 +505,19 @@ interface Content { * Converts this content object into another content object with the given content model, * if that is possible. * - * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags. - * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is - * not allowed, full round-trip conversion is expected to work without losing information. + * @param string $toModel The desired content model, use the CONTENT_MODEL_XXX flags. + * @param string $lossy Optional flag, set to "lossy" to allow lossy conversion. If lossy + * conversion is not allowed, full round-trip conversion is expected to work without losing + * information. * * @return Content|bool A content object with the content model $toModel, or false if * that conversion is not supported. */ public function convert( $toModel, $lossy = '' ); - - // TODO: ImagePage and CategoryPage interfere with per-content action handlers - // TODO: nice&sane integration of GeSHi syntax highlighting + // @todo ImagePage and CategoryPage interfere with per-content action handlers + // @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 + } diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php index 2a92e233..ac417223 100644 --- a/includes/content/ContentHandler.php +++ b/includes/content/ContentHandler.php @@ -31,7 +31,6 @@ * @ingroup Content */ class MWContentSerializationException extends MWException { - } /** @@ -54,7 +53,6 @@ class MWContentSerializationException extends MWException { * @ingroup Content */ abstract class ContentHandler { - /** * Switch for enabling deprecation warnings. Used by ContentHandler::deprecated() * and ContentHandler::runLegacyHooks(). @@ -87,10 +85,11 @@ abstract class ContentHandler { * * @since 1.21 * - * @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'. + * @param Content $content + * + * @throws MWException If the content is not an instance of TextContent and + * wgContentHandlerTextFallback was set to 'fail'. + * @return string|null Textual form of the content, if available. */ public static function getContentText( Content $content = null ) { global $wgContentHandlerTextFallback; @@ -129,24 +128,21 @@ abstract class ContentHandler { * * @since 1.21 * - * @param string $text the textual representation, will be + * @param string $text The textual representation, will be * unserialized to create the Content object - * @param $title null|Title the title of the page this text belongs to. + * @param Title $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, + * @param string $modelId The model to deserialize to. If not provided, * $title->getContentModel() is used. - * @param $format null|string the format to use for deserialization. If not + * @param string $format The format to use for deserialization. If not * given, the model's default format is used. * - * @throws MWException - * @return Content a Content object representing $text - * - * @throws MWException if $model or $format is not supported or if $text can - * not be unserialized using $format. + * @throws MWException If model ID or format is not supported or if the text can not be + * unserialized using the format. + * @return Content A Content object representing the text. */ public static function makeContent( $text, Title $title = null, - $modelId = null, $format = 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." ); @@ -156,6 +152,7 @@ abstract class ContentHandler { } $handler = ContentHandler::getForModelID( $modelId ); + return $handler->unserializeContent( $text, $format ); } @@ -189,8 +186,9 @@ abstract class ContentHandler { * * @since 1.21 * - * @param $title Title - * @return null|string default model name for the page given by $title + * @param Title $title + * + * @return string Default model name for the page given by $title */ public static function getDefaultModelFor( Title $title ) { // NOTE: this method must not rely on $title->getContentModel() directly or indirectly, @@ -254,11 +252,13 @@ abstract class ContentHandler { * * @since 1.21 * - * @param $title Title + * @param Title $title + * * @return ContentHandler */ public static function getForTitle( Title $title ) { $modelId = $title->getContentModel(); + return ContentHandler::getForModelID( $modelId ); } @@ -268,18 +268,20 @@ abstract class ContentHandler { * * @since 1.21 * - * @param $content Content + * @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 + * @var array A Cache of ContentHandler instances by model id */ - static $handlers; + protected static $handlers; /** * Returns the ContentHandler singleton for the given model ID. Use the @@ -302,9 +304,9 @@ abstract class ContentHandler { * * @param string $modelId 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. + * + * @throws MWException If no handler is known for the model ID. + * @return ContentHandler The ContentHandler singleton for handling the model given by the ID. */ public static function getForModelID( $modelId ) { global $wgContentHandlers; @@ -330,14 +332,16 @@ abstract class ContentHandler { $handler = new $class( $modelId ); if ( !( $handler instanceof ContentHandler ) ) { - throw new MWException( "$class from \$wgContentHandlers is not compatible with ContentHandler" ); + throw new MWException( "$class from \$wgContentHandlers is not " . + "compatible with ContentHandler" ); } } wfDebugLog( 'ContentHandler', 'Created handler for ' . $modelId - . ': ' . get_class( $handler ) ); + . ': ' . get_class( $handler ) ); ContentHandler::$handlers[$modelId] = $handler; + return ContentHandler::$handlers[$modelId]; } @@ -350,10 +354,12 @@ abstract class ContentHandler { * @param string $name 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. + * @throws MWException If the model ID isn't known. + * @return string The content model's localized name. */ public static function getLocalizedName( $name ) { + // Messages: content-model-wikitext, content-model-text, + // content-model-javascript, content-model-css $key = "content-model-$name"; $msg = wfMessage( $key ); @@ -378,12 +384,20 @@ abstract class ContentHandler { } $formats = array_unique( $formats ); + return $formats; } // ------------------------------------------------------------------------ + /** + * @var string + */ protected $mModelID; + + /** + * @var string[] + */ protected $mSupportedFormats; /** @@ -392,7 +406,7 @@ abstract class ContentHandler { * provided as literals by subclass's constructors. * * @param string $modelId (use CONTENT_MODEL_XXX constants). - * @param array $formats List for supported serialization formats + * @param string[] $formats List for supported serialization formats * (typically as MIME types) */ public function __construct( $modelId, $formats ) { @@ -409,23 +423,54 @@ abstract class ContentHandler { * * @since 1.21 * - * @param $content Content The Content object to serialize - * @param $format null|String The desired serialization format + * @param Content $content The Content object to serialize + * @param string $format The desired serialization format + * * @return string Serialized form of the content */ abstract public function serializeContent( Content $content, $format = null ); + /** + * Applies transformations on export (returns the blob unchanged per default). + * Subclasses may override this to perform transformations such as conversion + * of legacy formats or filtering of internal meta-data. + * + * @param string $blob The blob to be exported + * @param string|null $format The blob's serialization format + * + * @return string + */ + public function exportTransform( $blob, $format = null ) { + return $blob; + } + /** * Unserializes a Content object of the type supported by this ContentHandler. * * @since 1.21 * - * @param string $blob serialized form of the content - * @param $format null|String the format used for serialization - * @return Content the Content object created by deserializing $blob + * @param string $blob Serialized form of the content + * @param string $format The format used for serialization + * + * @return Content The Content object created by deserializing $blob */ abstract public function unserializeContent( $blob, $format = null ); + /** + * Apply import transformation (per default, returns $blob unchanged). + * This gives subclasses an opportunity to transform data blobs on import. + * + * @since 1.24 + * + * @param string $blob + * @param string|null $format + * + * @return string + */ + public function importTransform( $blob, $format = null ) { + return $blob; + } + /** * Creates an empty Content object of the type supported by this * ContentHandler. @@ -438,7 +483,7 @@ abstract class ContentHandler { /** * Creates a new Content object that acts as a redirect to the given page, - * or null of redirects are not supported by this content model. + * or null if redirects are not supported by this content model. * * This default implementation always returns null. Subclasses supporting redirects * must override this method. @@ -448,10 +493,10 @@ abstract class ContentHandler { * * @since 1.21 * - * @param Title $destination the page to redirect to. - * @param string $text text to include in the redirect, if possible. + * @param Title $destination The page to redirect to. + * @param string $text Text to include in the redirect, if possible. * - * @return Content + * @return Content Always null. */ public function makeRedirectContent( Title $destination, $text = '' ) { return null; @@ -463,21 +508,19 @@ abstract class ContentHandler { * * @since 1.21 * - * @return String The model ID + * @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 1.21 * * @param string $model_id The model to check * - * @throws MWException + * @throws MWException If the model ID is not the ID of the content model supported by this + * ContentHandler. */ protected function checkModelID( $model_id ) { if ( $model_id !== $this->mModelID ) { @@ -494,7 +537,7 @@ abstract class ContentHandler { * * @since 1.21 * - * @return array of serialization formats as MIME type like strings + * @return string[] List of serialization formats as MIME type like strings */ public function getSupportedFormats() { return $this->mSupportedFormats; @@ -509,7 +552,7 @@ abstract class ContentHandler { * * @since 1.21 * - * @return string the name of the default serialization format as a MIME type + * @return string The name of the default serialization format as a MIME type */ public function getDefaultFormat() { return $this->mSupportedFormats[0]; @@ -524,11 +567,11 @@ abstract class ContentHandler { * * @since 1.21 * - * @param string $format the serialization format to check + * @param string $format The serialization format to check + * * @return bool */ public function isSupportedFormat( $format ) { - if ( !$format ) { return true; // this means "use the default" } @@ -537,13 +580,11 @@ abstract class ContentHandler { } /** - * Throws an MWException if isSupportedFormat( $format ) is not true. - * Convenient for checking whether a format provided as a parameter is - * actually supported. + * Convenient for checking whether a format provided as a parameter is actually supported. * - * @param string $format the serialization format to check + * @param string $format The serialization format to check * - * @throws MWException + * @throws MWException If the format is not supported by this content handler. */ protected function checkFormat( $format ) { if ( !$this->isSupportedFormat( $format ) ) { @@ -562,7 +603,7 @@ abstract class ContentHandler { * * @since 1.21 * - * @return Array + * @return array Always an empty array. */ public function getActionOverrides() { return array(); @@ -573,21 +614,18 @@ abstract class ContentHandler { * * @since 1.21 * - * @param $context IContextSource context to use, anything else will be - * ignored - * @param $old Integer Old ID we want to show and diff with. - * @param int|string $new 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 + * @param IContextSource $context Context to use, anything else will be ignored. + * @param int $old Revision ID we want to show and diff with. + * @param int|string $new Either a revision ID or one of the strings 'cur', 'prev' or 'next'. + * @param int $rcid FIXME: Deprecated, no longer used. Defaults to 0. + * @param bool $refreshCache If set, refreshes the diff cache. Defaults to false. + * @param bool $unhide If set, allow viewing deleted revs. Defaults to false. * * @return DifferenceEngine */ - public function createDifferenceEngine( IContextSource $context, - $old = 0, $new = 0, - $rcid = 0, # FIXME: use everywhere! - $refreshCache = false, $unhide = false - ) { + public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0, + $rcid = 0, //FIXME: Deprecated, no longer used + $refreshCache = false, $unhide = false ) { $diffEngineClass = $this->getDiffEngineClass(); return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide ); @@ -596,19 +634,21 @@ abstract class ContentHandler { /** * 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) + * 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. + * 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.21 * - * @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. + * @param Title $title The page to determine the language for. + * @param Content $content The page's content, if you have it handy, to avoid reloading it. * - * @return Language the page's language + * @return Language The page's language */ public function getPageLanguage( Title $title, Content $content = null ) { global $wgContLang, $wgLang; @@ -621,6 +661,7 @@ abstract class ContentHandler { } wfRunHooks( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) ); + return wfGetLangObj( $pageLang ); } @@ -639,10 +680,10 @@ abstract class ContentHandler { * * @since 1.21 * - * @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. + * @param Title $title The page to determine the language for. + * @param Content $content The page's content, if you have it handy, to avoid reloading it. * - * @return Language the page's language for viewing + * @return Language The page's language for viewing */ public function getPageViewLanguage( Title $title, Content $content = null ) { $pageLang = $this->getPageLanguage( $title, $content ); @@ -668,12 +709,19 @@ abstract class ContentHandler { * 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. + * @note this calls the ContentHandlerCanBeUsedOn hook which may be used to override which + * content model can be used where. * - * @return bool true if content of this kind can be used on the given page, false otherwise. + * @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; + $ok = true; + + wfRunHooks( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) ); + + return $ok; } /** @@ -688,19 +736,18 @@ abstract class ContentHandler { } /** - * Attempts to merge differences between three versions. - * Returns a new Content object for a clean merge and false for failure or - * a conflict. + * 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 1.21 * - * @param $oldContent Content|string String - * @param $myContent Content|string String - * @param $yourContent Content|string String + * @param Content $oldContent The page's previous content. + * @param Content $myContent One of the page's conflicting contents. + * @param Content $yourContent One of the page's conflicting contents. * - * @return Content|Bool + * @return Content|bool Always false. */ public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) { return false; @@ -711,13 +758,14 @@ abstract class ContentHandler { * * @since 1.21 * - * @param $oldContent Content|null: the previous text of the page. - * @param $newContent Content|null: The submitted text of the page. + * @param Content $oldContent The previous text of the page. + * @param Content $newContent The submitted text of the page. * @param int $flags 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 ) { + public function getAutosummary( Content $oldContent = null, Content $newContent = null, + $flags ) { // Decide what kind of auto-summary is needed. // Redirect auto-summaries @@ -733,15 +781,15 @@ abstract class ContentHandler { if ( is_object( $rt ) ) { if ( !is_object( $ot ) || !$rt->equals( $ot ) - || $ot->getFragment() != $rt->getFragment() ) - { + || $ot->getFragment() != $rt->getFragment() + ) { $truncatedtext = $newContent->getTextForSummary( 250 - - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() ) - - strlen( $rt->getFullText() ) ); + - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() ) + - strlen( $rt->getFullText() ) ); return wfMessage( 'autoredircomment', $rt->getFullText() ) - ->rawParams( $truncatedtext )->inContentLanguage()->text(); + ->rawParams( $truncatedtext )->inContentLanguage()->text(); } } @@ -754,7 +802,7 @@ abstract class ContentHandler { 200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) ); return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext ) - ->inContentLanguage()->text(); + ->inContentLanguage()->text(); } // Blanking auto-summaries @@ -762,15 +810,20 @@ abstract class ContentHandler { return wfMessage( 'autosumm-blank' )->inContentLanguage()->text(); } elseif ( !empty( $oldContent ) && $oldContent->getSize() > 10 * $newContent->getSize() - && $newContent->getSize() < 500 ) - { + && $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(); + ->inContentLanguage()->text(); + } + + // New blank article auto-summary + if ( $flags & EDIT_NEW && $newContent->isEmpty() ) { + return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text(); } // If we reach this point, there's no applicable auto-summary for our @@ -783,12 +836,13 @@ abstract class ContentHandler { * * @since 1.21 * - * @param $title Title: the page's title - * @param &$hasHistory Boolean: whether the page has a history + * @param Title $title The page's title + * @param bool &$hasHistory 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 + * @todo &$hasHistory is extremely ugly, it's here because * WikiPage::getAutoDeleteReason() and Article::generateReason() * have it / want it. */ @@ -891,9 +945,9 @@ abstract class ContentHandler { * * @since 1.21 * - * @param $current Revision The current text - * @param $undo Revision The revision to undo - * @param $undoafter Revision Must be an earlier revision than $undo + * @param Revision $current The current text + * @param Revision $undo The revision to undo + * @param Revision $undoafter Must be an earlier revision than $undo * * @return mixed String on success, false on failure */ @@ -907,6 +961,10 @@ abstract class ContentHandler { $undo_content = $undo->getContent(); $undoafter_content = $undoafter->getContent(); + if ( !$undo_content || !$undoafter_content ) { + return false; // no content to undo + } + $this->checkModelID( $cur_content->getModel() ); $this->checkModelID( $undo_content->getModel() ); $this->checkModelID( $undoafter_content->getModel() ); @@ -922,7 +980,7 @@ abstract class ContentHandler { } /** - * Get parser options suitable for rendering the primary article wikitext + * Get parser options suitable for rendering and caching the article * * @param IContextSource|User|string $context One of the following: * - IContextSource: Use the User and the Language of the provided @@ -932,8 +990,6 @@ abstract class ContentHandler { * - 'canonical': Canonical options (anonymous user with default * preferences and content language). * - * @param IContextSource|User|string $context - * * @throws MWException * @return ParserOptions */ @@ -962,7 +1018,7 @@ abstract class ContentHandler { * * @since 1.21 * - * @return bool + * @return bool Always false. */ public function isParserCacheSupported() { return false; @@ -975,7 +1031,7 @@ abstract class ContentHandler { * Content models that return true here should also implement * Content::getSection, Content::replaceSection, etc. to handle sections.. * - * @return boolean whether sections are supported. + * @return bool Always false. */ public function supportsSections() { return false; @@ -988,7 +1044,7 @@ abstract class ContentHandler { * Content models that return true here should also implement * ContentHandler::makeRedirectContent to return a Content object. * - * @return boolean whether redirects are supported. + * @return bool Always false. */ public function supportsRedirects() { return false; @@ -998,11 +1054,11 @@ abstract class ContentHandler { * Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if * self::$enableDeprecationWarnings is set to true. * - * @param string $func The name of the deprecated function - * @param string $version The version since the method is deprecated. Usually 1.21 - * for ContentHandler related stuff. - * @param string|bool $component: Component to which the function belongs. - * If false, it is assumed the function is in MediaWiki core. + * @param string $func The name of the deprecated function + * @param string $version The version since the method is deprecated. Usually 1.21 + * for ContentHandler related stuff. + * @param string|bool $component : Component to which the function belongs. + * If false, it is assumed the function is in MediaWiki core. * * @see ContentHandler::$enableDeprecationWarnings * @see wfDeprecated @@ -1020,18 +1076,19 @@ abstract class ContentHandler { * hook function, a new Content object is constructed from the new * text. * - * @param string $event event name - * @param array $args parameters passed to hook functions - * @param bool $warn whether to log a warning. + * @param string $event Event name + * @param array $args Parameters passed to hook functions + * @param bool $warn Whether to log a warning. * Default to self::$enableDeprecationWarnings. * May be set to false for testing. * - * @return Boolean True if no handler aborted the hook + * @return bool True if no handler aborted the hook * * @see ContentHandler::$enableDeprecationWarnings */ public static function runLegacyHooks( $event, $args = array(), - $warn = null ) { + $warn = null + ) { if ( $warn === null ) { $warn = self::$enableDeprecationWarnings; @@ -1073,7 +1130,8 @@ abstract class ContentHandler { wfRestoreWarnings(); - wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " . implode( ', ', $handlerInfo ), 2 ); + wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " . + implode( ', ', $handlerInfo ), 2 ); } // convert Content objects to text diff --git a/includes/content/CssContent.php b/includes/content/CssContent.php index 03cc2d00..8290603c 100644 --- a/includes/content/CssContent.php +++ b/includes/content/CssContent.php @@ -31,18 +31,26 @@ * @ingroup Content */ class CssContent extends TextContent { - public function __construct( $text ) { - parent::__construct( $text, CONTENT_MODEL_CSS ); + + /** + * @param string $text CSS code. + * @param string $modelId the content content model + */ + public function __construct( $text, $modelId = CONTENT_MODEL_CSS ) { + parent::__construct( $text, $modelId ); } /** * Returns a Content object with pre-save transformations applied using * Parser::preSaveTransform(). * - * @param $title Title - * @param $user User - * @param $popts ParserOptions - * @return Content + * @param Title $title + * @param User $user + * @param ParserOptions $popts + * + * @return CssContent + * + * @see TextContent::preSaveTransform */ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { global $wgParser; @@ -51,15 +59,19 @@ class CssContent extends TextContent { $text = $this->getNativeData(); $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); - return new CssContent( $pst ); + return new static( $pst ); } + /** + * @return string CSS wrapped in a
 tag.
+	 */
 	protected function getHtml() {
 		$html = "";
 		$html .= "
\n";
-		$html .= $this->getHighlightHtml();
+		$html .= htmlspecialchars( $this->getNativeData() );
 		$html .= "\n
\n"; return $html; } + } diff --git a/includes/content/CssContentHandler.php b/includes/content/CssContentHandler.php index cb5a349d..b2a8676b 100644 --- a/includes/content/CssContentHandler.php +++ b/includes/content/CssContentHandler.php @@ -27,41 +27,16 @@ * @since 1.21 * @ingroup Content */ -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( '' ); - } +class CssContentHandler extends CodeContentHandler { /** - * Returns the english language, because CSS is english, and should be handled as such. - * - * @return Language wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageLanguage() + * @param string $modelId */ - public function getPageLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); + public function __construct( $modelId = CONTENT_MODEL_CSS ) { + parent::__construct( $modelId, array( CONTENT_FORMAT_CSS ) ); } - /** - * 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' ); + protected function getContentClass() { + return 'CssContent'; } } diff --git a/includes/content/JavaScriptContent.php b/includes/content/JavaScriptContent.php index 2ae572be..c0194c2e 100644 --- a/includes/content/JavaScriptContent.php +++ b/includes/content/JavaScriptContent.php @@ -31,8 +31,13 @@ * @ingroup Content */ class JavaScriptContent extends TextContent { - public function __construct( $text ) { - parent::__construct( $text, CONTENT_MODEL_JAVASCRIPT ); + + /** + * @param string $text JavaScript code. + * @param string $modelId the content model name + */ + public function __construct( $text, $modelId = CONTENT_MODEL_JAVASCRIPT ) { + parent::__construct( $text, $modelId ); } /** @@ -42,7 +47,8 @@ class JavaScriptContent extends TextContent { * @param Title $title * @param User $user * @param ParserOptions $popts - * @return Content + * + * @return JavaScriptContent */ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { global $wgParser; @@ -52,15 +58,19 @@ class JavaScriptContent extends TextContent { $text = $this->getNativeData(); $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); - return new JavaScriptContent( $pst ); + return new static( $pst ); } + /** + * @return string JavaScript wrapped in a
 tag.
+	 */
 	protected function getHtml() {
 		$html = "";
 		$html .= "
\n";
-		$html .= $this->getHighlightHtml();
+		$html .= htmlspecialchars( $this->getNativeData() );
 		$html .= "\n
\n"; return $html; } + } diff --git a/includes/content/JavaScriptContentHandler.php b/includes/content/JavaScriptContentHandler.php index 33fa9172..457b83d7 100644 --- a/includes/content/JavaScriptContentHandler.php +++ b/includes/content/JavaScriptContentHandler.php @@ -27,41 +27,16 @@ * @ingroup Content * @todo make ScriptContentHandler base class, do highlighting stuff there? */ -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( '' ); - } +class JavaScriptContentHandler extends CodeContentHandler { /** - * Returns the english language, because JS is english, and should be handled as such. - * - * @return Language wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageLanguage() + * @param string $modelId */ - public function getPageLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); + public function __construct( $modelId = CONTENT_MODEL_JAVASCRIPT ) { + parent::__construct( $modelId, array( CONTENT_FORMAT_JAVASCRIPT ) ); } - /** - * Returns the english language, because JS 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' ); + protected function getContentClass() { + return 'JavaScriptContent'; } } diff --git a/includes/content/JsonContent.php b/includes/content/JsonContent.php new file mode 100644 index 00000000..b36827c5 --- /dev/null +++ b/includes/content/JsonContent.php @@ -0,0 +1,120 @@ + + * @author Kunal Mehta + */ + +/** + * Represents the content of a JSON content. + * @since 1.24 + */ +class JsonContent extends TextContent { + + public function __construct( $text, $modelId = CONTENT_MODEL_JSON ) { + parent::__construct( $text, $modelId ); + } + + /** + * Decodes the JSON into a PHP associative array. + * @return array + */ + public function getJsonData() { + return FormatJson::decode( $this->getNativeData(), true ); + } + + /** + * @return bool Whether content is valid JSON. + */ + public function isValid() { + return $this->getJsonData() !== null; + } + + /** + * Pretty-print JSON + * + * @return bool|null|string + */ + public function beautifyJSON() { + $decoded = FormatJson::decode( $this->getNativeData(), true ); + if ( !is_array( $decoded ) ) { + return null; + } + return FormatJson::encode( $decoded, true ); + + } + + /** + * Beautifies JSON prior to save. + * @param Title $title Title + * @param User $user User + * @param ParserOptions $popts + * @return JsonContent + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { + return new static( $this->beautifyJSON() ); + } + + /** + * Set the HTML and add the appropriate styles + * + * + * @param Title $title + * @param int $revId + * @param ParserOptions $options + * @param bool $generateHtml + * @param ParserOutput $output + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output + ) { + if ( $generateHtml ) { + $output->setText( $this->objectTable( $this->getJsonData() ) ); + $output->addModuleStyles( 'mediawiki.content.json' ); + } else { + $output->setText( '' ); + } + } + /** + * Constructs an HTML representation of a JSON object. + * @param array $mapping + * @return string HTML + */ + protected function objectTable( $mapping ) { + $rows = array(); + + foreach ( $mapping as $key => $val ) { + $rows[] = $this->objectRow( $key, $val ); + } + return Xml::tags( 'table', array( 'class' => 'mw-json' ), + Xml::tags( 'tbody', array(), join( "\n", $rows ) ) + ); + } + + /** + * Constructs HTML representation of a single key-value pair. + * @param string $key + * @param mixed $val + * @return string HTML. + */ + protected function objectRow( $key, $val ) { + $th = Xml::elementClean( 'th', array(), $key ); + if ( is_array( $val ) ) { + $td = Xml::tags( 'td', array(), self::objectTable( $val ) ); + } else { + if ( is_string( $val ) ) { + $val = '"' . $val . '"'; + } else { + $val = FormatJson::encode( $val ); + } + + $td = Xml::elementClean( 'td', array( 'class' => 'value' ), $val ); + } + + return Xml::tags( 'tr', array(), $th . $td ); + } + +} diff --git a/includes/content/JsonContentHandler.php b/includes/content/JsonContentHandler.php new file mode 100644 index 00000000..392ce37b --- /dev/null +++ b/includes/content/JsonContentHandler.php @@ -0,0 +1,26 @@ + + * @author Kunal Mehta + */ + +/** + * @since 1.24 + */ +class JsonContentHandler extends CodeContentHandler { + + public function __construct( $modelId = CONTENT_MODEL_JSON ) { + parent::__construct( $modelId, array( CONTENT_FORMAT_JSON ) ); + } + + /** + * @return string + */ + protected function getContentClass() { + return 'JsonContent'; + } +} diff --git a/includes/content/MessageContent.php b/includes/content/MessageContent.php index b36b670c..5b84657f 100644 --- a/includes/content/MessageContent.php +++ b/includes/content/MessageContent.php @@ -29,7 +29,7 @@ * 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. + * MessageContent is just intended as glue for wrapping a message programmatically. * * @ingroup Content */ @@ -41,8 +41,8 @@ class MessageContent extends AbstractContent { protected $mMessage; /** - * @param Message|String $msg A Message object, or a message key - * @param array|null $params An optional array of message parameters + * @param Message|string $msg A Message object, or a message key. + * @param string[] $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. @@ -60,18 +60,18 @@ class MessageContent extends AbstractContent { } /** - * Returns the message as rendered HTML + * Fully parse the text from wikitext to HTML. * - * @return string The message text, parsed into html + * @return string Parsed HTML. */ public function getHtml() { return $this->mMessage->parse(); } /** - * Returns the message as rendered HTML + * Returns the message text. {{-transformation is done. * - * @return string The message text, parsed into html + * @return string Unescaped message text. */ public function getWikitext() { return $this->mMessage->text(); @@ -88,6 +88,8 @@ class MessageContent extends AbstractContent { } /** + * @return string + * * @see Content::getTextForSearchIndex */ public function getTextForSearchIndex() { @@ -95,6 +97,8 @@ class MessageContent extends AbstractContent { } /** + * @return string + * * @see Content::getWikitextForTransclusion */ public function getWikitextForTransclusion() { @@ -102,6 +106,10 @@ class MessageContent extends AbstractContent { } /** + * @param int $maxlength Maximum length of the summary text, defaults to 250. + * + * @return string The summary text. + * * @see Content::getTextForSummary */ public function getTextForSummary( $maxlength = 250 ) { @@ -109,18 +117,18 @@ class MessageContent extends AbstractContent { } /** - * @see Content::getSize - * * @return int + * + * @see Content::getSize */ public function getSize() { return strlen( $this->mMessage->plain() ); } /** - * @see Content::copy + * @return Content A copy of this object * - * @return Content. A copy of this object + * @see Content::copy */ public function copy() { // MessageContent is immutable (because getNativeData() returns a clone of the Message object) @@ -128,24 +136,28 @@ class MessageContent extends AbstractContent { } /** - * @see Content::isCountable + * @param bool $hasLinks + * + * @return bool Always false. * - * @return bool false + * @see Content::isCountable */ public function isCountable( $hasLinks = null ) { return false; } /** - * @see Content::getParserOutput + * @param Title $title Unused. + * @param int $revId Unused. + * @param ParserOptions $options Unused. + * @param bool $generateHtml Whether to generate HTML (default: true). * * @return ParserOutput + * + * @see Content::getParserOutput */ - public function getParserOutput( - Title $title, $revId = null, - ParserOptions $options = null, $generateHtml = true - ) { - + public function getParserOutput( Title $title, $revId = null, + ParserOptions $options = null, $generateHtml = true ) { if ( $generateHtml ) { $html = $this->getHtml(); } else { @@ -153,6 +165,10 @@ class MessageContent extends AbstractContent { } $po = new ParserOutput( $html ); + // Message objects are in the user language. + $po->recordOption( 'userlang' ); + return $po; } + } diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php index f66dacd7..c479f202 100644 --- a/includes/content/TextContent.php +++ b/includes/content/TextContent.php @@ -34,12 +34,16 @@ */ class TextContent extends AbstractContent { + /** + * @param string $text + * @param string $model_id + */ public function __construct( $text, $model_id = CONTENT_MODEL_TEXT ) { parent::__construct( $model_id ); if ( $text === null || $text === false ) { wfWarn( "TextContent constructed with \$text = " . var_export( $text, true ) . "! " - . "This may indicate an error in the caller's scope." ); + . "This may indicate an error in the caller's scope.", 2 ); $text = ''; } @@ -51,6 +55,11 @@ class TextContent extends AbstractContent { $this->mText = $text; } + /** + * @note Mutable subclasses MUST override this to return a copy! + * + * @return Content $this + */ public function copy() { return $this; # NOTE: this is ok since TextContent are immutable. } @@ -68,12 +77,13 @@ class TextContent extends AbstractContent { } /** - * returns the text's size in bytes. + * Returns the text's size in bytes. * - * @return int The size + * @return int */ public function getSize() { $text = $this->getNativeData(); + return strlen( $text ); } @@ -81,10 +91,10 @@ class TextContent extends AbstractContent { * Returns true if this content is not a redirect, and $wgArticleCountMethod * is "any". * - * @param bool $hasLinks if it is known whether this content contains links, + * @param bool $hasLinks 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 + * @return bool */ public function isCountable( $hasLinks = null ) { global $wgArticleCountMethod; @@ -103,17 +113,16 @@ class TextContent extends AbstractContent { /** * Returns the text represented by this Content object, as a string. * - * @return string: the raw text + * @return string The raw text. */ public function getNativeData() { - $text = $this->mText; - return $text; + return $this->mText; } /** * Returns the text represented by this Content object, as a string. * - * @return string: the raw text + * @return string The raw text. */ public function getTextForSearchIndex() { return $this->getNativeData(); @@ -123,9 +132,9 @@ class TextContent extends AbstractContent { * Returns attempts to convert this content object to wikitext, * and then returns the text string. The conversion may be lossy. * - * @note: this allows any text-based content to be transcluded as if it was wikitext. + * @note this allows any text-based content to be transcluded as if it was wikitext. * - * @return string|false: the raw text, or null if the conversion failed + * @return string|bool The raw text, or false if the conversion failed. */ public function getWikitextForTransclusion() { $wikitext = $this->convert( CONTENT_MODEL_WIKITEXT, 'lossy' ); @@ -141,29 +150,29 @@ class TextContent extends AbstractContent { * Returns a Content object with pre-save transformations applied. * This implementation just trims trailing whitespace. * - * @param $title Title - * @param $user User - * @param $popts ParserOptions + * @param Title $title + * @param User $user + * @param ParserOptions $popts + * * @return Content */ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { $text = $this->getNativeData(); $pst = rtrim( $text ); - return ( $text === $pst ) ? $this : new WikitextContent( $pst ); + return ( $text === $pst ) ? $this : new static( $pst, $this->getModel() ); } /** * Diff this content object with another content object. * - * @since 1.21diff + * @since 1.21 * - * @param $that Content: The other content object to compare this content - * object to. - * @param $lang Language: The language object to use for text segmentation. + * @param Content $that The other content object to compare this content object to. + * @param Language $lang 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 + * @return Diff 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 ) { @@ -178,43 +187,42 @@ class TextContent extends AbstractContent { } $otext = $this->getNativeData(); - $ntext = $this->getNativeData(); + $ntext = $that->getNativeData(); # Note: Use native PHP diff, external engines don't give us abstract output $ota = explode( "\n", $lang->segmentForDiff( $otext ) ); $nta = explode( "\n", $lang->segmentForDiff( $ntext ) ); $diff = new Diff( $ota, $nta ); + return $diff; } /** - * Returns a generic ParserOutput object, wrapping the HTML returned by - * getHtml(). + * Fills the provided ParserOutput object with information derived from the content. + * Unless $generateHtml was false, this includes an HTML representation of the content + * provided by getHtml(). * - * @param $title Title Context title for parsing - * @param int|null $revId Revision ID (for {{REVISIONID}}) - * @param $options ParserOptions|null Parser options - * @param bool $generateHtml Whether or not to generate HTML + * For content models listed in $wgTextModelsToParse, this method will call the MediaWiki + * wikitext parser on the text to extract any (wikitext) links, magic words, etc. * - * @return ParserOutput representing the HTML form of the text + * Subclasses may override this to provide custom content processing. + * For custom HTML generation alone, it is sufficient to override getHtml(). + * + * @param Title $title Context title for parsing + * @param int $revId Revision ID (for {{REVISIONID}}) + * @param ParserOptions $options Parser options + * @param bool $generateHtml Whether or not to generate HTML + * @param ParserOutput $output The output object to fill (reference). */ - public function getParserOutput( Title $title, - $revId = null, - ParserOptions $options = null, $generateHtml = true + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output ) { global $wgParser, $wgTextModelsToParse; - if ( !$options ) { - //NOTE: use canonical options per default to produce cacheable output - $options = $this->getContentHandler()->makeParserOptions( 'canonical' ); - } - if ( in_array( $this->getModel(), $wgTextModelsToParse ) ) { - // parse just to get links etc into the database - $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId ); - } else { - $po = new ParserOutput(); + // parse just to get links etc into the database, HTML is replaced below. + $output = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId ); } if ( $generateHtml ) { @@ -223,18 +231,19 @@ class TextContent extends AbstractContent { $html = ''; } - $po->setText( $html ); - return $po; + $output->setText( $html ); } /** * Generates an HTML version of the content, for display. Used by - * getParserOutput() to construct a ParserOutput object. + * fillParserOutput() to provide HTML for the ParserOutput object. + * + * Subclasses may override this to provide a custom HTML rendering. + * If further information is to be derived from the content (such as + * categories), the fillParserOutput() method can be overridden instead. * - * 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. + * For backwards-compatibility, this default implementation just calls + * getHighlightHtml(). * * @return string An HTML representation of the content */ @@ -243,28 +252,37 @@ class TextContent extends AbstractContent { } /** - * Generates a syntax-highlighted version of the content, as HTML. - * Used by the default implementation of getHtml(). + * Generates an HTML version of the content, for display. + * + * This default implementation returns an HTML-escaped version + * of the raw text content. + * + * @note The functionality of this method should really be implemented + * in getHtml(), and subclasses should override getHtml() if needed. + * getHighlightHtml() is kept around for backward compatibility with + * extensions that already override it. + * + * @deprecated since 1.24. Use getHtml() instead. In particular, subclasses overriding + * getHighlightHtml() should override getHtml() instead. * - * @return string an HTML representation of the content's markup + * @return string An HTML representation of the content */ protected function getHighlightHtml() { - # TODO: make Highlighter interface, use highlighter here, if available return htmlspecialchars( $this->getNativeData() ); } /** - * @see Content::convert() - * * This implementation provides lossless conversion between content models based * on TextContent. * - * @param string $toModel the desired content model, use the CONTENT_MODEL_XXX flags. - * @param string $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is - * not allowed, full round-trip conversion is expected to work without losing information. + * @param string $toModel The desired content model, use the CONTENT_MODEL_XXX flags. + * @param string $lossy Flag, set to "lossy" to allow lossy conversion. If lossy conversion is not + * allowed, full round-trip conversion is expected to work without losing information. * - * @return Content|bool A content object with the content model $toModel, or false if - * that conversion is not supported. + * @return Content|bool A content object with the content model $toModel, or false if that + * conversion is not supported. + * + * @see Content::convert() */ public function convert( $toModel, $lossy = '' ) { $converted = parent::convert( $toModel, $lossy ); @@ -276,11 +294,12 @@ class TextContent extends AbstractContent { $toHandler = ContentHandler::getForModelID( $toModel ); if ( $toHandler instanceof TextContentHandler ) { - //NOTE: ignore content serialization format - it's just text anyway. + // NOTE: ignore content serialization format - it's just text anyway. $text = $this->getNativeData(); $converted = $toHandler->unserializeContent( $text ); } return $converted; } + } diff --git a/includes/content/TextContentHandler.php b/includes/content/TextContentHandler.php index e7f41e18..ffe1acbd 100644 --- a/includes/content/TextContentHandler.php +++ b/includes/content/TextContentHandler.php @@ -30,19 +30,24 @@ */ class TextContentHandler extends ContentHandler { - public function __construct( $modelId = CONTENT_MODEL_TEXT, $formats = array( CONTENT_FORMAT_TEXT ) ) { + // @codingStandardsIgnoreStart bug 57585 + public function __construct( $modelId = CONTENT_MODEL_TEXT, + $formats = array( CONTENT_FORMAT_TEXT ) ) { parent::__construct( $modelId, $formats ); } + // @codingStandardsIgnoreEnd /** * Returns the content's text as-is. * - * @param $content Content - * @param $format string|null + * @param Content $content + * @param string $format The serialization format to check + * * @return mixed */ public function serializeContent( Content $content, $format = null ) { $this->checkFormat( $format ); + return $content->getNativeData(); } @@ -55,11 +60,11 @@ class TextContentHandler extends ContentHandler { * * This text-based implementation uses wfMerge(). * - * @param $oldContent Content|string String - * @param $myContent Content|string String - * @param $yourContent Content|string String + * @param Content $oldContent The page's previous content. + * @param Content $myContent One of the page's conflicting contents. + * @param Content $yourContent One of the page's conflicting contents. * - * @return Content|Bool + * @return Content|bool */ public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) { $this->checkModelID( $oldContent->getModel() ); @@ -83,23 +88,38 @@ class TextContentHandler extends ContentHandler { } $mergedContent = $this->unserializeContent( $result, $format ); + return $mergedContent; } + /** + * Returns the name of the associated Content class, to + * be used when creating new objects. Override expected + * by subclasses. + * + * @since 1.24 + * + * @return string + */ + protected function getContentClass() { + return 'TextContent'; + } + /** * Unserializes a Content object of the type supported by this ContentHandler. * * @since 1.21 * - * @param $text string serialized form of the content - * @param $format null|String the format used for serialization + * @param string $text Serialized form of the content + * @param string $format The format used for serialization * - * @return Content the TextContent object wrapping $text + * @return Content The TextContent object wrapping $text */ public function unserializeContent( $text, $format = null ) { $this->checkFormat( $format ); - return new TextContent( $text ); + $class = $this->getContentClass(); + return new $class( $text ); } /** @@ -107,9 +127,11 @@ class TextContentHandler extends ContentHandler { * * @since 1.21 * - * @return Content + * @return Content A new TextContent object with empty text. */ public function makeEmptyContent() { - return new TextContent( '' ); + $class = $this->getContentClass(); + return new $class( '' ); } + } diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php index 26337db9..9a8ab3a6 100644 --- a/includes/content/WikitextContent.php +++ b/includes/content/WikitextContent.php @@ -31,31 +31,43 @@ * @ingroup Content */ class WikitextContent extends TextContent { + private $redirectTargetAndText = null; public function __construct( $text ) { parent::__construct( $text, CONTENT_MODEL_WIKITEXT ); } /** + * @param string|number $sectionId + * + * @return Content|bool|null + * * @see Content::getSection() */ - public function getSection( $section ) { + public function getSection( $sectionId ) { global $wgParser; $text = $this->getNativeData(); - $sect = $wgParser->getSection( $text, $section, false ); + $sect = $wgParser->getSection( $text, $sectionId, false ); if ( $sect === false ) { return false; } else { - return new WikitextContent( $sect ); + return new static( $sect ); } } /** + * @param string|number|null|bool $sectionId + * @param Content $with + * @param string $sectionTitle + * + * @throws MWException + * @return Content + * * @see Content::replaceSection() */ - public function replaceSection( $section, Content $with, $sectionTitle = '' ) { + public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) { wfProfileIn( __METHOD__ ); $myModelId = $this->getModel(); @@ -71,13 +83,16 @@ class WikitextContent extends TextContent { $oldtext = $this->getNativeData(); $text = $with->getNativeData(); - if ( $section === '' ) { + if ( strval( $sectionId ) === '' ) { wfProfileOut( __METHOD__ ); + return $with; # XXX: copy first? - } if ( $section == 'new' ) { + } + + if ( $sectionId === 'new' ) { # Inserting a new section $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' ) - ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : ''; + ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : ''; if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) { $text = strlen( trim( $oldtext ) ) > 0 ? "{$oldtext}\n\n{$subject}{$text}" @@ -87,12 +102,13 @@ class WikitextContent extends TextContent { # Replacing an existing section; roll out the big guns global $wgParser; - $text = $wgParser->replaceSection( $oldtext, $section, $text ); + $text = $wgParser->replaceSection( $oldtext, $sectionId, $text ); } - $newContent = new WikitextContent( $text ); + $newContent = new static( $text ); wfProfileOut( __METHOD__ ); + return $newContent; } @@ -100,7 +116,8 @@ class WikitextContent extends TextContent { * Returns a new WikitextContent object with the given section heading * prepended. * - * @param $header string + * @param string $header + * * @return Content */ public function addSectionHeader( $header ) { @@ -109,16 +126,17 @@ class WikitextContent extends TextContent { $text .= "\n\n"; $text .= $this->getNativeData(); - return new WikitextContent( $text ); + return new static( $text ); } /** * Returns a Content object with pre-save transformations applied using * Parser::preSaveTransform(). * - * @param $title Title - * @param $user User - * @param $popts ParserOptions + * @param Title $title + * @param User $user + * @param ParserOptions $popts + * * @return Content */ public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { @@ -128,50 +146,58 @@ class WikitextContent extends TextContent { $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts ); rtrim( $pst ); - return ( $text === $pst ) ? $this : new WikitextContent( $pst ); + return ( $text === $pst ) ? $this : new static( $pst ); } /** * Returns a Content object with preload transformations applied (or this * object if no transformations apply). * - * @param $title Title - * @param $popts ParserOptions + * @param Title $title + * @param ParserOptions $popts + * @param array $params + * * @return Content */ - public function preloadTransform( Title $title, ParserOptions $popts ) { + public function preloadTransform( Title $title, ParserOptions $popts, $params = array() ) { global $wgParser; $text = $this->getNativeData(); - $plt = $wgParser->getPreloadText( $text, $title, $popts ); + $plt = $wgParser->getPreloadText( $text, $title, $popts, $params ); - return new WikitextContent( $plt ); + return new static( $plt ); } /** - * Implement redirect extraction for wikitext. + * Extract the redirect target and the remaining text on the page. * - * @return null|Title + * @note migrated here from Title::newFromRedirectInternal() * - * @note: migrated here from Title::newFromRedirectInternal() + * @since 1.23 * - * @see Content::getRedirectTarget - * @see AbstractContent::getRedirectTarget + * @return array List of two elements: Title|null and string. */ - public function getRedirectTarget() { + protected function getRedirectTargetAndText() { global $wgMaxRedirects; + + if ( $this->redirectTargetAndText !== null ) { + return $this->redirectTargetAndText; + } + if ( $wgMaxRedirects < 1 ) { // redirects are disabled, so quit early - return null; + $this->redirectTargetAndText = array( null, $this->getNativeData() ); + return $this->redirectTargetAndText; } + $redir = MagicWord::get( 'redirect' ); - $text = trim( $this->getNativeData() ); + $text = ltrim( $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 ) ) { + if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}\s*!', $text, $m ) ) { // Strip preceding colon used to "escape" categories, etc. // and URL-decode links if ( strpos( $m[1], '%' ) !== false ) { @@ -181,17 +207,33 @@ class WikitextContent extends TextContent { $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; + $this->redirectTargetAndText = array( null, $this->getNativeData() ); + return $this->redirectTargetAndText; } - return $title; + + $this->redirectTargetAndText = array( $title, substr( $text, strlen( $m[0] ) ) ); + return $this->redirectTargetAndText; } } - return null; + + $this->redirectTargetAndText = array( null, $this->getNativeData() ); + return $this->redirectTargetAndText; } /** - * @see Content::updateRedirect() + * Implement redirect extraction for wikitext. * + * @return Title|null + * + * @see Content::getRedirectTarget + */ + public function getRedirectTarget() { + list( $title, ) = $this->getRedirectTargetAndText(); + + return $title; + } + + /** * 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. * @@ -199,7 +241,9 @@ class WikitextContent extends TextContent { * * @param Title $target * - * @return Content a new Content object with the updated redirect (or $this if this Content object isn't a redirect) + * @return Content + * + * @see Content::updateRedirect() */ public function updateRedirect( Title $target ) { if ( !$this->isRedirect() ) { @@ -213,21 +257,19 @@ class WikitextContent extends TextContent { '[[' . $target->getFullText() . ']]', $this->getNativeData(), 1 ); - return new WikitextContent( $newText ); + return new static( $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 bool $hasLinks if it is known whether this content contains + * @param bool $hasLinks If it is known whether this content contains * links, provide this information here, to avoid redundant parsing to * find out (default: null). - * @param $title Title: (default: null) - * - * @internal param \IContextSource $context context for parsing if necessary + * @param Title $title Optional title, defaults to the title from the current main request. * - * @return bool True if the content is countable + * @return bool */ public function isCountable( $hasLinks = null, Title $title = null ) { global $wgArticleCountMethod; @@ -261,6 +303,10 @@ class WikitextContent extends TextContent { return false; } + /** + * @param int $maxlength + * @return string + */ public function getTextForSummary( $maxlength = 250 ) { $truncatedtext = parent::getTextForSummary( $maxlength ); @@ -276,31 +322,39 @@ class WikitextContent extends TextContent { * Returns a ParserOutput object resulting from parsing the content's text * using $wgParser. * - * @since 1.21 - * - * @param $title Title + * @param Title $title * @param int $revId Revision to pass to the parser (default: null) - * @param $options ParserOptions (default: null) - * @param bool $generateHtml (default: false) - * - * @internal param \IContextSource|null $context - * @return ParserOutput representing the HTML form of the text + * @param ParserOptions $options (default: null) + * @param bool $generateHtml (default: true) + * @param ParserOutput &$output ParserOutput representing the HTML form of the text, + * may be manipulated or replaced. */ - public function getParserOutput( Title $title, - $revId = null, - ParserOptions $options = null, $generateHtml = true + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output ) { global $wgParser; - if ( !$options ) { - //NOTE: use canonical options per default to produce cacheable output - $options = $this->getContentHandler()->makeParserOptions( 'canonical' ); + list( $redir, $text ) = $this->getRedirectTargetAndText(); + $output = $wgParser->parse( $text, $title, $options, true, true, $revId ); + + // Add redirect indicator at the top + if ( $redir ) { + // Make sure to include the redirect link in pagelinks + $output->addLink( $redir ); + if ( $generateHtml ) { + $chain = $this->getRedirectChain(); + $output->setText( + Article::getRedirectHeaderHtml( $title->getPageLanguage(), $chain, false ) . + $output->getText() + ); + $output->addModuleStyles( 'mediawiki.action.view.redirectPage' ); + } } - - $po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId ); - return $po; } + /** + * @throws MWException + */ protected function getHtml() { throw new MWException( "getHtml() not implemented for wikitext. " @@ -309,15 +363,16 @@ class WikitextContent extends TextContent { } /** - * @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. + * @return bool + * + * @see Content::matchMagicWord() */ public function matchMagicWord( MagicWord $word ) { return $word->match( $this->getNativeData() ); } + } diff --git a/includes/content/WikitextContentHandler.php b/includes/content/WikitextContentHandler.php index b1b461fb..c1db1de6 100644 --- a/includes/content/WikitextContentHandler.php +++ b/includes/content/WikitextContentHandler.php @@ -34,30 +34,19 @@ class WikitextContentHandler extends TextContentHandler { parent::__construct( $modelId, array( CONTENT_FORMAT_WIKITEXT ) ); } - public function unserializeContent( $text, $format = null ) { - $this->checkFormat( $format ); - - return new WikitextContent( $text ); - } - - /** - * @see ContentHandler::makeEmptyContent - * - * @return Content - */ - public function makeEmptyContent() { - return new WikitextContent( '' ); + protected function getContentClass() { + return 'WikitextContent'; } /** * Returns a WikitextContent object representing a redirect to the given destination page. * - * @see ContentHandler::makeRedirectContent - * - * @param Title $destination the page to redirect to. - * @param string $text text to include in the redirect, if possible. + * @param Title $destination The page to redirect to. + * @param string $text Text to include in the redirect, if possible. * * @return Content + * + * @see ContentHandler::makeRedirectContent */ public function makeRedirectContent( Title $destination, $text = '' ) { $optionalColon = ''; @@ -72,20 +61,23 @@ class WikitextContentHandler extends TextContentHandler { } $mwRedir = MagicWord::get( 'redirect' ); - $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $optionalColon . $destination->getFullText() . ']]'; + $redirectText = $mwRedir->getSynonym( 0 ) . + ' [[' . $optionalColon . $destination->getFullText() . ']]'; + if ( $text != '' ) { $redirectText .= "\n" . $text; } - return new WikitextContent( $redirectText ); + $class = $this->getContentClass(); + return new $class( $redirectText ); } /** * Returns true because wikitext supports redirects. * - * @see ContentHandler::supportsRedirects + * @return bool Always true. * - * @return boolean whether redirects are supported. + * @see ContentHandler::supportsRedirects */ public function supportsRedirects() { return true; @@ -94,7 +86,9 @@ class WikitextContentHandler extends TextContentHandler { /** * Returns true because wikitext supports sections. * - * @return boolean whether sections are supported. + * @return bool Always true. + * + * @see ContentHandler::supportsSections */ public function supportsSections() { return true; @@ -105,9 +99,13 @@ class WikitextContentHandler extends TextContentHandler { * ParserCache mechanism. * * @since 1.21 - * @return bool + * + * @return bool Always true. + * + * @see ContentHandler::isParserCacheSupported */ public function isParserCacheSupported() { return true; } + } -- cgit v1.2.3-54-g00ecf