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