2 declare(strict_types
= 1);
3 namespace TYPO3\CMS\Form\Domain\Runtime
;
6 * This file is part of the TYPO3 CMS project.
8 * It originated from the Neos.Form package (www.neos.io)
10 * It is free software; you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License, either version 2
12 * of the License, or any later version.
14 * For the full copyright and license information, please read the
15 * LICENSE.txt file that was distributed with this source code.
17 * The TYPO3 project - inspiring people to share!
20 use Psr\Http\Message\ServerRequestInterface
;
21 use TYPO3\CMS\Core\Context\Context
;
22 use TYPO3\CMS\Core\Site\Entity\Site
;
23 use TYPO3\CMS\Core\Site\Entity\SiteLanguage
;
24 use TYPO3\CMS\Core\Utility\ArrayUtility
;
25 use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException
;
26 use TYPO3\CMS\Core\Utility\GeneralUtility
;
27 use TYPO3\CMS\Extbase\Error\Result
;
28 use TYPO3\CMS\Extbase\Mvc\Controller\Arguments
;
29 use TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
;
30 use TYPO3\CMS\Extbase\Mvc\Web\Request
;
31 use TYPO3\CMS\Extbase\Mvc\Web\Response
;
32 use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
;
33 use TYPO3\CMS\Extbase\Property\Exception
as PropertyException
;
34 use TYPO3\CMS\Form\Domain\Condition\ConditionContext
;
35 use TYPO3\CMS\Form\Domain\Condition\ConditionResolver
;
36 use TYPO3\CMS\Form\Domain\Exception\RenderingException
;
37 use TYPO3\CMS\Form\Domain\Finishers\FinisherContext
;
38 use TYPO3\CMS\Form\Domain\Finishers\FinisherInterface
;
39 use TYPO3\CMS\Form\Domain\Model\FormDefinition
;
40 use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface
;
41 use TYPO3\CMS\Form\Domain\Model\FormElements\Page
;
42 use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface
;
43 use TYPO3\CMS\Form\Domain\Model\Renderable\VariableRenderableInterface
;
44 use TYPO3\CMS\Form\Domain\Renderer\RendererInterface
;
45 use TYPO3\CMS\Form\Domain\Runtime\Exception\PropertyMappingException
;
46 use TYPO3\CMS\Form\Exception
as FormException
;
47 use TYPO3\CMS\Form\Mvc\Validation\EmptyValidator
;
48 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication
;
49 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
;
52 * This class implements the *runtime logic* of a form, i.e. deciding which
53 * page is shown currently, what the current values of the form are, trigger
54 * validation and property mapping.
56 * You generally receive an instance of this class by calling {@link \TYPO3\CMS\Form\Domain\Model\FormDefinition::bind}.
61 * That's easy, just call render() on the FormRuntime:
64 * $form = $formDefinition->bind($request, $response);
65 * $renderedForm = $form->render();
68 * Accessing Form Values
69 * =====================
71 * In order to get the values the user has entered into the form, you can access
72 * this object like an array: If a form field with the identifier *firstName*
73 * exists, you can do **$form['firstName']** to retrieve its current value.
75 * You can also set values in the same way.
80 * The FormRuntime asks the FormDefinition about the configured Renderer
81 * which should be used ({@link \TYPO3\CMS\Form\Domain\Model\FormDefinition::getRendererClassName}),
82 * and then trigger render() on this Renderer.
84 * This makes it possible to declaratively define how a form should be rendered.
87 * **This class is NOT meant to be sub classed by developers.**
90 class FormRuntime
implements RootRenderableInterface
, \ArrayAccess
92 const HONEYPOT_NAME_SESSION_IDENTIFIER
= 'tx_form_honeypot_name_';
95 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
97 protected $objectManager;
100 * @var \TYPO3\CMS\Form\Domain\Model\FormDefinition
102 protected $formDefinition;
105 * @var \TYPO3\CMS\Extbase\Mvc\Web\Request
110 * @var \TYPO3\CMS\Extbase\Mvc\Web\Response
115 * @var \TYPO3\CMS\Form\Domain\Runtime\FormState
117 protected $formState;
120 * The current page is the page which will be displayed to the user
123 * If $currentPage is NULL, the *last* page has been submitted and
124 * finishing actions need to take place. You should use $this->isAfterLastPage()
125 * instead of explicitely checking for NULL.
127 * @var \TYPO3\CMS\Form\Domain\Model\FormElements\Page
129 protected $currentPage;
132 * Reference to the page which has been shown on the last request (i.e.
133 * we have to handle the submitted data from lastDisplayedPage)
135 * @var \TYPO3\CMS\Form\Domain\Model\FormElements\Page
137 protected $lastDisplayedPage;
140 * @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
142 protected $hashService;
145 * The current site language configuration.
149 protected $currentSiteLanguage = null
;
152 * Reference to the current running finisher
154 * @var \TYPO3\CMS\Form\Domain\Finishers\FinisherInterface
156 protected $currentFinisher = null
;
159 * @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
162 public function injectHashService(\TYPO3\CMS\Extbase\Security\Cryptography\HashService
$hashService)
164 $this->hashService
= $hashService;
168 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
171 public function injectObjectManager(\TYPO3\CMS\Extbase\
Object\ObjectManagerInterface
$objectManager)
173 $this->objectManager
= $objectManager;
177 * @param FormDefinition $formDefinition
178 * @param Request $request
179 * @param Response $response
182 public function __construct(FormDefinition
$formDefinition, Request
$request, Response
$response)
184 $this->formDefinition
= $formDefinition;
185 $arguments = $request->getArguments();
186 $this->request
= clone $request;
187 $formIdentifier = $this->formDefinition
->getIdentifier();
188 if (isset($arguments[$formIdentifier])) {
189 $this->request
->setArguments($arguments[$formIdentifier]);
192 $this->response
= $response;
198 public function initializeObject()
200 $this->initializeCurrentSiteLanguage();
201 $this->initializeFormStateFromRequest();
202 $this->processVariants();
203 $this->initializeCurrentPageFromRequest();
204 $this->initializeHoneypotFromRequest();
206 if (!$this->isFirstRequest() && $this->getRequest()->getMethod() === 'POST') {
207 $this->processSubmittedFormValues();
210 $this->renderHoneypot();
214 * Initializes the current state of the form, based on the request
216 protected function initializeFormStateFromRequest()
218 $serializedFormStateWithHmac = $this->request
->getInternalArgument('__state');
219 if ($serializedFormStateWithHmac === null
) {
220 $this->formState
= GeneralUtility
::makeInstance(FormState
::class);
222 $serializedFormState = $this->hashService
->validateAndStripHmac($serializedFormStateWithHmac);
223 $this->formState
= unserialize(base64_decode($serializedFormState));
228 * Initializes the current page data based on the current request, also modifiable by a hook
230 protected function initializeCurrentPageFromRequest()
232 if (!$this->formState
->isFormSubmitted()) {
233 $this->currentPage
= $this->formDefinition
->getPageByIndex(0);
234 $renderingOptions = $this->currentPage
->getRenderingOptions();
236 if (!$this->currentPage
->isEnabled()) {
237 throw new FormException('Disabling the first page is not allowed', 1527186844);
240 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterInitializeCurrentPage'] ??
[] as $className) {
241 $hookObj = GeneralUtility
::makeInstance($className);
242 if (method_exists($hookObj, 'afterInitializeCurrentPage')) {
243 $this->currentPage
= $hookObj->afterInitializeCurrentPage(
247 $this->request
->getArguments()
254 $this->lastDisplayedPage
= $this->formDefinition
->getPageByIndex($this->formState
->getLastDisplayedPageIndex());
255 $currentPageIndex = (int)$this->request
->getInternalArgument('__currentPage');
257 if ($this->userWentBackToPreviousStep()) {
258 if ($currentPageIndex < $this->lastDisplayedPage
->getIndex()) {
259 $currentPageIndex = $this->lastDisplayedPage
->getIndex();
262 if ($currentPageIndex > $this->lastDisplayedPage
->getIndex() +
1) {
263 $currentPageIndex = $this->lastDisplayedPage
->getIndex() +
1;
267 if ($currentPageIndex >= count($this->formDefinition
->getPages())) {
269 $this->currentPage
= null
;
271 $this->currentPage
= $this->formDefinition
->getPageByIndex($currentPageIndex);
272 $renderingOptions = $this->currentPage
->getRenderingOptions();
274 if (!$this->currentPage
->isEnabled()) {
275 if ($currentPageIndex === 0) {
276 throw new FormException('Disabling the first page is not allowed', 1527186845);
279 if ($this->userWentBackToPreviousStep()) {
280 $this->currentPage
= $this->getPreviousEnabledPage();
282 $this->currentPage
= $this->getNextEnabledPage();
287 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterInitializeCurrentPage'] ??
[] as $className) {
288 $hookObj = GeneralUtility
::makeInstance($className);
289 if (method_exists($hookObj, 'afterInitializeCurrentPage')) {
290 $this->currentPage
= $hookObj->afterInitializeCurrentPage(
293 $this->lastDisplayedPage
,
294 $this->request
->getArguments()
301 * Checks if the honey pot is active, and adds a validator if so.
303 protected function initializeHoneypotFromRequest()
305 $renderingOptions = $this->formDefinition
->getRenderingOptions();
306 if (!isset($renderingOptions['honeypot']['enable']) ||
$renderingOptions['honeypot']['enable'] === false || TYPO3_MODE
=== 'BE') {
310 ArrayUtility
::assertAllArrayKeysAreValid($renderingOptions['honeypot'], ['enable', 'formElementToUse']);
312 if (!$this->isFirstRequest()) {
313 $elementsCount = count($this->lastDisplayedPage
->getElements());
314 if ($elementsCount === 0) {
318 $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage
);
319 if ($honeypotNameFromSession) {
320 $honeypotElement = $this->lastDisplayedPage
->createElement($honeypotNameFromSession, $renderingOptions['honeypot']['formElementToUse']);
321 $validator = $this->objectManager
->get(EmptyValidator
::class);
322 $honeypotElement->addValidator($validator);
328 * Renders a hidden field if the honey pot is active.
330 protected function renderHoneypot()
332 $renderingOptions = $this->formDefinition
->getRenderingOptions();
333 if (!isset($renderingOptions['honeypot']['enable']) ||
$renderingOptions['honeypot']['enable'] === false || TYPO3_MODE
=== 'BE') {
337 ArrayUtility
::assertAllArrayKeysAreValid($renderingOptions['honeypot'], ['enable', 'formElementToUse']);
339 if (!$this->isAfterLastPage()) {
340 $elementsCount = count($this->currentPage
->getElements());
341 if ($elementsCount === 0) {
345 if (!$this->isFirstRequest()) {
346 $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage
);
347 if ($honeypotNameFromSession) {
348 $honeypotElement = $this->formDefinition
->getElementByIdentifier($honeypotNameFromSession);
349 if ($honeypotElement instanceof FormElementInterface
) {
350 $this->lastDisplayedPage
->removeElement($honeypotElement);
355 $elementsCount = count($this->currentPage
->getElements());
356 $randomElementNumber = mt_rand(0, $elementsCount - 1);
357 $honeypotName = substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, mt_rand(5, 26));
359 $referenceElement = $this->currentPage
->getElements()[$randomElementNumber];
360 $honeypotElement = $this->currentPage
->createElement($honeypotName, $renderingOptions['honeypot']['formElementToUse']);
361 $validator = $this->objectManager
->get(EmptyValidator
::class);
363 $honeypotElement->addValidator($validator);
364 if (mt_rand(0, 1) === 1) {
365 $this->currentPage
->moveElementAfter($honeypotElement, $referenceElement);
367 $this->currentPage
->moveElementBefore($honeypotElement, $referenceElement);
369 $this->setHoneypotNameInSession($this->currentPage
, $honeypotName);
375 * @return string|null
377 protected function getHoneypotNameFromSession(Page
$page)
379 if ($this->isFrontendUserAuthenticated()) {
380 $honeypotNameFromSession = $this->getFrontendUser()->getKey(
382 self
::HONEYPOT_NAME_SESSION_IDENTIFIER
. $this->getIdentifier() . $page->getIdentifier()
385 $honeypotNameFromSession = $this->getFrontendUser()->getKey(
387 self
::HONEYPOT_NAME_SESSION_IDENTIFIER
. $this->getIdentifier() . $page->getIdentifier()
390 return $honeypotNameFromSession;
395 * @param string $honeypotName
397 protected function setHoneypotNameInSession(Page
$page, string $honeypotName)
399 if ($this->isFrontendUserAuthenticated()) {
400 $this->getFrontendUser()->setKey(
402 self
::HONEYPOT_NAME_SESSION_IDENTIFIER
. $this->getIdentifier() . $page->getIdentifier(),
406 $this->getFrontendUser()->setKey(
408 self
::HONEYPOT_NAME_SESSION_IDENTIFIER
. $this->getIdentifier() . $page->getIdentifier(),
415 * Necessary to know if honeypot information should be stored in the user session info, or in the anonymous session
417 * @return bool true when a frontend user is logged, otherwise false
419 protected function isFrontendUserAuthenticated(): bool
421 return (bool
)GeneralUtility
::makeInstance(Context
::class)
422 ->getPropertyFromAspect('frontend.user', 'isLoggedIn', false
);
427 protected function processVariants()
429 $conditionResolver = $this->getConditionResolver();
431 $renderables = array_merge([$this->formDefinition
], $this->formDefinition
->getRenderablesRecursively());
432 foreach ($renderables as $renderable) {
433 if ($renderable instanceof VariableRenderableInterface
) {
434 $variants = $renderable->getVariants();
435 foreach ($variants as $variant) {
436 if ($variant->conditionMatches($conditionResolver)) {
445 * Returns TRUE if the last page of the form has been submitted, otherwise FALSE
449 protected function isAfterLastPage(): bool
451 return $this->currentPage
=== null
;
455 * Returns TRUE if no previous page is stored in the FormState, otherwise FALSE
459 protected function isFirstRequest(): bool
461 return $this->lastDisplayedPage
=== null
;
465 * Runs throuh all validations
467 protected function processSubmittedFormValues()
469 $result = $this->mapAndValidatePage($this->lastDisplayedPage
);
470 if ($result->hasErrors() && !$this->userWentBackToPreviousStep()) {
471 $this->currentPage
= $this->lastDisplayedPage
;
472 $this->request
->setOriginalRequestMappingResults($result);
477 * returns TRUE if the user went back to any previous step in the form.
481 protected function userWentBackToPreviousStep(): bool
483 return !$this->isAfterLastPage() && !$this->isFirstRequest() && $this->currentPage
->getIndex() < $this->lastDisplayedPage
->getIndex();
489 * @throws PropertyMappingException
491 protected function mapAndValidatePage(Page
$page): Result
493 $result = $this->objectManager
->get(Result
::class);
494 $requestArguments = $this->request
->getArguments();
496 $propertyPathsForWhichPropertyMappingShouldHappen = [];
497 $registerPropertyPaths = function ($propertyPath) use (&$propertyPathsForWhichPropertyMappingShouldHappen) {
498 $propertyPathParts = explode('.', $propertyPath);
499 $accumulatedPropertyPathParts = [];
500 foreach ($propertyPathParts as $propertyPathPart) {
501 $accumulatedPropertyPathParts[] = $propertyPathPart;
502 $temporaryPropertyPath = implode('.', $accumulatedPropertyPathParts);
503 $propertyPathsForWhichPropertyMappingShouldHappen[$temporaryPropertyPath] = $temporaryPropertyPath;
509 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterSubmit'] ??
[] as $className) {
510 $hookObj = GeneralUtility
::makeInstance($className);
511 if (method_exists($hookObj, 'afterSubmit')) {
512 $value = $hookObj->afterSubmit(
521 foreach ($page->getElementsRecursively() as $element) {
522 if (!$element->isEnabled()) {
527 $value = ArrayUtility
::getValueByPath($requestArguments, $element->getIdentifier(), '.');
528 } catch (MissingArrayPathException
$exception) {
532 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterSubmit'] ??
[] as $className) {
533 $hookObj = GeneralUtility
::makeInstance($className);
534 if (method_exists($hookObj, 'afterSubmit')) {
535 $value = $hookObj->afterSubmit(
544 $this->formState
->setFormValue($element->getIdentifier(), $value);
545 $registerPropertyPaths($element->getIdentifier());
548 // The more parts the path has, the more early it is processed
549 usort($propertyPathsForWhichPropertyMappingShouldHappen, function ($a, $b) {
550 return substr_count($b, '.') - substr_count($a, '.');
553 $processingRules = $this->formDefinition
->getProcessingRules();
555 foreach ($propertyPathsForWhichPropertyMappingShouldHappen as $propertyPath) {
556 if (isset($processingRules[$propertyPath])) {
557 $processingRule = $processingRules[$propertyPath];
558 $value = $this->formState
->getFormValue($propertyPath);
560 $value = $processingRule->process($value);
561 } catch (PropertyException
$exception) {
562 throw new PropertyMappingException(
563 'Failed to process FormValue at "' . $propertyPath . '" from "' . gettype($value) . '" to "' . $processingRule->getDataType() . '"',
568 $result->forProperty($this->getIdentifier() . '.' . $propertyPath)->merge($processingRule->getProcessingMessages());
569 $this->formState
->setFormValue($propertyPath, $value);
577 * Override the current page taken from the request, rendering the page with index $pageIndex instead.
579 * This is typically not needed in production code, but it is very helpful when displaying
580 * some kind of "preview" of the form (e.g. form editor).
582 * @param int $pageIndex
585 public function overrideCurrentPage(int $pageIndex)
587 $this->currentPage
= $this->formDefinition
->getPageByIndex($pageIndex);
593 * @return string|null rendered form
594 * @throws RenderingException
597 public function render()
599 if ($this->isAfterLastPage()) {
600 return $this->invokeFinishers();
602 $this->processVariants();
604 $this->formState
->setLastDisplayedPageIndex($this->currentPage
->getIndex());
606 if ($this->formDefinition
->getRendererClassName() === '') {
607 throw new RenderingException(sprintf('The form definition "%s" does not have a rendererClassName set.', $this->formDefinition
->getIdentifier()), 1326095912);
609 $rendererClassName = $this->formDefinition
->getRendererClassName();
610 $renderer = $this->objectManager
->get($rendererClassName);
611 if (!($renderer instanceof RendererInterface
)) {
612 throw new RenderingException(sprintf('The renderer "%s" des not implement RendererInterface', $rendererClassName), 1326096024);
615 $controllerContext = $this->getControllerContext();
617 $renderer->setControllerContext($controllerContext);
618 $renderer->setFormRuntime($this);
619 return $renderer->render($this);
623 * Executes all finishers of this form
627 protected function invokeFinishers(): string
629 $finisherContext = $this->objectManager
->get(
630 FinisherContext
::class,
632 $this->getControllerContext()
636 $originalContent = $this->response
->getContent();
637 $this->response
->setContent(null
);
638 foreach ($this->formDefinition
->getFinishers() as $finisher) {
639 $this->currentFinisher
= $finisher;
640 $this->processVariants();
642 $finisherOutput = $finisher->execute($finisherContext);
643 if (is_string($finisherOutput) && !empty($finisherOutput)) {
644 $output .= $finisherOutput;
646 $output .= $this->response
->getContent();
647 $this->response
->setContent(null
);
650 if ($finisherContext->isCancelled()) {
654 $this->response
->setContent($originalContent);
660 * @return string The identifier of underlying form
663 public function getIdentifier(): string
665 return $this->formDefinition
->getIdentifier();
669 * Get the request this object is bound to.
671 * This is mostly relevant inside Finishers, where you f.e. want to redirect
672 * the user to another page.
674 * @return Request the request this object is bound to
677 public function getRequest(): Request
679 return $this->request
;
683 * Get the response this object is bound to.
685 * This is mostly relevant inside Finishers, where you f.e. want to set response
686 * headers or output content.
688 * @return Response the response this object is bound to
691 public function getResponse(): Response
693 return $this->response
;
697 * Returns the currently selected page
702 public function getCurrentPage(): ?Page
704 return $this->currentPage
;
708 * Returns the previous page of the currently selected one or NULL if there is no previous page
713 public function getPreviousPage(): ?Page
715 $previousPageIndex = $this->currentPage
->getIndex() - 1;
716 if ($this->formDefinition
->hasPageWithIndex($previousPageIndex)) {
717 return $this->formDefinition
->getPageByIndex($previousPageIndex);
723 * Returns the next page of the currently selected one or NULL if there is no next page
728 public function getNextPage(): ?Page
730 $nextPageIndex = $this->currentPage
->getIndex() +
1;
731 if ($this->formDefinition
->hasPageWithIndex($nextPageIndex)) {
732 return $this->formDefinition
->getPageByIndex($nextPageIndex);
738 * Returns the previous enabled page of the currently selected one
739 * or NULL if there is no previous page
744 public function getPreviousEnabledPage(): ?Page
746 $previousPage = null
;
747 $previousPageIndex = $this->currentPage
->getIndex() - 1;
748 while ($previousPageIndex >= 0) {
749 if ($this->formDefinition
->hasPageWithIndex($previousPageIndex)) {
750 $previousPage = $this->formDefinition
->getPageByIndex($previousPageIndex);
752 if ($previousPage->isEnabled()) {
756 $previousPage = null
;
757 $previousPageIndex--;
759 $previousPage = null
;
764 return $previousPage;
768 * Returns the next enabled page of the currently selected one or
769 * NULL if there is no next page
774 public function getNextEnabledPage(): ?Page
777 $pageCount = count($this->formDefinition
->getPages());
778 $nextPageIndex = $this->currentPage
->getIndex() +
1;
780 while ($nextPageIndex < $pageCount) {
781 if ($this->formDefinition
->hasPageWithIndex($nextPageIndex)) {
782 $nextPage = $this->formDefinition
->getPageByIndex($nextPageIndex);
783 $renderingOptions = $nextPage->getRenderingOptions();
785 !isset($renderingOptions['enabled'])
786 ||
(bool
)$renderingOptions['enabled']
802 * @return ControllerContext
804 protected function getControllerContext(): ControllerContext
806 $uriBuilder = $this->objectManager
->get(UriBuilder
::class);
807 $uriBuilder->setRequest($this->request
);
808 $controllerContext = $this->objectManager
->get(ControllerContext
::class);
809 $controllerContext->setRequest($this->request
);
810 $controllerContext->setResponse($this->response
);
811 $controllerContext->setArguments($this->objectManager
->get(Arguments
::class, []));
812 $controllerContext->setUriBuilder($uriBuilder);
813 return $controllerContext;
817 * Abstract "type" of this Renderable. Is used during the rendering process
818 * to determine the template file or the View PHP class being used to render
819 * the particular element.
824 public function getType(): string
826 return $this->formDefinition
->getType();
830 * @param string $identifier
834 public function offsetExists($identifier)
836 if ($this->getElementValue($identifier) !== null
) {
840 if (is_callable([$this, 'get' . ucfirst($identifier)])) {
843 if (is_callable([$this, 'has' . ucfirst($identifier)])) {
846 if (is_callable([$this, 'is' . ucfirst($identifier)])) {
849 if (property_exists($this, $identifier)) {
850 $propertyReflection = new \
ReflectionProperty($this, $identifier);
851 return $propertyReflection->isPublic();
858 * @param string $identifier
862 public function offsetGet($identifier)
864 if ($this->getElementValue($identifier) !== null
) {
865 return $this->getElementValue($identifier);
867 $getterMethodName = 'get' . ucfirst($identifier);
868 if (is_callable([$this, $getterMethodName])) {
869 return $this->{$getterMethodName}();
875 * @param string $identifier
876 * @param mixed $value
879 public function offsetSet($identifier, $value)
881 $this->formState
->setFormValue($identifier, $value);
885 * @param string $identifier
888 public function offsetUnset($identifier)
890 $this->formState
->setFormValue($identifier, null
);
894 * Returns the value of the specified element
896 * @param string $identifier
900 public function getElementValue(string $identifier)
902 $formValue = $this->formState
->getFormValue($identifier);
903 if ($formValue !== null
) {
906 return $this->formDefinition
->getElementDefaultValueByIdentifier($identifier);
910 * @return array<Page> The Form's pages in the correct order
913 public function getPages(): array
915 return $this->formDefinition
->getPages();
919 * @return FormState|null
922 public function getFormState(): ?FormState
924 return $this->formState
;
928 * Get all rendering options
930 * @return array associative array of rendering options
933 public function getRenderingOptions(): array
935 return $this->formDefinition
->getRenderingOptions();
939 * Get the renderer class name to be used to display this renderable;
940 * must implement RendererInterface
942 * @return string the renderer class name
945 public function getRendererClassName(): string
947 return $this->formDefinition
->getRendererClassName();
951 * Get the label which shall be displayed next to the form element
956 public function getLabel(): string
958 return $this->formDefinition
->getLabel();
962 * Get the template name of the renderable
967 public function getTemplateName(): string
969 return $this->formDefinition
->getTemplateName();
973 * Get the underlying form definition from the runtime
975 * @return FormDefinition
978 public function getFormDefinition(): FormDefinition
980 return $this->formDefinition
;
984 * Get the current site language configuration.
986 * @return SiteLanguage
989 public function getCurrentSiteLanguage(): ?SiteLanguage
991 return $this->currentSiteLanguage
;
995 * Override the the current site language configuration.
997 * This is typically not needed in production code, but it is very
998 * helpful when displaying some kind of "preview" of the form (e.g. form editor).
1000 * @param SiteLanguage $currentSiteLanguage
1003 public function setCurrentSiteLanguage(SiteLanguage
$currentSiteLanguage): void
1005 $this->currentSiteLanguage
= $currentSiteLanguage;
1009 * Initialize the SiteLanguage object.
1010 * This is mainly used by the condition matcher.
1012 protected function initializeCurrentSiteLanguage(): void
1015 $GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
1016 && $GLOBALS['TYPO3_REQUEST']->getAttribute('language') instanceof SiteLanguage
1018 $this->currentSiteLanguage
= $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
1023 if (TYPO3_MODE
=== 'FE') {
1024 $pageId = $this->getTypoScriptFrontendController()->id
;
1025 $languageId = $this->getTypoScriptFrontendController()->sys_language_uid
;
1028 $fakeSiteConfiguration = [
1031 'languageId' => $languageId,
1033 'navigationTitle' => '',
1034 'typo3Language' => '',
1044 $this->currentSiteLanguage
= GeneralUtility
::makeInstance(Site
::class, 'form-dummy', $pageId, $fakeSiteConfiguration)
1045 ->getLanguageById($languageId);
1050 * Reference to the current running finisher
1052 * @return FinisherInterface|null
1055 public function getCurrentFinisher(): ?FinisherInterface
1057 return $this->currentFinisher
;
1061 * @return ConditionResolver
1063 protected function getConditionResolver(): ConditionResolver
1065 /** @var \TYPO3\CMS\Form\Domain\Condition\ConditionResolver $conditionResolver */
1066 $conditionResolver = $this->objectManager
->get(
1067 ConditionResolver
::class,
1068 GeneralUtility
::makeInstance(ConditionContext
::class, $this)
1070 return $conditionResolver;
1074 * @return FrontendUserAuthentication
1076 protected function getFrontendUser(): FrontendUserAuthentication
1078 return $this->getTypoScriptFrontendController()->fe_user
;
1082 * @return TypoScriptFrontendController
1084 protected function getTypoScriptFrontendController(): TypoScriptFrontendController
1086 return $GLOBALS['TSFE'];