[!!!][TASK] Use correct ISO8601 format
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Mvc / View / JsonView.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\View;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Extbase\Mvc\Web\Response as WebResponse;
18 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
19
20 /**
21 * A JSON view
22 *
23 * @api
24 */
25 class JsonView extends AbstractView
26 {
27 /**
28 * Definition for the class name exposure configuration,
29 * that is, if the class name of an object should also be
30 * part of the output JSON, if configured.
31 *
32 * Setting this value, the object's class name is fully
33 * put out, including the namespace.
34 */
35 const EXPOSE_CLASSNAME_FULLY_QUALIFIED = 1;
36
37 /**
38 * Puts out only the actual class name without namespace.
39 * See EXPOSE_CLASSNAME_FULL for the meaning of the constant at all.
40 */
41 const EXPOSE_CLASSNAME_UNQUALIFIED = 2;
42
43 /**
44 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
45 */
46 protected $reflectionService;
47
48 /**
49 * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
50 */
51 protected $controllerContext;
52
53 /**
54 * Only variables whose name is contained in this array will be rendered
55 *
56 * @var array
57 */
58 protected $variablesToRender = ['value'];
59
60 /**
61 * The rendering configuration for this JSON view which
62 * determines which properties of each variable to render.
63 *
64 * The configuration array must have the following structure:
65 *
66 * Example 1:
67 *
68 * array(
69 * 'variable1' => array(
70 * '_only' => array('property1', 'property2', ...)
71 * ),
72 * 'variable2' => array(
73 * '_exclude' => array('property3', 'property4, ...)
74 * ),
75 * 'variable3' => array(
76 * '_exclude' => array('secretTitle'),
77 * '_descend' => array(
78 * 'customer' => array(
79 * '_only' => array('firstName', 'lastName')
80 * )
81 * )
82 * ),
83 * 'somearrayvalue' => array(
84 * '_descendAll' => array(
85 * '_only' => array('property1')
86 * )
87 * )
88 * )
89 *
90 * Of variable1 only property1 and property2 will be included.
91 * Of variable2 all properties except property3 and property4
92 * are used.
93 * Of variable3 all properties except secretTitle are included.
94 *
95 * If a property value is an array or object, it is not included
96 * by default. If, however, such a property is listed in a "_descend"
97 * section, the renderer will descend into this sub structure and
98 * include all its properties (of the next level).
99 *
100 * The configuration of each property in "_descend" has the same syntax
101 * like at the top level. Therefore - theoretically - infinitely nested
102 * structures can be configured.
103 *
104 * To export indexed arrays the "_descendAll" section can be used to
105 * include all array keys for the output. The configuration inside a
106 * "_descendAll" will be applied to each array element.
107 *
108 *
109 * Example 2: exposing object identifier
110 *
111 * array(
112 * 'variableFoo' => array(
113 * '_exclude' => array('secretTitle'),
114 * '_descend' => array(
115 * 'customer' => array( // consider 'customer' being a persisted entity
116 * '_only' => array('firstName'),
117 * '_exposeObjectIdentifier' => TRUE,
118 * '_exposedObjectIdentifierKey' => 'guid'
119 * )
120 * )
121 * )
122 * )
123 *
124 * Note for entity objects you are able to expose the object's identifier
125 * also, just add an "_exposeObjectIdentifier" directive set to TRUE and
126 * an additional property '__identity' will appear keeping the persistence
127 * identifier. Renaming that property name instead of '__identity' is also
128 * possible with the directive "_exposedObjectIdentifierKey".
129 * Example 2 above would output (summarized):
130 * {"customer":{"firstName":"John","guid":"892693e4-b570-46fe-af71-1ad32918fb64"}}
131 *
132 *
133 * Example 3: exposing object's class name
134 *
135 * array(
136 * 'variableFoo' => array(
137 * '_exclude' => array('secretTitle'),
138 * '_descend' => array(
139 * 'customer' => array( // consider 'customer' being an object
140 * '_only' => array('firstName'),
141 * '_exposeClassName' => TYPO3\Flow\Mvc\View\JsonView::EXPOSE_CLASSNAME_FULLY_QUALIFIED
142 * )
143 * )
144 * )
145 * )
146 *
147 * The ``_exposeClassName`` is similar to the objectIdentifier one, but the class name is added to the
148 * JSON object output, for example (summarized):
149 * {"customer":{"firstName":"John","__class":"Acme\Foo\Domain\Model\Customer"}}
150 *
151 * The other option is EXPOSE_CLASSNAME_UNQUALIFIED which only will give the last part of the class
152 * without the namespace, for example (summarized):
153 * {"customer":{"firstName":"John","__class":"Customer"}}
154 * This might be of interest to not provide information about the package or domain structure behind.
155 *
156 * @var array
157 */
158 protected $configuration = [];
159
160 /**
161 * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
162 */
163 protected $persistenceManager;
164
165 /**
166 * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
167 */
168 public function injectPersistenceManager(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager)
169 {
170 $this->persistenceManager = $persistenceManager;
171 }
172
173 /**
174 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
175 */
176 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
177 {
178 $this->reflectionService = $reflectionService;
179 }
180
181 /**
182 * Specifies which variables this JsonView should render
183 * By default only the variable 'value' will be rendered
184 *
185 * @param array $variablesToRender
186 * @return void
187 * @api
188 */
189 public function setVariablesToRender(array $variablesToRender)
190 {
191 $this->variablesToRender = $variablesToRender;
192 }
193
194 /**
195 * @param array $configuration The rendering configuration for this JSON view
196 * @return void
197 */
198 public function setConfiguration(array $configuration)
199 {
200 $this->configuration = $configuration;
201 }
202
203 /**
204 * Transforms the value view variable to a serializable
205 * array representation using a YAML view configuration and JSON encodes
206 * the result.
207 *
208 * @return string The JSON encoded variables
209 * @api
210 */
211 public function render()
212 {
213 $response = $this->controllerContext->getResponse();
214 if ($response instanceof WebResponse) {
215 // @todo Ticket: #63643 This should be solved differently once request/response model is available for TSFE.
216 if (!empty($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) {
217 /** @var TypoScriptFrontendController $typoScriptFrontendController */
218 $typoScriptFrontendController = $GLOBALS['TSFE'];
219 if (empty($typoScriptFrontendController->config['config']['disableCharsetHeader'])) {
220 // If the charset header is *not* disabled in configuration,
221 // TypoScriptFrontendController will send the header later with the Content-Type which we set here.
222 $typoScriptFrontendController->setContentType('application/json');
223 } else {
224 // Although the charset header is disabled in configuration, we *must* send a Content-Type header here.
225 // Content-Type headers optionally carry charset information at the same time.
226 // Since we have the information about the charset, there is no reason to not include the charset information although disabled in TypoScript.
227 $response->setHeader('Content-Type', 'application/json; charset=' . trim($typoScriptFrontendController->metaCharset));
228 }
229 } else {
230 $response->setHeader('Content-Type', 'application/json');
231 }
232 }
233 $propertiesToRender = $this->renderArray();
234 return json_encode($propertiesToRender);
235 }
236
237 /**
238 * Loads the configuration and transforms the value to a serializable
239 * array.
240 *
241 * @return array An array containing the values, ready to be JSON encoded
242 * @api
243 */
244 protected function renderArray()
245 {
246 if (count($this->variablesToRender) === 1) {
247 $variableName = current($this->variablesToRender);
248 $valueToRender = isset($this->variables[$variableName]) ? $this->variables[$variableName] : null;
249 $configuration = isset($this->configuration[$variableName]) ? $this->configuration[$variableName] : [];
250 } else {
251 $valueToRender = [];
252 foreach ($this->variablesToRender as $variableName) {
253 $valueToRender[$variableName] = isset($this->variables[$variableName]) ? $this->variables[$variableName] : null;
254 }
255 $configuration = $this->configuration;
256 }
257 return $this->transformValue($valueToRender, $configuration);
258 }
259
260 /**
261 * Transforms a value depending on type recursively using the
262 * supplied configuration.
263 *
264 * @param mixed $value The value to transform
265 * @param array $configuration Configuration for transforming the value
266 * @return array The transformed value
267 */
268 protected function transformValue($value, array $configuration)
269 {
270 if (is_array($value) || $value instanceof \ArrayAccess) {
271 $array = [];
272 foreach ($value as $key => $element) {
273 if (isset($configuration['_descendAll']) && is_array($configuration['_descendAll'])) {
274 $array[$key] = $this->transformValue($element, $configuration['_descendAll']);
275 } else {
276 if (isset($configuration['_only']) && is_array($configuration['_only']) && !in_array($key, $configuration['_only'])) {
277 continue;
278 }
279 if (isset($configuration['_exclude']) && is_array($configuration['_exclude']) && in_array($key, $configuration['_exclude'])) {
280 continue;
281 }
282 $array[$key] = $this->transformValue($element, isset($configuration[$key]) ? $configuration[$key] : []);
283 }
284 }
285 return $array;
286 } elseif (is_object($value)) {
287 return $this->transformObject($value, $configuration);
288 } else {
289 return $value;
290 }
291 }
292
293 /**
294 * Traverses the given object structure in order to transform it into an
295 * array structure.
296 *
297 * @param object $object Object to traverse
298 * @param array $configuration Configuration for transforming the given object or NULL
299 * @return array Object structure as an array
300 */
301 protected function transformObject($object, array $configuration)
302 {
303 if ($object instanceof \DateTime) {
304 return $object->format(\DateTime::ATOM);
305 } else {
306 $propertyNames = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getGettablePropertyNames($object);
307
308 $propertiesToRender = [];
309 foreach ($propertyNames as $propertyName) {
310 if (isset($configuration['_only']) && is_array($configuration['_only']) && !in_array($propertyName, $configuration['_only'])) {
311 continue;
312 }
313 if (isset($configuration['_exclude']) && is_array($configuration['_exclude']) && in_array($propertyName, $configuration['_exclude'])) {
314 continue;
315 }
316
317 $propertyValue = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getProperty($object, $propertyName);
318
319 if (!is_array($propertyValue) && !is_object($propertyValue)) {
320 $propertiesToRender[$propertyName] = $propertyValue;
321 } elseif (isset($configuration['_descend']) && array_key_exists($propertyName, $configuration['_descend'])) {
322 $propertiesToRender[$propertyName] = $this->transformValue($propertyValue, $configuration['_descend'][$propertyName]);
323 }
324 }
325 if (isset($configuration['_exposeObjectIdentifier']) && $configuration['_exposeObjectIdentifier'] === true) {
326 if (isset($configuration['_exposedObjectIdentifierKey']) && strlen($configuration['_exposedObjectIdentifierKey']) > 0) {
327 $identityKey = $configuration['_exposedObjectIdentifierKey'];
328 } else {
329 $identityKey = '__identity';
330 }
331 $propertiesToRender[$identityKey] = $this->persistenceManager->getIdentifierByObject($object);
332 }
333 if (isset($configuration['_exposeClassName']) && ($configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_FULLY_QUALIFIED || $configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_UNQUALIFIED)) {
334 $className = get_class($object);
335 $classNameParts = explode('\\', $className);
336 $propertiesToRender['__class'] = ($configuration['_exposeClassName'] === self::EXPOSE_CLASSNAME_FULLY_QUALIFIED ? $className : array_pop($classNameParts));
337 }
338
339 return $propertiesToRender;
340 }
341 }
342 }