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