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