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