From f6d65e533c62f6deb21342d4901ece24497b433e Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Thu, 4 Jun 2015 07:31:04 +0200 Subject: Update to MediaWiki 1.25.1 --- includes/content/AbstractContent.php | 27 ++-- includes/content/CodeContentHandler.php | 1 + includes/content/Content.php | 7 +- includes/content/ContentHandler.php | 23 +-- includes/content/JavaScriptContentHandler.php | 8 +- includes/content/JsonContent.php | 192 +++++++++++++++++++++----- includes/content/JsonContentHandler.php | 24 +++- includes/content/TextContent.php | 1 + includes/content/WikitextContent.php | 7 +- 9 files changed, 227 insertions(+), 63 deletions(-) (limited to 'includes/content') diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php index 9d257a6a..6542d7df 100644 --- a/includes/content/AbstractContent.php +++ b/includes/content/AbstractContent.php @@ -204,13 +204,13 @@ abstract class AbstractContent implements Content { * 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. + * This default implementation returns a LinksUpdate object and calls the + * SecondaryDataUpdates hook. * * Subclasses may override this to determine the secondary data updates more * efficiently, preferably without the need to generate a parser output object. + * They should however make sure to call SecondaryDataUpdates to give extensions + * a chance to inject additional updates. * * @since 1.21 * @@ -224,12 +224,19 @@ abstract class AbstractContent implements Content { * @see Content::getSecondaryDataUpdates() */ public function getSecondaryDataUpdates( Title $title, Content $old = null, - $recursive = true, ParserOutput $parserOutput = null ) { + $recursive = true, ParserOutput $parserOutput = null + ) { if ( $parserOutput === null ) { $parserOutput = $this->getParserOutput( $title, null, null, false ); } - return $parserOutput->getSecondaryDataUpdates( $title, $recursive ); + $updates = array( + new LinksUpdate( $title, $parserOutput, $recursive ) + ); + + Hooks::run( 'SecondaryDataUpdates', array( $title, $old, $recursive, $parserOutput, &$updates ) ); + + return $updates; } /** @@ -386,7 +393,7 @@ abstract class AbstractContent implements Content { * * @see Content::prepareSave */ - public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ) { + public function prepareSave( WikiPage $page, $flags, $parentRevId, User $user ) { if ( $this->isValid() ) { return Status::newGood(); } else { @@ -446,7 +453,7 @@ abstract class AbstractContent implements Content { $lossy = ( $lossy === 'lossy' ); // string flag, convert to boolean for convenience $result = false; - wfRunHooks( 'ConvertContent', array( $this, $toModel, $lossy, &$result ) ); + Hooks::run( 'ConvertContent', array( $this, $toModel, $lossy, &$result ) ); return $result; } @@ -480,7 +487,7 @@ abstract class AbstractContent implements Content { $po = new ParserOutput(); - if ( wfRunHooks( 'ContentGetParserOutput', + if ( Hooks::run( 'ContentGetParserOutput', array( $this, $title, $revId, $options, $generateHtml, &$po ) ) ) { // Save and restore the old value, just in case something is reusing @@ -491,6 +498,8 @@ abstract class AbstractContent implements Content { $options->setRedirectTarget( $oldRedir ); } + Hooks::run( 'ContentAlterParserOutput', array( $this, $title, $po ) ); + return $po; } diff --git a/includes/content/CodeContentHandler.php b/includes/content/CodeContentHandler.php index 447a2a73..694b633e 100644 --- a/includes/content/CodeContentHandler.php +++ b/includes/content/CodeContentHandler.php @@ -58,6 +58,7 @@ abstract class CodeContentHandler extends TextContentHandler { /** * @return string + * @throws MWException */ protected function getContentClass() { throw new MWException( 'Subclass must override' ); diff --git a/includes/content/Content.php b/includes/content/Content.php index 61b9254f..8ed761f5 100644 --- a/includes/content/Content.php +++ b/includes/content/Content.php @@ -292,6 +292,9 @@ interface Content { * Subclasses may implement this to determine the necessary updates more * efficiently, or make use of information about the old content. * + * @note Implementations should call the SecondaryDataUpdates hook, like + * AbstractContent does. + * * @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 @@ -461,7 +464,7 @@ interface Content { * * @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 int $parentRevId The ID of the current revision * @param User $user * * @return Status A status object indicating whether the content was @@ -470,7 +473,7 @@ interface Content { * * @see WikiPage::doEditContent() */ - public function prepareSave( WikiPage $page, $flags, $baseRevId, User $user ); + public function prepareSave( WikiPage $page, $flags, $parentRevId, User $user ); /** * Returns a list of updates to perform when this content is deleted. diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php index ac417223..371b267e 100644 --- a/includes/content/ContentHandler.php +++ b/includes/content/ContentHandler.php @@ -201,7 +201,7 @@ abstract class ContentHandler { $model = MWNamespace::getNamespaceContentModel( $ns ); // Hook can determine default model - if ( !wfRunHooks( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) { + if ( !Hooks::run( 'ContentHandlerDefaultModelFor', array( $title, &$model ) ) ) { if ( !is_null( $model ) ) { return $model; } @@ -214,7 +214,7 @@ abstract class ContentHandler { } // Hook can force JS/CSS - wfRunHooks( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ) ); + Hooks::run( 'TitleIsCssOrJsPage', array( $title, &$isCssOrJsPage ), '1.25' ); // Is this a .css subpage of a user page? $isJsCssSubpage = NS_USER == $ns @@ -229,7 +229,7 @@ abstract class ContentHandler { $isWikitext = $isWikitext && !$isCssOrJsPage && !$isJsCssSubpage; // Hook can override $isWikitext - wfRunHooks( 'TitleIsWikitextPage', array( $title, &$isWikitext ) ); + Hooks::run( 'TitleIsWikitextPage', array( $title, &$isWikitext ), '1.25' ); if ( !$isWikitext ) { switch ( $ext ) { @@ -318,7 +318,7 @@ abstract class ContentHandler { if ( empty( $wgContentHandlers[$modelId] ) ) { $handler = null; - wfRunHooks( 'ContentHandlerForModelID', array( $modelId, &$handler ) ); + Hooks::run( 'ContentHandlerForModelID', array( $modelId, &$handler ) ); if ( $handler === null ) { throw new MWException( "No handler for model '$modelId' registered in \$wgContentHandlers" ); @@ -626,8 +626,15 @@ abstract class ContentHandler { public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0, $rcid = 0, //FIXME: Deprecated, no longer used $refreshCache = false, $unhide = false ) { - $diffEngineClass = $this->getDiffEngineClass(); + // hook: get difference engine + $differenceEngine = null; + if ( !wfRunHooks( 'GetDifferenceEngine', + array( $context, $old, $new, $refreshCache, $unhide, &$differenceEngine ) + ) ) { + return $differenceEngine; + } + $diffEngineClass = $this->getDiffEngineClass(); return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide ); } @@ -660,7 +667,7 @@ abstract class ContentHandler { $pageLang = wfGetLangObj( $lang ); } - wfRunHooks( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) ); + Hooks::run( 'PageContentLanguage', array( $title, &$pageLang, $wgLang ) ); return wfGetLangObj( $pageLang ); } @@ -719,7 +726,7 @@ abstract class ContentHandler { public function canBeUsedOn( Title $title ) { $ok = true; - wfRunHooks( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) ); + Hooks::run( 'ContentModelCanBeUsedOn', array( $this->getModelID(), $title, &$ok ) ); return $ok; } @@ -1151,7 +1158,7 @@ abstract class ContentHandler { } // call the hook functions - $ok = wfRunHooks( $event, $args ); + $ok = Hooks::run( $event, $args ); // see if the hook changed the text foreach ( $contentTexts as $k => $orig ) { diff --git a/includes/content/JavaScriptContentHandler.php b/includes/content/JavaScriptContentHandler.php index 457b83d7..d2218971 100644 --- a/includes/content/JavaScriptContentHandler.php +++ b/includes/content/JavaScriptContentHandler.php @@ -1,7 +1,5 @@ getNativeData(), true ); } /** - * @return bool Whether content is valid JSON. + * Decodes the JSON string. + * + * Note that this parses it without casting objects to associative arrays. + * Objects and arrays are kept as distinguishable types in the PHP values. + * + * @return Status + */ + public function getData() { + if ( $this->jsonParse === null ) { + $this->jsonParse = FormatJson::parse( $this->getNativeData() ); + } + return $this->jsonParse; + } + + /** + * @return bool Whether content is valid. */ public function isValid() { - return $this->getJsonData() !== null; + return $this->getData()->isGood(); } /** - * Pretty-print JSON + * Pretty-print JSON. * - * @return bool|null|string + * If called before validation, it may return JSON "null". + * + * @return string */ public function beautifyJSON() { - $decoded = FormatJson::decode( $this->getNativeData(), true ); - if ( !is_array( $decoded ) ) { - return null; - } - return FormatJson::encode( $decoded, true ); - + return FormatJson::encode( $this->getData()->getValue(), true, FormatJson::UTF8_OK ); } /** * 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 ) { + // FIXME: WikiPage::doEditContent invokes PST before validation. As such, native data + // may be invalid (though PST result is discarded later in that case). + if ( !$this->isValid() ) { + return $this; + } + return new static( $this->beautifyJSON() ); } /** - * Set the HTML and add the appropriate styles - * + * Set the HTML and add the appropriate styles. * * @param Title $title * @param int $revId @@ -71,50 +101,150 @@ class JsonContent extends TextContent { protected function fillParserOutput( Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output ) { - if ( $generateHtml ) { - $output->setText( $this->objectTable( $this->getJsonData() ) ); + // FIXME: WikiPage::doEditContent generates parser output before validation. + // As such, native data may be invalid (though output is discarded later in that case). + if ( $generateHtml && $this->isValid() ) { + $output->setText( $this->rootValueTable( $this->getData()->getValue() ) ); $output->addModuleStyles( 'mediawiki.content.json' ); } else { $output->setText( '' ); } } + /** - * Constructs an HTML representation of a JSON object. - * @param array $mapping + * Construct HTML table representation of any JSON value. + * + * See also valueCell, which is similar. + * + * @param mixed $val + * @return string HTML. + */ + protected function rootValueTable( $val ) { + if ( is_object( $val ) ) { + return self::objectTable( $val ); + } + + if ( is_array( $val ) ) { + // Wrap arrays in another array so that they're visually boxed in a container. + // Otherwise they are visually indistinguishable from a single value. + return self::arrayTable( array( $val ) ); + } + + return Html::rawElement( 'table', array( 'class' => 'mw-json mw-json-single-value' ), + Html::rawElement( 'tbody', array(), + Html::rawElement( 'tr', array(), + Html::element( 'td', array(), self::primitiveValue( $val ) ) + ) + ) + ); + } + + /** + * Create HTML table representing a JSON object. + * + * @param stdClass $mapping * @return string HTML */ protected function objectTable( $mapping ) { $rows = array(); + $empty = true; foreach ( $mapping as $key => $val ) { $rows[] = $this->objectRow( $key, $val ); + $empty = false; + } + if ( $empty ) { + $rows[] = Html::rawElement( 'tr', array(), + Html::element( 'td', array( 'class' => 'mw-json-empty' ), + wfMessage( 'content-json-empty-object' )->text() + ) + ); } - return Xml::tags( 'table', array( 'class' => 'mw-json' ), - Xml::tags( 'tbody', array(), join( "\n", $rows ) ) + return Html::rawElement( 'table', array( 'class' => 'mw-json' ), + Html::rawElement( 'tbody', array(), join( '', $rows ) ) ); } /** - * Constructs HTML representation of a single key-value pair. + * Create HTML table row representing one object property. + * * @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 ); - } + $th = Html::element( 'th', array(), $key ); + $td = self::valueCell( $val ); + return Html::rawElement( 'tr', array(), $th . $td ); + } - $td = Xml::elementClean( 'td', array( 'class' => 'value' ), $val ); + /** + * Create HTML table representing a JSON array. + * + * @param array $mapping + * @return string HTML + */ + protected function arrayTable( $mapping ) { + $rows = array(); + $empty = true; + + foreach ( $mapping as $val ) { + $rows[] = $this->arrayRow( $val ); + $empty = false; + } + if ( $empty ) { + $rows[] = Html::rawElement( 'tr', array(), + Html::element( 'td', array( 'class' => 'mw-json-empty' ), + wfMessage( 'content-json-empty-array' )->text() + ) + ); } + return Html::rawElement( 'table', array( 'class' => 'mw-json' ), + Html::rawElement( 'tbody', array(), join( "\n", $rows ) ) + ); + } - return Xml::tags( 'tr', array(), $th . $td ); + /** + * Create HTML table row representing the value in an array. + * + * @param mixed $val + * @return string HTML. + */ + protected function arrayRow( $val ) { + $td = self::valueCell( $val ); + return Html::rawElement( 'tr', array(), $td ); } + /** + * Construct HTML table cell representing any JSON value. + * + * @param mixed $val + * @return string HTML. + */ + protected function valueCell( $val ) { + if ( is_object( $val ) ) { + return Html::rawElement( 'td', array(), self::objectTable( $val ) ); + } + + if ( is_array( $val ) ) { + return Html::rawElement( 'td', array(), self::arrayTable( $val ) ); + } + + return Html::element( 'td', array( 'class' => 'value' ), self::primitiveValue( $val ) ); + } + + /** + * Construct text representing a JSON primitive value. + * + * @param mixed $val + * @return string Text. + */ + protected function primitiveValue( $val ) { + if ( is_string( $val ) ) { + // Don't FormatJson::encode for strings since we want quotes + // and new lines to render visually instead of escaped. + return '"' . $val . '"'; + } + return FormatJson::encode( $val ); + } } diff --git a/includes/content/JsonContentHandler.php b/includes/content/JsonContentHandler.php index 392ce37b..b149f528 100644 --- a/includes/content/JsonContentHandler.php +++ b/includes/content/JsonContentHandler.php @@ -1,15 +1,31 @@ - * @author Kunal Mehta + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file */ /** + * Content handler for JSON. + * + * @author Ori Livneh + * @author Kunal Mehta + * * @since 1.24 + * @ingroup Content */ class JsonContentHandler extends CodeContentHandler { diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php index c479f202..baea8125 100644 --- a/includes/content/TextContent.php +++ b/includes/content/TextContent.php @@ -37,6 +37,7 @@ class TextContent extends AbstractContent { /** * @param string $text * @param string $model_id + * @throws MWException */ public function __construct( $text, $model_id = CONTENT_MODEL_TEXT ) { parent::__construct( $model_id ); diff --git a/includes/content/WikitextContent.php b/includes/content/WikitextContent.php index 9a8ab3a6..dbe09f91 100644 --- a/includes/content/WikitextContent.php +++ b/includes/content/WikitextContent.php @@ -68,13 +68,11 @@ class WikitextContent extends TextContent { * @see Content::replaceSection() */ public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) { - wfProfileIn( __METHOD__ ); $myModelId = $this->getModel(); $sectionModelId = $with->getModel(); if ( $sectionModelId != $myModelId ) { - wfProfileOut( __METHOD__ ); throw new MWException( "Incompatible content model for section: " . "document uses $myModelId but " . "section uses $sectionModelId." ); @@ -84,7 +82,6 @@ class WikitextContent extends TextContent { $text = $with->getNativeData(); if ( strval( $sectionId ) === '' ) { - wfProfileOut( __METHOD__ ); return $with; # XXX: copy first? } @@ -93,7 +90,7 @@ class WikitextContent extends TextContent { # Inserting a new section $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' ) ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : ''; - if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) { + if ( Hooks::run( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) { $text = strlen( trim( $oldtext ) ) > 0 ? "{$oldtext}\n\n{$subject}{$text}" : "{$subject}{$text}"; @@ -107,8 +104,6 @@ class WikitextContent extends TextContent { $newContent = new static( $text ); - wfProfileOut( __METHOD__ ); - return $newContent; } -- cgit v1.2.3-54-g00ecf