[CLEANUP] Improve the @param/@return/@var PHPDoc
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Domain / Factory / TypoScriptFactory.php
1 <?php
2 namespace TYPO3\CMS\Form\Domain\Factory;
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\Core\TimeTracker\TimeTracker;
18 use TYPO3\CMS\Core\TypoScript\TemplateService;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Form\Domain\Model\Element\AbstractElement;
21
22 /**
23 * Typoscript factory for form
24 *
25 * Takes the incoming Typoscipt and adds all the necessary form objects
26 * according to the configuration.
27 *
28 * @author Patrick Broens <patrick@patrickbroens.nl>
29 */
30 class TypoScriptFactory implements \TYPO3\CMS\Core\SingletonInterface {
31
32 /**
33 * @var string
34 */
35 const PROPERTY_DisableContentElement = 'disableContentElement';
36
37 /**
38 * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
39 */
40 protected $localContentObject;
41
42 /**
43 * @var bool
44 */
45 protected $disableContentElement = FALSE;
46
47 /**
48 * @var TimeTracker
49 */
50 protected $timeTracker;
51
52 /**
53 * @var \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
54 */
55 protected $frontendController;
56
57 public function __construct() {
58 $this->timeTracker = $GLOBALS['TT'];
59 $this->frontendController = $GLOBALS['TSFE'];
60 }
61
62 /**
63 * Build model from Typoscript
64 *
65 * @param array $typoscript Typoscript containing all configuration
66 * @return \TYPO3\CMS\Form\Domain\Model\Form The form object containing the child elements
67 */
68 public function buildModelFromTyposcript(array $typoscript) {
69 if (isset($typoscript[self::PROPERTY_DisableContentElement])) {
70 $this->setDisableContentElement($typoscript[self::PROPERTY_DisableContentElement]);
71 }
72 $this->setLayoutHandler($typoscript);
73 $form = $this->createElement('form', $typoscript);
74 return $form;
75 }
76
77 /**
78 * Disables the content element.
79 *
80 * @param bool $disableContentElement
81 * @return void
82 */
83 public function setDisableContentElement($disableContentElement) {
84 $this->disableContentElement = (bool)$disableContentElement;
85 }
86
87 /**
88 * Rendering of a "numerical array" of Form objects from TypoScript
89 * Creates new object for each element found
90 *
91 * @param AbstractElement $parentElement Parent model object
92 * @param array $typoscript Configuration array
93 * @return void
94 * @throws \InvalidArgumentException
95 */
96 public function getChildElementsByIntegerKey(AbstractElement $parentElement, array $typoscript) {
97 if (is_array($typoscript)) {
98 $keys = TemplateService::sortedKeyList($typoscript);
99 foreach ($keys as $key) {
100 $class = $typoscript[$key];
101 if ((int)$key && strpos($key, '.') === FALSE) {
102 if (isset($typoscript[$key . '.'])) {
103 $elementArguments = $typoscript[$key . '.'];
104 } else {
105 $elementArguments = array();
106 }
107 $this->setElementType($parentElement, $class, $elementArguments);
108 }
109 }
110 } else {
111 throw new \InvalidArgumentException('Container element with id=' . $parentElement->getElementId() . ' has no configuration which means no children.', 1333754854);
112 }
113 }
114
115 /**
116 * Create and add element by type.
117 * This can be a derived Typoscript object by "<",
118 * a form element, or a regular Typoscript object.
119 *
120 * @param AbstractElement $parentElement The parent for the new element
121 * @param string $class Classname for the element
122 * @param array $arguments Configuration array
123 * @return void
124 */
125 public function setElementType(AbstractElement $parentElement, $class, array $arguments) {
126 if (in_array($class, \TYPO3\CMS\Form\Utility\FormUtility::getInstance()->getFormObjects())) {
127 $this->addElement($parentElement, $class, $arguments);
128 } elseif ($this->disableContentElement === FALSE) {
129 if ($class[0] === '<') {
130 $key = trim(substr($class, 1));
131 /** @var $typoscriptParser \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser */
132 $typoscriptParser = GeneralUtility::makeInstance(\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::class);
133 $oldArguments = $arguments;
134 list($class, $arguments) = $typoscriptParser->getVal($key, $this->frontendController->tmpl->setup);
135 if (is_array($oldArguments) && count($oldArguments)) {
136 $arguments = array_replace_recursive($arguments, $oldArguments);
137 }
138 $this->timeTracker->incStackPointer();
139 $contentObject = array(
140 'cObj' => $class,
141 'cObj.' => $arguments
142 );
143 $this->addElement($parentElement, 'content', $contentObject);
144 $this->timeTracker->decStackPointer();
145 } else {
146 $contentObject = array(
147 'cObj' => $class,
148 'cObj.' => $arguments
149 );
150 $this->addElement($parentElement, 'content', $contentObject);
151 }
152 }
153 }
154
155 /**
156 * Add child object to this element
157 *
158 * @param AbstractElement $parentElement Parent model object
159 * @param string $class Type of element
160 * @param array $arguments Configuration array
161 * @return void
162 */
163 public function addElement(AbstractElement $parentElement, $class, array $arguments = array()) {
164 $element = $this->createElement($class, $arguments);
165 if (method_exists($parentElement, 'addElement')) {
166 $parentElement->addElement($element);
167 }
168 }
169
170 /**
171 * Create element by loading class
172 * and instantiating the object
173 *
174 * @param string $class Type of element
175 * @param array $arguments Configuration array
176 * @return AbstractElement
177 * @throws \InvalidArgumentException
178 */
179 public function createElement($class, array $arguments = array()) {
180 $class = strtolower((string)$class);
181 if ($class === 'form') {
182 $className = 'TYPO3\\CMS\\Form\\Domain\\Model\\' . ucfirst($class);
183 } else {
184 $className = 'TYPO3\\CMS\\Form\\Domain\\Model\\Element\\' . ucfirst($class) . 'Element';
185 }
186 /* @var $object AbstractElement */
187 $object = GeneralUtility::makeInstance($className);
188 if ($object->getElementType() === AbstractElement::ELEMENT_TYPE_CONTENT) {
189 $object->setData($arguments['cObj'], $arguments['cObj.']);
190 } elseif ($object->getElementType() === AbstractElement::ELEMENT_TYPE_PLAIN) {
191 /* @var $object \TYPO3\CMS\Form\Domain\Model\Element\AbstractPlainElement */
192 $object->setProperties($arguments);
193 } elseif ($object->getElementType() === AbstractElement::ELEMENT_TYPE_FORM) {
194 $object->setData($arguments['data']);
195 $this->reconstituteElement($object, $arguments);
196 } else {
197 throw new \InvalidArgumentException('Element type "' . $object->getElementType() . '" is not supported.', 1333754878);
198 }
199 return $object;
200 }
201
202 /**
203 * Reconstitutes the domain model of the accordant element.
204 *
205 * @param AbstractElement $element
206 * @param array $arguments Configuration array
207 * @return void
208 */
209 protected function reconstituteElement(AbstractElement $element, array $arguments = array()) {
210 if (isset($arguments['value.'])) {
211 $cObj = $this->getLocalContentObject();
212 $arguments['value'] = $cObj->stdWrap($arguments['value'], $arguments['value.']);
213 }
214
215 $this->setAttributes($element, $arguments);
216 $this->setAdditionals($element, $arguments);
217 if (isset($arguments['filters.'])) {
218 $this->setFilters($element, $arguments['filters.']);
219 }
220 $element->setLayout($arguments['layout']);
221 $element->setValue($arguments['value']);
222 $element->setName($arguments['name']);
223 $element->setMessagesFromValidation();
224 $element->setErrorsFromValidation();
225 $element->checkFilterAndSetIncomingDataFromRequest();
226 $this->getChildElementsByIntegerKey($element, $arguments);
227 }
228
229 /**
230 * Set the attributes
231 *
232 * @param AbstractElement $element Model object
233 * @param array $arguments Arguments
234 * @return void
235 * @throws \RuntimeException
236 * @throws \InvalidArgumentException
237 */
238 public function setAttributes(AbstractElement $element, array $arguments) {
239 if ($element->hasAllowedAttributes()) {
240 $attributes = $element->getAllowedAttributes();
241 $mandatoryAttributes = $element->getMandatoryAttributes();
242 foreach ($attributes as $attribute => $value) {
243 if (isset($arguments[$attribute]) || isset($arguments[$attribute . '.']) || in_array($attribute, $mandatoryAttributes) || !empty($value)) {
244 if ($arguments[$attribute] !== '') {
245 $value = $arguments[$attribute];
246 } elseif (!empty($arguments[($attribute . '.')])) {
247 $value = $arguments[$attribute . '.'];
248 }
249 try {
250 $element->setAttribute($attribute, $value);
251 } catch (\Exception $exception) {
252 throw new \RuntimeException('Cannot call user function for attribute ' . ucfirst($attribute), 1333754904);
253 }
254 }
255 }
256 } else {
257 throw new \InvalidArgumentException('The element with id=' . $element->getElementId() . ' has no default attributes set.', 1333754925);
258 }
259 }
260
261 /**
262 * Set the additionals from Element Typoscript configuration
263 *
264 * @param AbstractElement $element Model object
265 * @param array $arguments Arguments
266 * @return void
267 * @throws \RuntimeException
268 * @throws \InvalidArgumentException
269 */
270 public function setAdditionals(AbstractElement $element, array $arguments) {
271 if (!empty($arguments)) {
272 if ($element->hasAllowedAdditionals()) {
273 $additionals = $element->getAllowedAdditionals();
274 foreach ($additionals as $additional) {
275 if (isset($arguments[$additional . '.']) || isset($arguments[$additional])) {
276 if (isset($arguments[$additional]) && isset($arguments[$additional . '.'])) {
277 $value = $arguments[$additional . '.'];
278 $type = $arguments[$additional];
279 } elseif (isset($arguments[$additional . '.'])) {
280 $value = $arguments[$additional . '.'];
281 $type = 'TEXT';
282 } else {
283 $value['value'] = $arguments[$additional];
284 $type = 'TEXT';
285 }
286 try {
287 $element->setAdditional($additional, $type, $value);
288 } catch (\Exception $exception) {
289 throw new \RuntimeException('Cannot call user function for additional ' . ucfirst($additional), 1333754941);
290 }
291 }
292 if (isset($arguments['layout.'][$additional]) && $element->additionalIsSet($additional)) {
293 $layout = $arguments['layout.'][$additional];
294 $element->setAdditionalLayout($additional, $layout);
295 }
296 }
297 } else {
298 throw new \InvalidArgumentException('The element with id=' . $element->getElementId() . ' has no additionals set.', 1333754962);
299 }
300 }
301 }
302
303 /**
304 * Add the filters according to the settings in the Typoscript array
305 *
306 * @param AbstractElement $element Model object
307 * @param array $arguments TypoScript
308 * @return void
309 */
310 protected function setFilters(AbstractElement $element, array $arguments) {
311 $keys = TemplateService::sortedKeyList($arguments);
312 foreach ($keys as $key) {
313 $class = $arguments[$key];
314 if ((int)$key && strpos($key, '.') === FALSE) {
315 $filterArguments = $arguments[$key . '.'];
316 $filter = $element->makeFilter($class, $filterArguments);
317 $element->addFilter($filter);
318 }
319 }
320 }
321
322 /**
323 * Set the layout handler
324 *
325 * @param array $typoscript TypoScript
326 * @return \TYPO3\CMS\Form\Layout The layout handler
327 */
328 public function setLayoutHandler(array $typoscript) {
329 /** @var $layoutHandler \TYPO3\CMS\Form\Layout */
330 $layoutHandler = GeneralUtility::makeInstance(\TYPO3\CMS\Form\Layout::class);
331 $layoutHandler->setLayout($this->getLayoutFromTypoScript($typoscript));
332 return $layoutHandler;
333 }
334
335 /**
336 * Gets the layout that is configured in TypoScript
337 * If no layout is defined, it returns an empty array to use the default.
338 *
339 * @param array $typoscript The TypoScript configuration
340 * @return array $layout The layout but with respecting its TypoScript configuration
341 */
342 public function getLayoutFromTypoScript($typoscript) {
343 return !empty($typoscript['layout.']) ? $typoscript['layout.'] : array();
344 }
345
346 /**
347 * Set the request handler
348 *
349 * @param array $typoscript TypoScript
350 * @return \TYPO3\CMS\Form\Request The request handler
351 */
352 public function setRequestHandler($typoscript) {
353 $prefix = isset($typoscript['prefix']) ? $typoscript['prefix'] : '';
354 $method = isset($typoscript['method']) ? $typoscript['method'] : '';
355 /** @var $requestHandler \TYPO3\CMS\Form\Request */
356 $requestHandler = GeneralUtility::makeInstance(\TYPO3\CMS\Form\Request::class);
357 // singleton
358 $requestHandler->setPrefix($prefix);
359 $requestHandler->setMethod($method);
360 $requestHandler->storeFiles();
361 return $requestHandler;
362 }
363
364 /**
365 * Set the validation rules
366 *
367 * Makes the validation object and adds rules to it
368 *
369 * @param array $typoscript TypoScript
370 * @return \TYPO3\CMS\Form\Utility\ValidatorUtility The validation object
371 */
372 public function setRules(array $typoscript) {
373 $rulesTyposcript = isset($typoscript['rules.']) ? $typoscript['rules.'] : NULL;
374 /** @var $rulesClass \TYPO3\CMS\Form\Utility\ValidatorUtility */
375 $rulesClass = GeneralUtility::makeInstance(\TYPO3\CMS\Form\Utility\ValidatorUtility::class, $rulesTyposcript);
376 // singleton
377 if (is_array($rulesTyposcript)) {
378 $keys = TemplateService::sortedKeyList($rulesTyposcript);
379 foreach ($keys as $key) {
380 $class = $rulesTyposcript[$key];
381 if ((int)$key && strpos($key, '.') === FALSE) {
382 $elementArguments = $rulesTyposcript[$key . '.'];
383 $rule = $rulesClass->createRule($class, $elementArguments);
384 $rule->setFieldName($elementArguments['element']);
385 $breakOnError = isset($elementArguments['breakOnError']) ? $elementArguments['breakOnError'] : FALSE;
386 $rulesClass->addRule($rule, $elementArguments['element'], $breakOnError);
387 }
388 }
389 }
390 return $rulesClass;
391 }
392
393 /**
394 * Gets the local content object.
395 *
396 * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
397 */
398 protected function getLocalContentObject() {
399 if (!isset($this->localContentObject)) {
400 $this->localContentObject = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
401 }
402 return $this->localContentObject;
403 }
404
405 }