X-Git-Url: https://git.cyclocoop.org/%28%28?a=blobdiff_plain;f=includes%2Fcontent%2FJsonContent.php;h=1d9ee33804bf2a235f38922b50675719c0349400;hb=a6c17640fc571ebd173e5460bfbda599cf5f5f67;hp=b36827c507824691e4ca71e878fc8e417f378542;hpb=a3872d2efd9cbbaf45a40c653732e7e94844c672;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/content/JsonContent.php b/includes/content/JsonContent.php index b36827c507..1d9ee33804 100644 --- a/includes/content/JsonContent.php +++ b/includes/content/JsonContent.php @@ -14,53 +14,83 @@ */ class JsonContent extends TextContent { + /** + * @since 1.25 + * @var Status + */ + protected $jsonParse; + + /** + * @param string $text JSON + */ public function __construct( $text, $modelId = CONTENT_MODEL_JSON ) { parent::__construct( $text, $modelId ); } /** * Decodes the JSON into a PHP associative array. - * @return array + * + * @deprecated since 1.25 Use getData instead. + * @return array|null */ public function getJsonData() { + wfDeprecated( __METHOD__, '1.25' ); return FormatJson::decode( $this->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 ); } /** * 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 ); + } }