JsonContent.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. /**
  3. * JSON Content Model
  4. *
  5. * @file
  6. *
  7. * @author Ori Livneh <ori@wikimedia.org>
  8. * @author Kunal Mehta <legoktm@gmail.com>
  9. */
  10. /**
  11. * Represents the content of a JSON content.
  12. * @since 1.24
  13. */
  14. class JsonContent extends TextContent {
  15. /**
  16. * @since 1.25
  17. * @var Status
  18. */
  19. protected $jsonParse;
  20. /**
  21. * @param string $text JSON
  22. * @param string $modelId
  23. */
  24. public function __construct( $text, $modelId = CONTENT_MODEL_JSON ) {
  25. parent::__construct( $text, $modelId );
  26. }
  27. /**
  28. * Decodes the JSON into a PHP associative array.
  29. *
  30. * @deprecated since 1.25 Use getData instead.
  31. * @return array|null
  32. */
  33. public function getJsonData() {
  34. wfDeprecated( __METHOD__, '1.25' );
  35. return FormatJson::decode( $this->getNativeData(), true );
  36. }
  37. /**
  38. * Decodes the JSON string.
  39. *
  40. * Note that this parses it without casting objects to associative arrays.
  41. * Objects and arrays are kept as distinguishable types in the PHP values.
  42. *
  43. * @return Status
  44. */
  45. public function getData() {
  46. if ( $this->jsonParse === null ) {
  47. $this->jsonParse = FormatJson::parse( $this->getNativeData() );
  48. }
  49. return $this->jsonParse;
  50. }
  51. /**
  52. * @return bool Whether content is valid.
  53. */
  54. public function isValid() {
  55. return $this->getData()->isGood();
  56. }
  57. /**
  58. * Pretty-print JSON.
  59. *
  60. * If called before validation, it may return JSON "null".
  61. *
  62. * @return string
  63. */
  64. public function beautifyJSON() {
  65. return FormatJson::encode( $this->getData()->getValue(), true, FormatJson::UTF8_OK );
  66. }
  67. /**
  68. * Beautifies JSON prior to save.
  69. *
  70. * @param Title $title
  71. * @param User $user
  72. * @param ParserOptions $popts
  73. * @return JsonContent
  74. */
  75. public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
  76. // FIXME: WikiPage::doEditContent invokes PST before validation. As such, native data
  77. // may be invalid (though PST result is discarded later in that case).
  78. if ( !$this->isValid() ) {
  79. return $this;
  80. }
  81. return new static( self::normalizeLineEndings( $this->beautifyJSON() ) );
  82. }
  83. /**
  84. * Set the HTML and add the appropriate styles.
  85. *
  86. * @param Title $title
  87. * @param int $revId
  88. * @param ParserOptions $options
  89. * @param bool $generateHtml
  90. * @param ParserOutput &$output
  91. */
  92. protected function fillParserOutput( Title $title, $revId,
  93. ParserOptions $options, $generateHtml, ParserOutput &$output
  94. ) {
  95. // FIXME: WikiPage::doEditContent generates parser output before validation.
  96. // As such, native data may be invalid (though output is discarded later in that case).
  97. if ( $generateHtml && $this->isValid() ) {
  98. $output->setText( $this->rootValueTable( $this->getData()->getValue() ) );
  99. $output->addModuleStyles( 'mediawiki.content.json' );
  100. } else {
  101. $output->setText( '' );
  102. }
  103. }
  104. /**
  105. * Construct HTML table representation of any JSON value.
  106. *
  107. * See also valueCell, which is similar.
  108. *
  109. * @param mixed $val
  110. * @return string HTML.
  111. */
  112. protected function rootValueTable( $val ) {
  113. if ( is_object( $val ) ) {
  114. return $this->objectTable( $val );
  115. }
  116. if ( is_array( $val ) ) {
  117. // Wrap arrays in another array so that they're visually boxed in a container.
  118. // Otherwise they are visually indistinguishable from a single value.
  119. return $this->arrayTable( [ $val ] );
  120. }
  121. return Html::rawElement( 'table', [ 'class' => 'mw-json mw-json-single-value' ],
  122. Html::rawElement( 'tbody', [],
  123. Html::rawElement( 'tr', [],
  124. Html::element( 'td', [], $this->primitiveValue( $val ) )
  125. )
  126. )
  127. );
  128. }
  129. /**
  130. * Create HTML table representing a JSON object.
  131. *
  132. * @param stdClass $mapping
  133. * @return string HTML
  134. */
  135. protected function objectTable( $mapping ) {
  136. $rows = [];
  137. $empty = true;
  138. foreach ( $mapping as $key => $val ) {
  139. $rows[] = $this->objectRow( $key, $val );
  140. $empty = false;
  141. }
  142. if ( $empty ) {
  143. $rows[] = Html::rawElement( 'tr', [],
  144. Html::element( 'td', [ 'class' => 'mw-json-empty' ],
  145. wfMessage( 'content-json-empty-object' )->text()
  146. )
  147. );
  148. }
  149. return Html::rawElement( 'table', [ 'class' => 'mw-json' ],
  150. Html::rawElement( 'tbody', [], implode( '', $rows ) )
  151. );
  152. }
  153. /**
  154. * Create HTML table row representing one object property.
  155. *
  156. * @param string $key
  157. * @param mixed $val
  158. * @return string HTML.
  159. */
  160. protected function objectRow( $key, $val ) {
  161. $th = Html::element( 'th', [], $key );
  162. $td = $this->valueCell( $val );
  163. return Html::rawElement( 'tr', [], $th . $td );
  164. }
  165. /**
  166. * Create HTML table representing a JSON array.
  167. *
  168. * @param array $mapping
  169. * @return string HTML
  170. */
  171. protected function arrayTable( $mapping ) {
  172. $rows = [];
  173. $empty = true;
  174. foreach ( $mapping as $val ) {
  175. $rows[] = $this->arrayRow( $val );
  176. $empty = false;
  177. }
  178. if ( $empty ) {
  179. $rows[] = Html::rawElement( 'tr', [],
  180. Html::element( 'td', [ 'class' => 'mw-json-empty' ],
  181. wfMessage( 'content-json-empty-array' )->text()
  182. )
  183. );
  184. }
  185. return Html::rawElement( 'table', [ 'class' => 'mw-json' ],
  186. Html::rawElement( 'tbody', [], implode( "\n", $rows ) )
  187. );
  188. }
  189. /**
  190. * Create HTML table row representing the value in an array.
  191. *
  192. * @param mixed $val
  193. * @return string HTML.
  194. */
  195. protected function arrayRow( $val ) {
  196. $td = $this->valueCell( $val );
  197. return Html::rawElement( 'tr', [], $td );
  198. }
  199. /**
  200. * Construct HTML table cell representing any JSON value.
  201. *
  202. * @param mixed $val
  203. * @return string HTML.
  204. */
  205. protected function valueCell( $val ) {
  206. if ( is_object( $val ) ) {
  207. return Html::rawElement( 'td', [], $this->objectTable( $val ) );
  208. }
  209. if ( is_array( $val ) ) {
  210. return Html::rawElement( 'td', [], $this->arrayTable( $val ) );
  211. }
  212. return Html::element( 'td', [ 'class' => 'value' ], $this->primitiveValue( $val ) );
  213. }
  214. /**
  215. * Construct text representing a JSON primitive value.
  216. *
  217. * @param mixed $val
  218. * @return string Text.
  219. */
  220. protected function primitiveValue( $val ) {
  221. if ( is_string( $val ) ) {
  222. // Don't FormatJson::encode for strings since we want quotes
  223. // and new lines to render visually instead of escaped.
  224. return '"' . $val . '"';
  225. }
  226. return FormatJson::encode( $val );
  227. }
  228. }