295f5fb479efd12965917b0c399c6452f3474bf0
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Domain / Runtime / FormRuntime.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Form\Domain\Runtime;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Utility\ArrayUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Extbase\Error\Result;
21 use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
22 use TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext;
23 use TYPO3\CMS\Extbase\Mvc\Web\Request;
24 use TYPO3\CMS\Extbase\Mvc\Web\Response;
25 use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
26 use TYPO3\CMS\Extbase\Object\ObjectManager;
27 use TYPO3\CMS\Extbase\Property\Exception as PropertyException;
28 use TYPO3\CMS\Extbase\Reflection\PropertyReflection;
29 use TYPO3\CMS\Form\Domain\Exception\RenderingException;
30 use TYPO3\CMS\Form\Domain\Finishers\FinisherContext;
31 use TYPO3\CMS\Form\Domain\Model\FormDefinition;
32 use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
33 use TYPO3\CMS\Form\Domain\Model\FormElements\Page;
34 use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface;
35 use TYPO3\CMS\Form\Domain\Renderer\RendererInterface;
36 use TYPO3\CMS\Form\Domain\Runtime\Exception\PropertyMappingException;
37 use TYPO3\CMS\Form\Mvc\Validation\EmptyValidator;
38 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
39
40 /**
41 * This class implements the *runtime logic* of a form, i.e. deciding which
42 * page is shown currently, what the current values of the form are, trigger
43 * validation and property mapping.
44 *
45 * You generally receive an instance of this class by calling {@link \TYPO3\CMS\Form\Domain\Model\FormDefinition::bind}.
46 *
47 * Rendering a Form
48 * ================
49 *
50 * That's easy, just call render() on the FormRuntime:
51 *
52 * /---code php
53 * $form = $formDefinition->bind($request, $response);
54 * $renderedForm = $form->render();
55 * \---
56 *
57 * Accessing Form Values
58 * =====================
59 *
60 * In order to get the values the user has entered into the form, you can access
61 * this object like an array: If a form field with the identifier *firstName*
62 * exists, you can do **$form['firstName']** to retrieve its current value.
63 *
64 * You can also set values in the same way.
65 *
66 * Rendering Internals
67 * ===================
68 *
69 * The FormRuntime asks the FormDefinition about the configured Renderer
70 * which should be used ({@link \TYPO3\CMS\Form\Domain\Model\FormDefinition::getRendererClassName}),
71 * and then trigger render() on this element.
72 *
73 * This makes it possible to declaratively define how a form should be rendered.
74 *
75 * Scope: frontend
76 * **This class is NOT meant to be sub classed by developers.**
77 * @api
78 */
79 class FormRuntime implements RootRenderableInterface, \ArrayAccess
80 {
81 const HONEYPOT_NAME_SESSION_IDENTIFIER = 'tx_form_honeypot_name_';
82
83 /**
84 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
85 */
86 protected $objectManager;
87
88 /**
89 * @var \TYPO3\CMS\Form\Domain\Model\FormDefinition
90 */
91 protected $formDefinition;
92
93 /**
94 * @var \TYPO3\CMS\Extbase\Mvc\Web\Request
95 */
96 protected $request;
97
98 /**
99 * @var \TYPO3\CMS\Extbase\Mvc\Web\Response
100 */
101 protected $response;
102
103 /**
104 * @var \TYPO3\CMS\Form\Domain\Runtime\FormState
105 */
106 protected $formState;
107
108 /**
109 * The current page is the page which will be displayed to the user
110 * during rendering.
111 *
112 * If $currentPage is NULL, the *last* page has been submitted and
113 * finishing actions need to take place. You should use $this->isAfterLastPage()
114 * instead of explicitely checking for NULL.
115 *
116 * @var \TYPO3\CMS\Form\Domain\Model\FormElements\Page
117 */
118 protected $currentPage = null;
119
120 /**
121 * Reference to the page which has been shown on the last request (i.e.
122 * we have to handle the submitted data from lastDisplayedPage)
123 *
124 * @var \TYPO3\CMS\Form\Domain\Model\FormElements\Page
125 */
126 protected $lastDisplayedPage = null;
127
128 /**
129 * @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
130 */
131 protected $hashService;
132
133 /**
134 * @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
135 * @return void
136 * @internal
137 */
138 public function injectHashService(\TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService)
139 {
140 $this->hashService = $hashService;
141 }
142
143 /**
144 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
145 * @internal
146 */
147 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
148 {
149 $this->objectManager = $objectManager;
150 }
151
152 /**
153 * @param FormDefinition $formDefinition
154 * @param Request $request
155 * @param Response $response
156 * @api
157 */
158 public function __construct(FormDefinition $formDefinition, Request $request, Response $response)
159 {
160 $this->formDefinition = $formDefinition;
161 $arguments = $request->getArguments();
162 $this->request = clone $request;
163 $formIdentifier = $this->formDefinition->getIdentifier();
164 if (isset($arguments[$formIdentifier])) {
165 $this->request->setArguments($arguments[$formIdentifier]);
166 }
167
168 $this->response = $response;
169 }
170
171 /**
172 * @return void
173 * @internal
174 */
175 public function initializeObject()
176 {
177 $this->initializeFormStateFromRequest();
178 $this->initializeCurrentPageFromRequest();
179 $this->initializeHoneypotFromRequest();
180
181 if (!$this->isFirstRequest() && $this->getRequest()->getMethod() === 'POST') {
182 $this->processSubmittedFormValues();
183 }
184
185 $this->renderHoneypot();
186 }
187
188 /**
189 * @return void
190 */
191 protected function initializeFormStateFromRequest()
192 {
193 $serializedFormStateWithHmac = $this->request->getInternalArgument('__state');
194 if ($serializedFormStateWithHmac === null) {
195 $this->formState = GeneralUtility::makeInstance(FormState::class);
196 } else {
197 $serializedFormState = $this->hashService->validateAndStripHmac($serializedFormStateWithHmac);
198 $this->formState = unserialize(base64_decode($serializedFormState));
199 }
200 }
201
202 /**
203 * @return void
204 */
205 protected function initializeCurrentPageFromRequest()
206 {
207 if (!$this->formState->isFormSubmitted()) {
208 $this->currentPage = $this->formDefinition->getPageByIndex(0);
209 return;
210 }
211 $this->lastDisplayedPage = $this->formDefinition->getPageByIndex($this->formState->getLastDisplayedPageIndex());
212
213 // We know now that lastDisplayedPage is filled
214 $currentPageIndex = (int)$this->request->getInternalArgument('__currentPage');
215 if ($currentPageIndex > $this->lastDisplayedPage->getIndex() + 1) {
216 // We only allow jumps to following pages
217 $currentPageIndex = $this->lastDisplayedPage->getIndex() + 1;
218 }
219
220 // We now know that the user did not try to skip a page
221 if ($currentPageIndex === count($this->formDefinition->getPages())) {
222 // Last Page
223 $this->currentPage = null;
224 } else {
225 $this->currentPage = $this->formDefinition->getPageByIndex($currentPageIndex);
226 }
227 }
228
229 /**
230 * @return void
231 */
232 protected function initializeHoneypotFromRequest()
233 {
234 $renderingOptions = $this->formDefinition->getRenderingOptions();
235 if (!isset($renderingOptions['honeypot']['enable']) || $renderingOptions['honeypot']['enable'] === false || TYPO3_MODE === 'BE') {
236 return;
237 }
238
239 ArrayUtility::assertAllArrayKeysAreValid($renderingOptions['honeypot'], ['enable', 'formElementToUse']);
240
241 if (!$this->isFirstRequest()) {
242 $elementsCount = count($this->lastDisplayedPage->getElements());
243 if ($elementsCount === 0) {
244 return;
245 }
246
247 $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage);
248 if ($honeypotNameFromSession) {
249 $honeypotElement = $this->lastDisplayedPage->createElement($honeypotNameFromSession, $renderingOptions['honeypot']['formElementToUse']);
250 $validator = $this->objectManager->get(EmptyValidator::class);
251 $honeypotElement->addValidator($validator);
252 }
253 }
254 }
255
256 /**
257 * @return void
258 */
259 protected function renderHoneypot()
260 {
261 $renderingOptions = $this->formDefinition->getRenderingOptions();
262 if (!isset($renderingOptions['honeypot']['enable']) || $renderingOptions['honeypot']['enable'] === false || TYPO3_MODE === 'BE') {
263 return;
264 }
265
266 ArrayUtility::assertAllArrayKeysAreValid($renderingOptions['honeypot'], ['enable', 'formElementToUse']);
267
268 if (!$this->isAfterLastPage()) {
269 $elementsCount = count($this->currentPage->getElements());
270 if ($elementsCount === 0) {
271 return;
272 }
273
274 if (!$this->isFirstRequest()) {
275 $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage);
276 if ($honeypotNameFromSession) {
277 $honeypotElement = $this->formDefinition->getElementByIdentifier($honeypotNameFromSession);
278 if ($honeypotElement instanceof FormElementInterface) {
279 $this->lastDisplayedPage->removeElement($honeypotElement);
280 }
281 }
282 }
283
284 $elementsCount = count($this->currentPage->getElements());
285 $randomElementNumber = mt_rand(0, ($elementsCount - 1));
286 $honeypotName = substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, mt_rand(5, 26));
287
288 $referenceElement = $this->currentPage->getElements()[$randomElementNumber];
289 $honeypotElement = $this->currentPage->createElement($honeypotName, $renderingOptions['honeypot']['formElementToUse']);
290 $validator = $this->objectManager->get(EmptyValidator::class);
291
292 $honeypotElement->addValidator($validator);
293 if (mt_rand(0, 1) === 1) {
294 $this->currentPage->moveElementAfter($honeypotElement, $referenceElement);
295 } else {
296 $this->currentPage->moveElementBefore($honeypotElement, $referenceElement);
297 }
298 $this->setHoneypotNameInSession($this->currentPage, $honeypotName);
299 }
300 }
301
302 /**
303 * @param Page $page
304 * return null|string
305 */
306 protected function getHoneypotNameFromSession(Page $page)
307 {
308 if ($this->getTypoScriptFrontendController()->loginUser) {
309 $honeypotNameFromSession = $this->getTypoScriptFrontendController()->fe_user->getKey(
310 'user',
311 self::HONEYPOT_NAME_SESSION_IDENTIFIER . $this->getIdentifier() . $page->getIdentifier()
312 );
313 } else {
314 $honeypotNameFromSession = $this->getTypoScriptFrontendController()->fe_user->getKey(
315 'ses',
316 self::HONEYPOT_NAME_SESSION_IDENTIFIER . $this->getIdentifier() . $page->getIdentifier()
317 );
318 }
319 return $honeypotNameFromSession;
320 }
321
322 /**
323 * @param Page $page
324 * @param string $honeypotName
325 * @return void
326 */
327 protected function setHoneypotNameInSession(Page $page, string $honeypotName)
328 {
329 if ($this->getTypoScriptFrontendController()->loginUser) {
330 $this->getTypoScriptFrontendController()->fe_user->setKey(
331 'user',
332 self::HONEYPOT_NAME_SESSION_IDENTIFIER . $this->getIdentifier() . $page->getIdentifier(),
333 $honeypotName
334 );
335 } else {
336 $this->getTypoScriptFrontendController()->fe_user->setKey(
337 'ses',
338 self::HONEYPOT_NAME_SESSION_IDENTIFIER . $this->getIdentifier() . $page->getIdentifier(),
339 $honeypotName
340 );
341 }
342 }
343
344 /**
345 * Returns TRUE if the last page of the form has been submitted, otherwise FALSE
346 *
347 * @return bool
348 */
349 protected function isAfterLastPage(): bool
350 {
351 return $this->currentPage === null;
352 }
353
354 /**
355 * Returns TRUE if no previous page is stored in the FormState, otherwise FALSE
356 *
357 * @return bool
358 */
359 protected function isFirstRequest(): bool
360 {
361 return $this->lastDisplayedPage === null;
362 }
363
364 /**
365 * @return void
366 */
367 protected function processSubmittedFormValues()
368 {
369 $result = $this->mapAndValidatePage($this->lastDisplayedPage);
370 if ($result->hasErrors() && !$this->userWentBackToPreviousStep()) {
371 $this->currentPage = $this->lastDisplayedPage;
372 $this->request->setOriginalRequestMappingResults($result);
373 }
374 }
375
376 /**
377 * returns TRUE if the user went back to any previous step in the form.
378 *
379 * @return bool
380 */
381 protected function userWentBackToPreviousStep(): bool
382 {
383 return !$this->isAfterLastPage() && !$this->isFirstRequest() && $this->currentPage->getIndex() < $this->lastDisplayedPage->getIndex();
384 }
385
386 /**
387 * @param Page $page
388 * @return Result
389 * @throws PropertyMappingException
390 */
391 protected function mapAndValidatePage(Page $page): Result
392 {
393 $result = $this->objectManager->get(Result::class);
394 $requestArguments = $this->request->getArguments();
395
396 $propertyPathsForWhichPropertyMappingShouldHappen = [];
397 $registerPropertyPaths = function ($propertyPath) use (&$propertyPathsForWhichPropertyMappingShouldHappen) {
398 $propertyPathParts = explode('.', $propertyPath);
399 $accumulatedPropertyPathParts = [];
400 foreach ($propertyPathParts as $propertyPathPart) {
401 $accumulatedPropertyPathParts[] = $propertyPathPart;
402 $temporaryPropertyPath = implode('.', $accumulatedPropertyPathParts);
403 $propertyPathsForWhichPropertyMappingShouldHappen[$temporaryPropertyPath] = $temporaryPropertyPath;
404 }
405 };
406 foreach ($page->getElementsRecursively() as $element) {
407 $value = ArrayUtility::getValueByPath($requestArguments, $element->getIdentifier());
408 $element->onSubmit($this, $value, $requestArguments);
409
410 $this->formState->setFormValue($element->getIdentifier(), $value);
411 $registerPropertyPaths($element->getIdentifier());
412 }
413
414 // The more parts the path has, the more early it is processed
415 usort($propertyPathsForWhichPropertyMappingShouldHappen, function ($a, $b) {
416 return substr_count($b, '.') - substr_count($a, '.');
417 });
418
419 $processingRules = $this->formDefinition->getProcessingRules();
420
421 foreach ($propertyPathsForWhichPropertyMappingShouldHappen as $propertyPath) {
422 if (isset($processingRules[$propertyPath])) {
423 $processingRule = $processingRules[$propertyPath];
424 $value = $this->formState->getFormValue($propertyPath);
425 try {
426 $value = $processingRule->process($value);
427 } catch (PropertyException $exception) {
428 throw new PropertyMappingException(
429 'Failed to process FormValue at "' . $propertyPath . '" from "' . gettype($value) . '" to "' . $processingRule->getDataType() . '"',
430 1480024933,
431 $exception
432 );
433 }
434 $result->forProperty($propertyPath)->merge($processingRule->getProcessingMessages());
435 $this->formState->setFormValue($propertyPath, $value);
436 }
437 }
438
439 return $result;
440 }
441
442 /**
443 * Override the current page taken from the request, rendering the page with index $pageIndex instead.
444 *
445 * This is typically not needed in production code, but it is very helpful when displaying
446 * some kind of "preview" of the form.
447 *
448 * @param int $pageIndex
449 * @return void
450 * @api
451 */
452 public function overrideCurrentPage(int $pageIndex)
453 {
454 $this->currentPage = $this->formDefinition->getPageByIndex($pageIndex);
455 }
456
457 /**
458 * Render this form.
459 *
460 * @return null|string rendered form
461 * @throws RenderingException
462 * @api
463 */
464 public function render()
465 {
466 if ($this->isAfterLastPage()) {
467 $this->invokeFinishers();
468 return $this->response->getContent();
469 }
470
471 $this->formState->setLastDisplayedPageIndex($this->currentPage->getIndex());
472
473 if ($this->formDefinition->getRendererClassName() === null) {
474 throw new RenderingException(sprintf('The form definition "%s" does not have a rendererClassName set.', $this->formDefinition->getIdentifier()), 1326095912);
475 }
476 $rendererClassName = $this->formDefinition->getRendererClassName();
477 $renderer = $this->objectManager->get($rendererClassName);
478 if (!($renderer instanceof RendererInterface)) {
479 throw new RenderingException(sprintf('The renderer "%s" des not implement RendererInterface', $rendererClassName), 1326096024);
480 }
481
482 $controllerContext = $this->getControllerContext();
483
484 $renderer->setControllerContext($controllerContext);
485 $renderer->setFormRuntime($this);
486 return $renderer->render($this);
487 }
488
489 /**
490 * Executes all finishers of this form
491 *
492 * @return void
493 */
494 protected function invokeFinishers()
495 {
496 $finisherContext = $this->objectManager->get(FinisherContext::class,
497 $this,
498 $this->getControllerContext()
499 );
500 foreach ($this->formDefinition->getFinishers() as $finisher) {
501 $finisher->execute($finisherContext);
502 if ($finisherContext->isCancelled()) {
503 break;
504 }
505 }
506 }
507
508 /**
509 * @return string The identifier of underlying form
510 * @api
511 */
512 public function getIdentifier(): string
513 {
514 return $this->formDefinition->getIdentifier();
515 }
516
517 /**
518 * Get the request this object is bound to.
519 *
520 * This is mostly relevant inside Finishers, where you f.e. want to redirect
521 * the user to another page.
522 *
523 * @return Request the request this object is bound to
524 * @api
525 */
526 public function getRequest(): Request
527 {
528 return $this->request;
529 }
530
531 /**
532 * Get the response this object is bound to.
533 *
534 * This is mostly relevant inside Finishers, where you f.e. want to set response
535 * headers or output content.
536 *
537 * @return Response the response this object is bound to
538 * @api
539 */
540 public function getResponse(): Response
541 {
542 return $this->response;
543 }
544
545 /**
546 * Returns the currently selected page
547 *
548 * @return Page
549 * @api
550 */
551 public function getCurrentPage(): Page
552 {
553 return $this->currentPage;
554 }
555
556 /**
557 * Returns the previous page of the currently selected one or NULL if there is no previous page
558 *
559 * @return null|Page
560 * @api
561 */
562 public function getPreviousPage()
563 {
564 $previousPageIndex = $this->currentPage->getIndex() - 1;
565 if ($this->formDefinition->hasPageWithIndex($previousPageIndex)) {
566 return $this->formDefinition->getPageByIndex($previousPageIndex);
567 }
568 return null;
569 }
570
571 /**
572 * Returns the next page of the currently selected one or NULL if there is no next page
573 *
574 * @return null|Page
575 * @api
576 */
577 public function getNextPage()
578 {
579 $nextPageIndex = $this->currentPage->getIndex() + 1;
580 if ($this->formDefinition->hasPageWithIndex($nextPageIndex)) {
581 return $this->formDefinition->getPageByIndex($nextPageIndex);
582 }
583 return null;
584 }
585
586 /**
587 * @return ControllerContext
588 */
589 protected function getControllerContext(): ControllerContext
590 {
591 $uriBuilder = $this->objectManager->get(UriBuilder::class);
592 $uriBuilder->setRequest($this->request);
593 $controllerContext = $this->objectManager->get(ControllerContext::class);
594 $controllerContext->setRequest($this->request);
595 $controllerContext->setResponse($this->response);
596 $controllerContext->setArguments($this->objectManager->get(Arguments::class, []));
597 $controllerContext->setUriBuilder($uriBuilder);
598 return $controllerContext;
599 }
600
601 /**
602 * Abstract "type" of this Renderable. Is used during the rendering process
603 * to determine the template file or the View PHP class being used to render
604 * the particular element.
605 *
606 * @return string
607 * @api
608 */
609 public function getType(): string
610 {
611 return $this->formDefinition->getType();
612 }
613
614 /**
615 * @param string $identifier
616 * @return bool
617 * @internal
618 */
619 public function offsetExists($identifier)
620 {
621 if ($this->getElementValue($identifier) !== null) {
622 return true;
623 }
624
625 if (is_callable([$this, 'get' . ucfirst($identifier)])) {
626 return true;
627 }
628 if (is_callable([$this, 'has' . ucfirst($identifier)])) {
629 return true;
630 }
631 if (is_callable([$this, 'is' . ucfirst($identifier)])) {
632 return true;
633 }
634 if (property_exists($this, $identifier)) {
635 $propertyReflection = new PropertyReflection($this, $identifier);
636 return $propertyReflection->isPublic();
637 }
638
639 return false;
640 }
641
642 /**
643 * @param string $identifier
644 * @return mixed
645 * @internal
646 */
647 public function offsetGet($identifier)
648 {
649 if ($this->getElementValue($identifier) !== null) {
650 return $this->getElementValue($identifier);
651 }
652 $getterMethodName = 'get' . ucfirst($identifier);
653 if (is_callable([$this, $getterMethodName])) {
654 return $this->{$getterMethodName}();
655 }
656 return null;
657 }
658
659 /**
660 * @param string $identifier
661 * @param mixed $value
662 * @return void
663 * @internal
664 */
665 public function offsetSet($identifier, $value)
666 {
667 $this->formState->setFormValue($identifier, $value);
668 }
669
670 /**
671 * @param string $identifier
672 * @return void
673 * @internal
674 */
675 public function offsetUnset($identifier)
676 {
677 $this->formState->setFormValue($identifier, null);
678 }
679
680 /**
681 * Returns the value of the specified element
682 *
683 * @param string $identifier
684 * @return mixed
685 * @api
686 */
687 public function getElementValue(string $identifier)
688 {
689 $formValue = $this->formState->getFormValue($identifier);
690 if ($formValue !== null) {
691 return $formValue;
692 }
693 return $this->formDefinition->getElementDefaultValueByIdentifier($identifier);
694 }
695
696 /**
697 * @return array<Page> The Form's pages in the correct order
698 * @api
699 */
700 public function getPages(): array
701 {
702 return $this->formDefinition->getPages();
703 }
704
705 /**
706 * @return FormState
707 * @internal
708 */
709 public function getFormState(): FormState
710 {
711 return $this->formState;
712 }
713
714 /**
715 * Get all rendering options
716 *
717 * @return array associative array of rendering options
718 * @api
719 */
720 public function getRenderingOptions(): array
721 {
722 return $this->formDefinition->getRenderingOptions();
723 }
724
725 /**
726 * Get the renderer class name to be used to display this renderable;
727 * must implement RendererInterface
728 *
729 * @return string the renderer class name
730 * @api
731 */
732 public function getRendererClassName(): string
733 {
734 return $this->formDefinition->getRendererClassName();
735 }
736
737 /**
738 * Get the label which shall be displayed next to the form element
739 *
740 * @return string
741 * @api
742 */
743 public function getLabel(): string
744 {
745 return $this->formDefinition->getLabel();
746 }
747
748 /**
749 * Get the underlying form definition from the runtime
750 *
751 * @return FormDefinition
752 * @api
753 */
754 public function getFormDefinition(): FormDefinition
755 {
756 return $this->formDefinition;
757 }
758
759 /**
760 * This is a callback that is invoked by the Renderer before the corresponding element is rendered.
761 * Use this to access previously submitted values and/or modify the $formRuntime before an element
762 * is outputted to the browser.
763 *
764 * @param FormRuntime $formRuntime
765 * @return void
766 * @api
767 */
768 public function beforeRendering(FormRuntime $formRuntime)
769 {
770 }
771
772 /**
773 * @return TypoScriptFrontendController
774 */
775 protected function getTypoScriptFrontendController()
776 {
777 return $GLOBALS['TSFE'];
778 }
779 }