Merge "resourceloader: Inverse hasOwn() check to fix require in debug mode"
[lhc/web/wiklou.git] / includes / content / JsonContent.php
index df90f22..40d9277 100644 (file)
@@ -2,8 +2,6 @@
 /**
  * JSON Content Model
  *
- * This class requires the root structure to be an object (not primitives or arrays).
- *
  * @file
  *
  * @author Ori Livneh <ori@wikimedia.org>
@@ -41,7 +39,10 @@ class JsonContent extends TextContent {
        }
 
        /**
-        * Decodes the JSON string into a PHP object.
+        * 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
         */
@@ -56,7 +57,7 @@ class JsonContent extends TextContent {
         * @return bool Whether content is valid.
         */
        public function isValid() {
-               return $this->getData()->isGood() && is_object( $this->getData()->getValue() );
+               return $this->getData()->isGood();
        }
 
        /**
@@ -67,7 +68,7 @@ class JsonContent extends TextContent {
         * @return string
         */
        public function beautifyJSON() {
-               return FormatJson::encode( $this->getData()->getValue(), true );
+               return FormatJson::encode( $this->getData()->getValue(), true, FormatJson::UTF8_OK );
        }
 
        /**
@@ -103,7 +104,7 @@ class JsonContent extends TextContent {
                // 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->objectTable( $this->getData()->getValue() ) );
+                       $output->setText( $this->rootValueTable( $this->getData()->getValue() ) );
                        $output->addModuleStyles( 'mediawiki.content.json' );
                } else {
                        $output->setText( '' );
@@ -111,15 +112,41 @@ class JsonContent extends TextContent {
        }
 
        /**
-        * Construct an HTML representation of a JSON object.
+        * Construct HTML table representation of any JSON value.
         *
-        * Called recursively via valueCell().
+        * 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 = array();
+               $rows = [];
                $empty = true;
 
                foreach ( $mapping as $key => $val ) {
@@ -127,39 +154,38 @@ class JsonContent extends TextContent {
                        $empty = false;
                }
                if ( $empty ) {
-                       $rows[] = Html::rawElement( 'tr', array(),
-                               Html::element( 'td', array( 'class' => 'mw-json-empty' ),
+                       $rows[] = Html::rawElement( 'tr', [],
+                               Html::element( 'td', [ 'class' => 'mw-json-empty' ],
                                        wfMessage( 'content-json-empty-object' )->text()
                                )
                        );
                }
-               return Html::rawElement( 'table', array( 'class' => 'mw-json' ),
-                       Html::rawElement( 'tbody', array(), join( "\n", $rows ) )
+               return Html::rawElement( 'table', [ 'class' => 'mw-json' ],
+                       Html::rawElement( 'tbody', [], implode( '', $rows ) )
                );
        }
 
        /**
-        * Construct 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 );
-               $td = self::valueCell( $val );
-               return Html::rawElement( 'tr', array(), $th . $td );
+               $th = Html::element( 'th', [], $key );
+               $td = $this->valueCell( $val );
+               return Html::rawElement( 'tr', [], $th . $td );
        }
 
        /**
-        * Constructs an HTML representation of a JSON array.
-        *
-        * Called recursively via valueCell().
+        * Create HTML table representing a JSON array.
         *
         * @param array $mapping
         * @return string HTML
         */
        protected function arrayTable( $mapping ) {
-               $rows = array();
+               $rows = [];
                $empty = true;
 
                foreach ( $mapping as $val ) {
@@ -167,45 +193,58 @@ class JsonContent extends TextContent {
                        $empty = false;
                }
                if ( $empty ) {
-                       $rows[] = Html::rawElement( 'tr', array(),
-                               Html::element( 'td', array( 'class' => 'mw-json-empty' ),
+                       $rows[] = Html::rawElement( 'tr', [],
+                               Html::element( 'td', [ '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 Html::rawElement( 'table', [ 'class' => 'mw-json' ],
+                       Html::rawElement( 'tbody', [], implode( "\n", $rows ) )
                );
        }
 
        /**
-        * Construct HTML representation of a single array value.
+        * 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 );
+               $td = $this->valueCell( $val );
+               return Html::rawElement( 'tr', [], $td );
        }
 
        /**
-        * Construct HTML representation of a single value.
+        * 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 ) );
+                       return Html::rawElement( 'td', [], $this->objectTable( $val ) );
                }
+
                if ( is_array( $val ) ) {
-                       return Html::rawElement( 'td', array(), self::arrayTable( $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 ) ) {
-                       $val = '"' . $val . '"';
-               } else {
-                       $val = FormatJson::encode( $val );
+                       // Don't FormatJson::encode for strings since we want quotes
+                       // and new lines to render visually instead of escaped.
+                       return '"' . $val . '"';
                }
-
-               return Xml::elementClean( 'td', array( 'class' => 'value' ), $val );
+               return FormatJson::encode( $val );
        }
 }