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