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