[TASK] Remove ext:dbal from installation steps
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Domain / Builder / FormBuilder.php
1 <?php
2 namespace TYPO3\CMS\Form\Domain\Builder;
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\Utility\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Form\Domain\Model\Configuration;
20 use TYPO3\CMS\Form\Domain\Model\Element;
21 use TYPO3\CMS\Form\Domain\Model\ValidationElement;
22 use TYPO3\CMS\Form\Mvc\Controller\ControllerContext;
23 use TYPO3\CMS\Form\Utility\FormUtility;
24
25 /**
26 * TypoScript factory for form
27 *
28 * Takes the incoming TypoScript and adds all the necessary form objects
29 * according to the configuration.
30 */
31 class FormBuilder
32 {
33 /**
34 * @param Configuration $configuration
35 * @return FormBuilder
36 */
37 public static function create(Configuration $configuration)
38 {
39 /** @var FormBuilder $formBuilder */
40 $formBuilder = \TYPO3\CMS\Form\Utility\FormUtility::getObjectManager()->get(FormBuilder::class);
41 $formBuilder->setConfiguration($configuration);
42 return $formBuilder;
43 }
44
45 /**
46 * @var FormUtility
47 */
48 protected $formUtility;
49
50 /**
51 * @var \TYPO3\CMS\Extbase\Service\TypoScriptService
52 */
53 protected $typoScriptService;
54
55 /**
56 * @var ValidationBuilder
57 */
58 protected $validationBuilder;
59
60 /**
61 * @var \TYPO3\CMS\Form\Domain\Repository\TypoScriptRepository
62 */
63 protected $typoScriptRepository;
64
65 /**
66 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
67 */
68 protected $signalSlotDispatcher;
69
70 /**
71 * @var \TYPO3\CMS\Form\Utility\SessionUtility
72 */
73 protected $sessionUtility;
74
75 /**
76 * @var \TYPO3\CMS\Extbase\Object\ObjectManager
77 */
78 protected $objectManager;
79
80 /**
81 * @var \TYPO3\CMS\Form\Utility\ElementCounter
82 */
83 protected $elementCounter;
84
85 /**
86 * @var NULL|\TYPO3\CMS\Extbase\Error\Result
87 */
88 protected $validationErrors = null;
89
90 /**
91 * @var Configuration;
92 */
93 protected $configuration;
94
95 /**
96 * @var ControllerContext
97 */
98 protected $controllerContext;
99
100 /**
101 * @param \TYPO3\CMS\Extbase\Service\TypoScriptService $typoScriptService
102 * @return void
103 */
104 public function injectTypoScriptService(\TYPO3\CMS\Extbase\Service\TypoScriptService $typoScriptService)
105 {
106 $this->typoScriptService = $typoScriptService;
107 }
108
109 /**
110 * @param \TYPO3\CMS\Form\Domain\Repository\TypoScriptRepository $typoScriptRepository
111 * @return void
112 */
113 public function injectTypoScriptRepository(\TYPO3\CMS\Form\Domain\Repository\TypoScriptRepository $typoScriptRepository)
114 {
115 $this->typoScriptRepository = $typoScriptRepository;
116 }
117
118 /**
119 * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
120 * @return void
121 */
122 public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
123 {
124 $this->signalSlotDispatcher = $signalSlotDispatcher;
125 }
126
127 /**
128 * @param \TYPO3\CMS\Form\Utility\SessionUtility $sessionUtility
129 * @return void
130 */
131 public function injectSessionUtility(\TYPO3\CMS\Form\Utility\SessionUtility $sessionUtility)
132 {
133 $this->sessionUtility = $sessionUtility;
134 }
135
136 /**
137 * @param \TYPO3\CMS\Form\Utility\ElementCounter $elementCounter
138 * @return void
139 */
140 public function injectElementCounter(\TYPO3\CMS\Form\Utility\ElementCounter $elementCounter)
141 {
142 $this->elementCounter = $elementCounter;
143 }
144
145 /**
146 * @param \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager
147 * @return void
148 */
149 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManager $objectManager)
150 {
151 $this->objectManager = $objectManager;
152 }
153
154 /**
155 * @return Configuration
156 */
157 public function getConfiguration()
158 {
159 return $this->configuration;
160 }
161
162 /**
163 * @param Configuration $configuration
164 */
165 public function setConfiguration(Configuration $configuration)
166 {
167 $this->configuration = $configuration;
168 }
169
170 /**
171 * @return ControllerContext
172 */
173 public function getControllerContext()
174 {
175 return $this->controllerContext;
176 }
177
178 /**
179 * @param ControllerContext $controllerContext
180 */
181 public function setControllerContext(ControllerContext $controllerContext)
182 {
183 $this->controllerContext = $controllerContext;
184 }
185
186 /**
187 * @return FormUtility
188 */
189 public function getFormUtility()
190 {
191 return $this->formUtility;
192 }
193
194 /**
195 * @param FormUtility $formUtility
196 */
197 public function setFormUtility(FormUtility $formUtility)
198 {
199 $this->formUtility = $formUtility;
200 }
201
202 /**
203 * @return ValidationBuilder
204 */
205 public function getValidationBuilder()
206 {
207 return $this->validationBuilder;
208 }
209
210 /**
211 * @param ValidationBuilder $validationBuilder
212 */
213 public function setValidationBuilder(ValidationBuilder $validationBuilder)
214 {
215 $this->validationBuilder = $validationBuilder;
216 }
217
218 /**
219 * Build model from TypoScript
220 * Needed if more than one form exist at a page
221 *
222 * @return NULL|\TYPO3\CMS\Form\Domain\Model\Element The form object containing the child elements
223 */
224 public function buildModel()
225 {
226 $userConfiguredFormTypoScript = $this->configuration->getTypoScript();
227 $form = $this->createElementObject();
228 $this->reviveElement($form, $userConfiguredFormTypoScript, 'FORM');
229 $form->setThemeName($this->configuration->getThemeName());
230 return $form;
231 }
232
233 /**
234 * Create a element
235 *
236 * @return \TYPO3\CMS\Form\Domain\Model\Element
237 */
238 protected function createElementObject()
239 {
240 $element = GeneralUtility::makeInstance(Element::class);
241 return $element;
242 }
243
244 /**
245 * Revive the domain model of the accordant element.
246 *
247 * @param Element $element
248 * @param array $userConfiguredElementTypoScript The configuration array
249 * @param string $elementType The element type (e.g BUTTON)
250 * @return void
251 */
252 protected function reviveElement(Element $element, array $userConfiguredElementTypoScript, $elementType = '')
253 {
254 // @todo Check $userConfiguredElementTypoScript
255
256 $element->setElementType($elementType);
257 $element->setElementCounter($this->elementCounter->getElementId());
258
259 $elementBuilder = ElementBuilder::create($this, $element, $userConfiguredElementTypoScript);
260 $elementBuilder->setPartialPaths();
261 $elementBuilder->setVisibility();
262
263 if ($element->getElementType() == 'CONTENTELEMENT') {
264 $attributeValue = '';
265 if ($this->configuration->getContentElementRendering()) {
266 $attributeValue = $this->formUtility->renderItem(
267 $userConfiguredElementTypoScript['cObj.'],
268 $userConfiguredElementTypoScript['cObj']
269 );
270 }
271 $element->setAdditionalArguments([
272 'content' => $attributeValue,
273 ]);
274 } else {
275 $this->setAttributes($elementBuilder, $element, $userConfiguredElementTypoScript);
276 $userConfiguredElementTypoScript = $elementBuilder->getUserConfiguredElementTyposcript();
277 $this->setValidationMessages($element);
278
279 $this->signalSlotDispatcher->dispatch(
280 __CLASS__,
281 'txFormAfterElementCreation',
282 [$element, $this]
283 );
284 // create all child elements
285 $this->setChildElementsByIntegerKey($element, $userConfiguredElementTypoScript);
286 }
287 }
288
289 /**
290 * Rendering of a "numerical array" of Form objects from TypoScript
291 * Creates new object for each element found
292 *
293 * @param Element $element
294 * @param array $userConfiguredElementTypoScript The configuration array
295 * @return void
296 * @throws \InvalidArgumentException
297 */
298 protected function setChildElementsByIntegerKey(Element $element, array $userConfiguredElementTypoScript)
299 {
300 if (is_array($userConfiguredElementTypoScript)) {
301 $keys = ArrayUtility::filterAndSortByNumericKeys($userConfiguredElementTypoScript);
302 foreach ($keys as $key) {
303 if (
304 (int)$key
305 && strpos($key, '.') === false
306 ) {
307 $elementType = $userConfiguredElementTypoScript[$key];
308 if (isset($userConfiguredElementTypoScript[$key . '.'])) {
309 $concreteChildElementTypoScript = $userConfiguredElementTypoScript[$key . '.'];
310 } else {
311 $concreteChildElementTypoScript = [];
312 }
313 $this->distinguishElementType($element, $concreteChildElementTypoScript, $elementType);
314 }
315 }
316 } else {
317 throw new \InvalidArgumentException('Container element with id=' . $element->getElementCounter() . ' has no configuration which means no children.', 1333754854);
318 }
319 }
320
321 /**
322 * Create and add element by type.
323 * If its not a registered form element
324 * try to render it as contentelement with the internal elementType
325 * CONTENTELEMENT
326 *
327 * @param Element $element
328 * @param array $userConfiguredElementTypoScript The configuration array
329 * @param string $elementType The element type (e.g BUTTON)
330 * @return void
331 */
332 protected function distinguishElementType(Element $element, array $userConfiguredElementTypoScript, $elementType = '')
333 {
334 if (in_array($elementType, $this->typoScriptRepository->getRegisteredElementTypes())) {
335 $this->addChildElement($element, $userConfiguredElementTypoScript, $elementType);
336 } elseif ($this->configuration->getContentElementRendering()) {
337 $contentObject = [
338 'cObj' => $elementType,
339 'cObj.' => $userConfiguredElementTypoScript
340 ];
341 $this->addChildElement($element, $contentObject, 'CONTENTELEMENT');
342 }
343 }
344
345 /**
346 * Add child object to this element
347 *
348 * @param Element $element
349 * @param array $userConfiguredElementTypoScript The configuration array
350 * @param string $elementType The element type (e.g BUTTON)
351 * @return void
352 */
353 protected function addChildElement(Element $element, array $userConfiguredElementTypoScript, $elementType = '')
354 {
355 $childElement = $this->createElementObject();
356 $childElement->setParentElement($element);
357 $element->addChildElement($childElement);
358 $this->reviveElement($childElement, $userConfiguredElementTypoScript, $elementType);
359 }
360
361 /**
362 * Set the htmlAttributes and the additionalAttributes
363 * Remap htmlAttributes to additionalAttributes if needed
364 *
365 * @param ElementBuilder $elementBuilder
366 * @param Element $element
367 * @return void
368 */
369 protected function setAttributes(ElementBuilder $elementBuilder, Element $element)
370 {
371 $htmlAttributes = $this->typoScriptRepository->getModelDefinedHtmlAttributes($element->getElementType());
372 $elementBuilder->setHtmlAttributes($htmlAttributes);
373 $elementBuilder->setHtmlAttributeWildcards();
374 $elementBuilder->overlayUserdefinedHtmlAttributeValues();
375 $elementBuilder->setNameAndId();
376 $elementBuilder->overlayFixedHtmlAttributeValues();
377 // remove all NULL values
378 $htmlAttributes = array_filter($elementBuilder->getHtmlAttributes());
379
380 $elementBuilder->setHtmlAttributes($htmlAttributes);
381 $elementBuilder->moveHtmlAttributesToAdditionalArguments();
382 $elementBuilder->setViewHelperDefaulArgumentsToAdditionalArguments();
383 $elementBuilder->moveAllOtherUserdefinedPropertiesToAdditionalArguments();
384 $htmlAttributes = $elementBuilder->getHtmlAttributes();
385 $userConfiguredElementTypoScript = $elementBuilder->getUserConfiguredElementTyposcript();
386 $additionalArguments = $elementBuilder->getAdditionalArguments();
387 $element->setHtmlAttributes($htmlAttributes);
388 $additionalArguments = $this->typoScriptService->convertTypoScriptArrayToPlainArray($additionalArguments);
389 $additionalArguments['prefix'] = $this->configuration->getPrefix();
390 $element->setAdditionalArguments($additionalArguments);
391 $this->handleIncomingValues($element, $userConfiguredElementTypoScript);
392
393 if (
394 $element->getElementType() === 'FORM'
395 && $this->getControllerAction() === 'show'
396 ) {
397 if (empty($element->getHtmlAttribute('action'))) {
398 if (
399 $element->getAdditionalArgument('confirmation')
400 && (int)$element->getAdditionalArgument('confirmation') === 1
401 ) {
402 $element->setAdditionalArgument('action', 'confirmation');
403 } else {
404 $element->setAdditionalArgument('action', 'process');
405 }
406 } else {
407 $element->setAdditionalArgument('pageUid', $element->getHtmlAttribute('action'));
408 $element->setAdditionalArgument('action', null);
409 }
410 }
411
412 // needed if confirmation page is enabled
413 if (
414 $this->sessionUtility->getSessionData($element->getName())
415 && $element->getAdditionalArgument('uploadedFiles') === null
416 ) {
417 $element->setAdditionalArgument('uploadedFiles', $this->sessionUtility->getSessionData($element->getName()));
418 }
419 }
420
421 /**
422 * Handles the incoming form data
423 *
424 * @param Element $element
425 * @param array $userConfiguredElementTypoScript
426 * @return array
427 */
428 protected function handleIncomingValues(Element $element, array $userConfiguredElementTypoScript)
429 {
430 if (!$this->getIncomingData()) {
431 return;
432 }
433 $elementName = $element->getName();
434 if ($element->getHtmlAttribute('value') !== null) {
435 $modelValue = $element->getHtmlAttribute('value');
436 } else {
437 $modelValue = $element->getAdditionalArgument('value');
438 }
439
440 if ($this->getIncomingData()->getIncomingField($elementName) !== null) {
441 /* filter values and set it back to incoming fields */
442 $filters = isset($userConfiguredElementTypoScript['filters.']) ? $userConfiguredElementTypoScript['filters.'] : [];
443 if (!empty($filters)) {
444 $keys = ArrayUtility::filterAndSortByNumericKeys($filters);
445 foreach ($keys as $key) {
446 $class = $userConfiguredElementTypoScript['filters.'][$key];
447 if (
448 (int)$key
449 && strpos($key, '.') === false
450 ) {
451 $filterArguments = $userConfiguredElementTypoScript['filters.'][$key . '.'];
452 $filterClassName = $this->typoScriptRepository->getRegisteredClassName((string)$class, 'registeredFilters');
453 if ($filterClassName !== null) {
454 // toDo: handel array values
455 if (is_string($this->getIncomingData()->getIncomingField($elementName))) {
456 if (is_null($filterArguments)) {
457 $filter = $this->objectManager->get($filterClassName);
458 } else {
459 $filter = $this->objectManager->get($filterClassName, $filterArguments);
460 }
461 if ($filter) {
462 $value = $filter->filter($this->getIncomingData()->getIncomingField($elementName));
463 $this->getIncomingData()->setIncomingField($elementName, $value);
464 } else {
465 throw new \RuntimeException('Class "' . $filterClassName . '" could not be loaded.');
466 }
467 }
468 } else {
469 throw new \RuntimeException('Class "' . $filterClassName . '" not registered via TypoScript.');
470 }
471 }
472 }
473 }
474
475 if ($element->getHtmlAttribute('value') !== null) {
476 $element->setHtmlAttribute('value', $this->getIncomingData()->getIncomingField($elementName));
477 } else {
478 $element->setAdditionalArgument('value', $this->getIncomingData()->getIncomingField($elementName));
479 }
480 }
481 $this->signalSlotDispatcher->dispatch(
482 __CLASS__,
483 'txFormHandleIncomingValues',
484 [
485 $element,
486 $this->getIncomingData(),
487 $modelValue,
488 $this
489 ]
490 );
491 }
492
493 /**
494 * Set the rendered mandatory message
495 * and the validation error message if available
496 *
497 * @param Element $element
498 * @return void
499 */
500 protected function setValidationMessages(Element $element)
501 {
502 $elementName = $element->getName();
503 $mandatoryMessages = $this->validationBuilder->getMandatoryValidationMessagesByElementName($elementName);
504 $element->setMandatoryValidationMessages($mandatoryMessages);
505 if (
506 $this->getValidationErrors()
507 && $this->getValidationErrors()->forProperty($elementName)->hasErrors()
508 ) {
509 /** @var \TYPO3\CMS\Extbase\Error\Error[] $errors */
510 $errors = $this->getValidationErrors()->forProperty($elementName)->getErrors();
511 $errorMessages = [];
512 foreach ($errors as $error) {
513 $errorMessages[] = $error->getMessage();
514 }
515 $element->setValidationErrorMessages($errorMessages);
516 }
517 }
518
519 /**
520 * Return the form prefix
521 *
522 * @return string
523 */
524 public function getFormPrefix()
525 {
526 return $this->configuration->getPrefix();
527 }
528
529 /**
530 * TRUE if the content element rendering should be disabled.
531 *
532 * @return bool
533 */
534 public function getDisableContentElementRendering()
535 {
536 return !$this->configuration->getContentElementRendering();
537 }
538
539 /**
540 * TRUE if the content element rendering should be disabled.
541 *
542 * @return string
543 */
544 public function getControllerAction()
545 {
546 return $this->controllerContext->getRequest()->getControllerActionName();
547 }
548
549 /**
550 * Get the incoming flat form data
551 *
552 * @return ValidationElement
553 */
554 public function getIncomingData()
555 {
556 return $this->controllerContext->getValidationElement();
557 }
558
559 /**
560 * Set the validation errors
561 *
562 * @param \TYPO3\CMS\Extbase\Error\Result $validationErrors
563 * @return void
564 */
565 public function setValidationErrors(\TYPO3\CMS\Extbase\Error\Result $validationErrors)
566 {
567 $this->validationErrors = $validationErrors;
568 }
569
570 /**
571 * Get the validation errors
572 *
573 * @return NULL|\TYPO3\CMS\Extbase\Error\Result
574 */
575 public function getValidationErrors()
576 {
577 return $this->validationErrors;
578 }
579 }