[TASK] Deprecate TemplateService methods
[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 if ($elementType === 'IMAGEBUTTON') {
257 GeneralUtility::deprecationLog('EXT:form: The element IMAGEBUTTON is deprecated since TYPO3 CMS 7, will be removed with TYPO3 CMS 8.');
258 }
259
260 $element->setElementType($elementType);
261 $element->setElementCounter($this->elementCounter->getElementId());
262
263 $elementBuilder = ElementBuilder::create($this, $element, $userConfiguredElementTypoScript);
264 $elementBuilder->setPartialPaths();
265 $elementBuilder->setVisibility();
266
267 if ($element->getElementType() == 'CONTENTELEMENT') {
268 $attributeValue = '';
269 if ($this->configuration->getContentElementRendering()) {
270 $attributeValue = $this->formUtility->renderItem(
271 $userConfiguredElementTypoScript['cObj.'],
272 $userConfiguredElementTypoScript['cObj']
273 );
274 }
275 $element->setAdditionalArguments(array(
276 'content' => $attributeValue,
277 ));
278 } else {
279 $this->setAttributes($elementBuilder, $element, $userConfiguredElementTypoScript);
280 $userConfiguredElementTypoScript = $elementBuilder->getUserConfiguredElementTypoScript();
281 $this->setValidationMessages($element);
282
283 $this->signalSlotDispatcher->dispatch(
284 __CLASS__,
285 'txFormAfterElementCreation',
286 array($element, $this)
287 );
288 // create all child elements
289 $this->setChildElementsByIntegerKey($element, $userConfiguredElementTypoScript);
290 }
291 }
292
293 /**
294 * Rendering of a "numerical array" of Form objects from TypoScript
295 * Creates new object for each element found
296 *
297 * @param Element $element
298 * @param array $userConfiguredElementTypoScript The configuration array
299 * @return void
300 * @throws \InvalidArgumentException
301 */
302 protected function setChildElementsByIntegerKey(Element $element, array $userConfiguredElementTypoScript)
303 {
304 if (is_array($userConfiguredElementTypoScript)) {
305 $keys = ArrayUtility::filterAndSortByNumericKeys($userConfiguredElementTypoScript);
306 foreach ($keys as $key) {
307 if (
308 (int)$key
309 && strpos($key, '.') === false
310 ) {
311 $elementType = $userConfiguredElementTypoScript[$key];
312 if (isset($userConfiguredElementTypoScript[$key . '.'])) {
313 $concreteChildElementTypoScript = $userConfiguredElementTypoScript[$key . '.'];
314 } else {
315 $concreteChildElementTypoScript = array();
316 }
317 $this->distinguishElementType($element, $concreteChildElementTypoScript, $elementType);
318 }
319 }
320 } else {
321 throw new \InvalidArgumentException('Container element with id=' . $element->getElementCounter() . ' has no configuration which means no children.', 1333754854);
322 }
323 }
324
325 /**
326 * Create and add element by type.
327 * If its not a registered form element
328 * try to render it as contentelement with the internal elementType
329 * CONTENTELEMENT
330 *
331 * @param Element $element
332 * @param array $userConfiguredElementTypoScript The configuration array
333 * @param string $elementType The element type (e.g BUTTON)
334 * @return void
335 */
336 protected function distinguishElementType(Element $element, array $userConfiguredElementTypoScript, $elementType = '')
337 {
338 if (in_array($elementType, $this->typoScriptRepository->getRegisteredElementTypes())) {
339 $this->addChildElement($element, $userConfiguredElementTypoScript, $elementType);
340 } elseif ($this->configuration->getContentElementRendering()) {
341 $contentObject = array(
342 'cObj' => $elementType,
343 'cObj.' => $userConfiguredElementTypoScript
344 );
345 $this->addChildElement($element, $contentObject, 'CONTENTELEMENT');
346 }
347 }
348
349 /**
350 * Add child object to this element
351 *
352 * @param Element $element
353 * @param array $userConfiguredElementTypoScript The configuration array
354 * @param string $elementType The element type (e.g BUTTON)
355 * @return void
356 */
357 protected function addChildElement(Element $element, array $userConfiguredElementTypoScript, $elementType = '')
358 {
359 $childElement = $this->createElementObject();
360 $childElement->setParentElement($element);
361 $element->addChildElement($childElement);
362 $this->reviveElement($childElement, $userConfiguredElementTypoScript, $elementType);
363 }
364
365 /**
366 * Set the htmlAttributes and the additionalAttributes
367 * Remap htmlAttributes to additionalAttributes if needed
368 *
369 * @param ElementBuilder $elementBuilder
370 * @param Element $element
371 * @return void
372 */
373 protected function setAttributes(ElementBuilder $elementBuilder, Element $element)
374 {
375 $htmlAttributes = $this->typoScriptRepository->getModelDefinedHtmlAttributes($element->getElementType());
376 $elementBuilder->setHtmlAttributes($htmlAttributes);
377 $elementBuilder->setHtmlAttributeWildcards();
378 $elementBuilder->overlayUserdefinedHtmlAttributeValues();
379 $elementBuilder->setNameAndId();
380 $elementBuilder->overlayFixedHtmlAttributeValues();
381 // remove all NULL values
382 $htmlAttributes = array_filter($elementBuilder->getHtmlAttributes());
383
384 $elementBuilder->setHtmlAttributes($htmlAttributes);
385 $elementBuilder->moveHtmlAttributesToAdditionalArguments();
386 $elementBuilder->setViewHelperDefaulArgumentsToAdditionalArguments();
387 $elementBuilder->moveAllOtherUserdefinedPropertiesToAdditionalArguments();
388 $htmlAttributes = $elementBuilder->getHtmlAttributes();
389 $userConfiguredElementTypoScript = $elementBuilder->getUserConfiguredElementTypoScript();
390 $additionalArguments = $elementBuilder->getAdditionalArguments();
391 $element->setHtmlAttributes($htmlAttributes);
392 $additionalArguments = $this->typoScriptService->convertTypoScriptArrayToPlainArray($additionalArguments);
393 $additionalArguments['prefix'] = $this->configuration->getPrefix();
394 $element->setAdditionalArguments($additionalArguments);
395 $this->handleIncomingValues($element, $userConfiguredElementTypoScript);
396
397 if (
398 $element->getElementType() === 'FORM'
399 && $this->getControllerAction() === 'show'
400 ) {
401 if (empty($element->getHtmlAttribute('action'))) {
402 if (
403 $element->getAdditionalArgument('confirmation')
404 && (int)$element->getAdditionalArgument('confirmation') === 1
405 ) {
406 $element->setAdditionalArgument('action', 'confirmation');
407 } else {
408 $element->setAdditionalArgument('action', 'process');
409 }
410 } else {
411 $element->setAdditionalArgument('pageUid', $element->getHtmlAttribute('action'));
412 $element->setAdditionalArgument('action', null);
413 }
414 }
415
416 // needed if confirmation page is enabled
417 if (
418 $this->sessionUtility->getSessionData($element->getName())
419 && $element->getAdditionalArgument('uploadedFiles') === null
420 ) {
421 $element->setAdditionalArgument('uploadedFiles', $this->sessionUtility->getSessionData($element->getName()));
422 }
423 }
424
425 /**
426 * Handles the incoming form data
427 *
428 * @param Element $element
429 * @param array $userConfiguredElementTypoScript
430 * @return array
431 */
432 protected function handleIncomingValues(Element $element, array $userConfiguredElementTypoScript)
433 {
434 if (!$this->getIncomingData()) {
435 return;
436 }
437 $elementName = $element->getName();
438 if ($element->getHtmlAttribute('value') !== null) {
439 $modelValue = $element->getHtmlAttribute('value');
440 } else {
441 $modelValue = $element->getAdditionalArgument('value');
442 }
443
444 if ($this->getIncomingData()->getIncomingField($elementName) !== null) {
445 /* filter values and set it back to incoming fields */
446 /* remove xss every time */
447 $userConfiguredElementTypoScript['filters.'][-1] = 'removexss';
448 $keys = ArrayUtility::filterAndSortByNumericKeys($userConfiguredElementTypoScript['filters.']);
449 foreach ($keys as $key) {
450 $class = $userConfiguredElementTypoScript['filters.'][$key];
451 if (
452 (int)$key
453 && strpos($key, '.') === false
454 ) {
455 $filterArguments = $userConfiguredElementTypoScript['filters.'][$key . '.'];
456 $filterClassName = $this->typoScriptRepository->getRegisteredClassName((string)$class, 'registeredFilters');
457 if ($filterClassName !== null) {
458 // toDo: handel array values
459 if (is_string($this->getIncomingData()->getIncomingField($elementName))) {
460 if (is_null($filterArguments)) {
461 $filter = $this->objectManager->get($filterClassName);
462 } else {
463 $filter = $this->objectManager->get($filterClassName, $filterArguments);
464 }
465 if ($filter) {
466 $value = $filter->filter($this->getIncomingData()->getIncomingField($elementName));
467 $this->getIncomingData()->setIncomingField($elementName, $value);
468 } else {
469 throw new \RuntimeException('Class "' . $filterClassName . '" could not be loaded.');
470 }
471 }
472 } else {
473 throw new \RuntimeException('Class "' . $filterClassName . '" not registered via TypoScript.');
474 }
475 }
476 }
477
478 if ($element->getHtmlAttribute('value') !== null) {
479 $element->setHtmlAttribute('value', $this->getIncomingData()->getIncomingField($elementName));
480 } else {
481 $element->setAdditionalArgument('value', $this->getIncomingData()->getIncomingField($elementName));
482 }
483 }
484 $this->signalSlotDispatcher->dispatch(
485 __CLASS__,
486 'txFormHandleIncomingValues',
487 array(
488 $element,
489 $this->getIncomingData(),
490 $modelValue,
491 $this
492 )
493 );
494 }
495
496 /**
497 * Set the rendered mandatory message
498 * and the validation error message if available
499 *
500 * @param Element $element
501 * @return void
502 */
503 protected function setValidationMessages(Element $element)
504 {
505 $elementName = $element->getName();
506 $mandatoryMessages = $this->validationBuilder->getMandatoryValidationMessagesByElementName($elementName);
507 $element->setMandatoryValidationMessages($mandatoryMessages);
508 if (
509 $this->getValidationErrors()
510 && $this->getValidationErrors()->forProperty($elementName)->hasErrors()
511 ) {
512 /** @var \TYPO3\CMS\Extbase\Error\Error[] $errors */
513 $errors = $this->getValidationErrors()->forProperty($elementName)->getErrors();
514 $errorMessages = array();
515 foreach ($errors as $error) {
516 $errorMessages[] = $error->getMessage();
517 }
518 $element->setValidationErrorMessages($errorMessages);
519 }
520 }
521
522 /**
523 * Return the form prefix
524 *
525 * @return string
526 */
527 public function getFormPrefix()
528 {
529 return $this->configuration->getPrefix();
530 }
531
532 /**
533 * TRUE if the content element rendering should be disabled.
534 *
535 * @return bool
536 */
537 public function getDisableContentElementRendering()
538 {
539 return !$this->configuration->getContentElementRendering();
540 }
541
542 /**
543 * TRUE if the content element rendering should be disabled.
544 *
545 * @return string
546 */
547 public function getControllerAction()
548 {
549 return $this->controllerContext->getRequest()->getControllerActionName();
550 }
551
552 /**
553 * Get the incoming flat form data
554 *
555 * @return ValidationElement
556 */
557 public function getIncomingData()
558 {
559 return $this->controllerContext->getValidationElement();
560 }
561
562 /**
563 * Set the validation errors
564 *
565 * @param \TYPO3\CMS\Extbase\Error\Result $validationErrors
566 * @return void
567 */
568 public function setValidationErrors(\TYPO3\CMS\Extbase\Error\Result $validationErrors)
569 {
570 $this->validationErrors = $validationErrors;
571 }
572
573 /**
574 * Get the validation errors
575 *
576 * @return NULL|\TYPO3\CMS\Extbase\Error\Result
577 */
578 public function getValidationErrors()
579 {
580 return $this->validationErrors;
581 }
582 }