X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/includes/content/JsonContent.php diff --git a/includes/content/JsonContent.php b/includes/content/JsonContent.php new file mode 100644 index 00000000..2b94f3f7 --- /dev/null +++ b/includes/content/JsonContent.php @@ -0,0 +1,251 @@ + + * @author Kunal Mehta + */ + +/** + * Represents the content of a JSON content. + * @since 1.24 + */ +class JsonContent extends TextContent { + + /** + * @since 1.25 + * @var Status + */ + protected $jsonParse; + + /** + * @param string $text JSON + * @param string $modelId + */ + public function __construct( $text, $modelId = CONTENT_MODEL_JSON ) { + parent::__construct( $text, $modelId ); + } + + /** + * Decodes the JSON into a PHP associative array. + * + * @deprecated since 1.25 Use getData instead. + * @return array|null + */ + public function getJsonData() { + wfDeprecated( __METHOD__, '1.25' ); + return FormatJson::decode( $this->getNativeData(), true ); + } + + /** + * 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->getData()->isGood(); + } + + /** + * Pretty-print JSON. + * + * If called before validation, it may return JSON "null". + * + * @return string + */ + public function beautifyJSON() { + 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( self::normalizeLineEndings( $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 + ) { + // 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( '' ); + } + } + + /** + * 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 $this->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 $this->arrayTable( [ $val ] ); + } + + return Html::rawElement( 'table', [ 'class' => 'mw-json mw-json-single-value' ], + Html::rawElement( 'tbody', [], + Html::rawElement( 'tr', [], + Html::element( 'td', [], $this->primitiveValue( $val ) ) + ) + ) + ); + } + + /** + * Create HTML table representing a JSON object. + * + * @param stdClass $mapping + * @return string HTML + */ + protected function objectTable( $mapping ) { + $rows = []; + $empty = true; + + foreach ( $mapping as $key => $val ) { + $rows[] = $this->objectRow( $key, $val ); + $empty = false; + } + if ( $empty ) { + $rows[] = Html::rawElement( 'tr', [], + Html::element( 'td', [ 'class' => 'mw-json-empty' ], + wfMessage( 'content-json-empty-object' )->text() + ) + ); + } + return Html::rawElement( 'table', [ 'class' => 'mw-json' ], + Html::rawElement( 'tbody', [], implode( '', $rows ) ) + ); + } + + /** + * Create HTML table row representing one object property. + * + * @param string $key + * @param mixed $val + * @return string HTML. + */ + protected function objectRow( $key, $val ) { + $th = Html::element( 'th', [], $key ); + $td = $this->valueCell( $val ); + return Html::rawElement( 'tr', [], $th . $td ); + } + + /** + * Create HTML table representing a JSON array. + * + * @param array $mapping + * @return string HTML + */ + protected function arrayTable( $mapping ) { + $rows = []; + $empty = true; + + foreach ( $mapping as $val ) { + $rows[] = $this->arrayRow( $val ); + $empty = false; + } + if ( $empty ) { + $rows[] = Html::rawElement( 'tr', [], + Html::element( 'td', [ 'class' => 'mw-json-empty' ], + wfMessage( 'content-json-empty-array' )->text() + ) + ); + } + return Html::rawElement( 'table', [ 'class' => 'mw-json' ], + Html::rawElement( 'tbody', [], implode( "\n", $rows ) ) + ); + } + + /** + * Create HTML table row representing the value in an array. + * + * @param mixed $val + * @return string HTML. + */ + protected function arrayRow( $val ) { + $td = $this->valueCell( $val ); + return Html::rawElement( 'tr', [], $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', [], $this->objectTable( $val ) ); + } + + if ( is_array( $val ) ) { + return Html::rawElement( 'td', [], $this->arrayTable( $val ) ); + } + + return Html::element( 'td', [ 'class' => 'value' ], $this->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 ); + } +}