bind($request); * $renderedForm = $form->render(); * \--- * * Accessing Form Values * ===================== * * In order to get the values the user has entered into the form, you can access * this object like an array: If a form field with the identifier *firstName* * exists, you can do **$form['firstName']** to retrieve its current value. * * You can also set values in the same way. * * Rendering Internals * =================== * * The FormRuntime asks the FormDefinition about the configured Renderer * which should be used ({@link \TYPO3\CMS\Form\Domain\Model\FormDefinition::getRendererClassName}), * and then trigger render() on this Renderer. * * This makes it possible to declaratively define how a form should be rendered. * * Scope: frontend * **This class is NOT meant to be sub classed by developers.** * * @internal High cohesion to FormDefinition, may change any time * @todo: Declare final in v12 */ #[Autoconfigure(public: true, shared: false)] class FormRuntime implements RootRenderableInterface, \ArrayAccess { public const HONEYPOT_NAME_SESSION_IDENTIFIER = 'tx_form_honeypot_name_'; protected FormDefinition $formDefinition; protected RequestInterface $request; protected ResponseInterface $response; /** * @var FormState */ protected $formState; /** * Individual unique random form session identifier valid * for current user session. This value is not persisted server-side. * * @var FormSession|null */ protected $formSession; /** * The current page is the page which will be displayed to the user * during rendering. * * If $currentPage is NULL, the *last* page has been submitted and * finishing actions need to take place. You should use $this->isAfterLastPage() * instead of explicitly checking for NULL. * * @var Page|null */ protected $currentPage; /** * Reference to the page which has been shown on the last request (i.e. * we have to handle the submitted data from lastDisplayedPage) * * @var Page */ protected $lastDisplayedPage; /** * The current site language configuration. * * @var SiteLanguage */ protected $currentSiteLanguage; /** * Reference to the current running finisher * * @var FinisherInterface */ protected $currentFinisher; public function __construct( protected readonly ContainerInterface $container, protected readonly ConfigurationManagerInterface $configurationManager, protected readonly HashService $hashService, protected readonly ValidatorResolver $validatorResolver, private readonly Context $context, private readonly EventDispatcherInterface $eventDispatcher, ) { $this->response = new Response(); } public function setFormDefinition(FormDefinition $formDefinition) { $this->formDefinition = $formDefinition; } public function setRequest(RequestInterface $request) { $this->request = clone $request; } public function initialize() { $arguments = $this->request->getArguments(); $formIdentifier = $this->formDefinition->getIdentifier(); if (isset($arguments[$formIdentifier])) { $this->request = $this->request->withArguments($arguments[$formIdentifier]); } $this->initializeCurrentSiteLanguage(); $this->initializeFormSessionFromRequest(); $this->initializeFormStateFromRequest(); $this->triggerAfterFormStateInitialized(); $this->processVariants(); $this->initializeCurrentPageFromRequest(); $this->initializeHoneypotFromRequest(); // Only validate and set form values within the form state // if the current request is not the very first request // and the current request can be processed (POST request and uncached). if (!$this->isFirstRequest() && $this->canProcessFormSubmission()) { $this->processSubmittedFormValues(); } $this->renderHoneypot(); } /** * @todo `FormRuntime::$formSession` is still vulnerable to session fixation unless a real cookie-based process is used */ protected function initializeFormSessionFromRequest(): void { // Initialize the form session only if the current request can be processed // (POST request and uncached) to ensure unique sessions for each form submitter. if (!$this->canProcessFormSubmission()) { return; } /** @var ExtbaseRequestParameters $extbaseRequestParameters */ $extbaseRequestParameters = $this->request->getAttribute('extbase'); $sessionIdentifierFromRequest = $extbaseRequestParameters->getInternalArgument('__session'); $this->formSession = GeneralUtility::makeInstance(FormSession::class, $sessionIdentifierFromRequest); } /** * Initializes the current state of the form, based on the request * @throws BadRequestException */ protected function initializeFormStateFromRequest() { // Only try to reconstitute the form state if the current request // is not the very first request and if the current request can // be processed (POST request and uncached). /** @var ExtbaseRequestParameters $extbaseRequestParameters */ $extbaseRequestParameters = $this->request->getAttribute('extbase'); $serializedFormStateWithHmac = $extbaseRequestParameters->getInternalArgument('__state'); if ($serializedFormStateWithHmac === null || !$this->canProcessFormSubmission()) { $this->formState = GeneralUtility::makeInstance(FormState::class); } else { try { $serializedFormState = $this->hashService->validateAndStripHmac($serializedFormStateWithHmac, HashScope::FormState->prefix(), HashAlgo::SHA3_256); } catch (InvalidHashStringException $e) { throw new BadRequestException('The HMAC of the form state could not be validated.', 1581862823); } /* @phpstan-ignore unserialize.allowedClasses.insecure (Integrity check already happens via HMAC validation) */ $this->formState = unserialize(base64_decode($serializedFormState), ['allowed_classes' => true]); } } protected function triggerAfterFormStateInitialized(): void { $this->eventDispatcher->dispatch( new AfterFormStateInitializedEvent($this, $this->request) ); } /** * Initializes the current page data based on the current request, also modifiable by a hook */ protected function initializeCurrentPageFromRequest(): void { // If there was no previous form submissions or if the current request // can't be processed (no POST request and/or cached) then display the first // form step if (!$this->formState->isFormSubmitted() || !$this->canProcessFormSubmission()) { $this->currentPage = $this->formDefinition->getPageByIndex(0); if (!$this->currentPage->isEnabled()) { throw new FormException('Disabling the first page is not allowed', 1527186844); } $this->dispatchCurrentPageInitializedEvent(); return; } $this->lastDisplayedPage = $this->formDefinition->getPageByIndex($this->formState->getLastDisplayedPageIndex()); $currentPageIndex = $this->determineCurrentPageIndex(); if ($this->isLastPage($currentPageIndex)) { $this->currentPage = null; } else { $this->currentPage = $this->formDefinition->getPageByIndex($currentPageIndex); if (!$this->currentPage->isEnabled()) { if ($currentPageIndex === 0) { throw new FormException('Disabling the first page is not allowed', 1527186845); } if ($this->userWentBackToPreviousStep()) { $this->currentPage = $this->getPreviousEnabledPage(); } else { $this->currentPage = $this->getNextEnabledPage(); } } } $this->dispatchCurrentPageInitializedEvent($this->lastDisplayedPage); } private function isLastPage(int $currentPageIndex): bool { return $currentPageIndex >= count($this->formDefinition->getPages()); } /** * Get the current page index by resolving the request */ private function determineCurrentPageIndex(): int { /** @var ExtbaseRequestParameters $extbaseRequestParameters */ $extbaseRequestParameters = $this->request->getAttribute('extbase'); $currentPageIndex = (int)$extbaseRequestParameters->getInternalArgument('__currentPage'); if ($this->userWentBackToPreviousStep()) { if ($currentPageIndex < $this->lastDisplayedPage->getIndex()) { $currentPageIndex = $this->lastDisplayedPage->getIndex(); } } else { if ($currentPageIndex > $this->lastDisplayedPage->getIndex() + 1) { $currentPageIndex = $this->lastDisplayedPage->getIndex() + 1; } } return $currentPageIndex; } /** * Dispatches the AfterCurrentPageIsInitializedEvent event and sets the current page */ private function dispatchCurrentPageInitializedEvent(?Page $lastDisplayedPage = null): void { $event = $this->eventDispatcher->dispatch( new AfterCurrentPageIsResolvedEvent( $this->currentPage, $this, $lastDisplayedPage, $this->request, ) ); $this->currentPage = $event->currentPage; } /** * Checks if the honey pot is active, and adds a validator if so. */ protected function initializeHoneypotFromRequest() { $renderingOptions = $this->formDefinition->getRenderingOptions(); if (!isset($renderingOptions['honeypot']['enable']) || $renderingOptions['honeypot']['enable'] === false || ApplicationType::fromRequest($this->request)->isBackend() ) { return; } ArrayUtility::assertAllArrayKeysAreValid($renderingOptions['honeypot'], ['enable', 'formElementToUse']); if (!$this->isFirstRequest()) { $elementsCount = count($this->lastDisplayedPage->getElements()); if ($elementsCount === 0) { return; } $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage); if ($honeypotNameFromSession) { $honeypotElement = $this->lastDisplayedPage->createElement($honeypotNameFromSession, $renderingOptions['honeypot']['formElementToUse']); $validator = $this->validatorResolver->createValidator(EmptyValidator::class, [], $this->request); $honeypotElement->addValidator($validator); } } } /** * Renders a hidden field if the honey pot is active. */ protected function renderHoneypot() { $renderingOptions = $this->formDefinition->getRenderingOptions(); if (!isset($renderingOptions['honeypot']['enable']) || $this->currentPage === null || $renderingOptions['honeypot']['enable'] === false || ApplicationType::fromRequest($this->request)->isBackend() ) { return; } ArrayUtility::assertAllArrayKeysAreValid($renderingOptions['honeypot'], ['enable', 'formElementToUse']); if (!$this->isAfterLastPage()) { $elementsCount = count($this->currentPage->getElements()); if ($elementsCount === 0) { return; } if (!$this->isFirstRequest()) { $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage); if ($honeypotNameFromSession) { $honeypotElement = $this->formDefinition->getElementByIdentifier($honeypotNameFromSession); if ($honeypotElement instanceof FormElementInterface) { $this->lastDisplayedPage->removeElement($honeypotElement); } } } $elementsCount = count($this->currentPage->getElements()); $randomElementNumber = random_int(0, $elementsCount - 1); $honeypotName = substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, random_int(5, 26)); $referenceElement = $this->currentPage->getElements()[$randomElementNumber]; $honeypotElement = $this->currentPage->createElement($honeypotName, $renderingOptions['honeypot']['formElementToUse']); $validator = $this->validatorResolver->createValidator(EmptyValidator::class, [], $this->request); $honeypotElement->addValidator($validator); if (random_int(0, 1) === 1) { $this->currentPage->moveElementAfter($honeypotElement, $referenceElement); } else { $this->currentPage->moveElementBefore($honeypotElement, $referenceElement); } $this->setHoneypotNameInSession($this->currentPage, $honeypotName); } } /** * @return string|null */ protected function getHoneypotNameFromSession(Page $page) { if ($this->isFrontendUserAuthenticated()) { $honeypotNameFromSession = $this->getFrontendUser()->getKey( 'user', self::HONEYPOT_NAME_SESSION_IDENTIFIER . $this->getIdentifier() . $page->getIdentifier() ); } else { $honeypotNameFromSession = $this->getFrontendUser()->getKey( 'ses', self::HONEYPOT_NAME_SESSION_IDENTIFIER . $this->getIdentifier() . $page->getIdentifier() ); } return $honeypotNameFromSession; } protected function setHoneypotNameInSession(Page $page, string $honeypotName) { if ($this->isFrontendUserAuthenticated()) { $this->getFrontendUser()->setKey( 'user', self::HONEYPOT_NAME_SESSION_IDENTIFIER . $this->getIdentifier() . $page->getIdentifier(), $honeypotName ); } else { $this->getFrontendUser()->setKey( 'ses', self::HONEYPOT_NAME_SESSION_IDENTIFIER . $this->getIdentifier() . $page->getIdentifier(), $honeypotName ); } } /** * Necessary to know if honeypot information should be stored in the user session info, or in the anonymous session. */ protected function isFrontendUserAuthenticated(): bool { return (bool)$this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn', false); } protected function processVariants(): void { $conditionResolver = $this->getConditionResolver(); $renderables = array_merge([$this->formDefinition], $this->formDefinition->getRenderablesRecursively()); foreach ($renderables as $renderable) { if ($renderable instanceof VariableRenderableInterface) { $variants = $renderable->getVariants(); foreach ($variants as $variant) { if ($variant->conditionMatches($conditionResolver)) { $variant->apply(); } } } } } /** * Returns TRUE if the last page of the form has been submitted, otherwise FALSE */ protected function isAfterLastPage(): bool { return $this->currentPage === null; } /** * Returns TRUE if no previous page is stored in the FormState, otherwise FALSE */ protected function isFirstRequest(): bool { return $this->lastDisplayedPage === null; } protected function isPostRequest(): bool { return $this->getRequest()->getMethod() === 'POST'; } /** * Determine whether the surrounding content object is cached. * If no surrounding content object can be found (which would be strange) * we assume a cached request for safety which means that an empty form * will be rendered. */ protected function isRenderedCached(): bool { $contentObject = $this->request->getAttribute('currentContentObject'); // @todo: this does not work when rendering a cached `FLUIDTEMPLATE` (not nested in `COA_INT`) // Rendering the form other than with the controller, will never work out cleanly. // This likely can only be resolved by deprecating using the form render view helper // other than in a template for the form plugin and covering the use cases the VH was introduced // with a different concept return $contentObject === null || $contentObject->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER; } /** * Runs through all validations */ protected function processSubmittedFormValues() { $result = $this->mapAndValidatePage($this->lastDisplayedPage); if ($result->hasErrors() && !$this->userWentBackToPreviousStep()) { $this->currentPage = $this->lastDisplayedPage; /** @var ExtbaseRequestParameters $extbaseRequestParameters */ $extbaseRequestParameters = clone $this->request->getAttribute('extbase'); $extbaseRequestParameters->setOriginalRequestMappingResults($result); $this->request = $this->request->withAttribute('extbase', $extbaseRequestParameters); } } /** * returns TRUE if the user went back to any previous step in the form. */ protected function userWentBackToPreviousStep(): bool { return !$this->isAfterLastPage() && !$this->isFirstRequest() && $this->currentPage->getIndex() < $this->lastDisplayedPage->getIndex(); } /** * @throws PropertyMappingException */ protected function mapAndValidatePage(Page $page): Result { $result = GeneralUtility::makeInstance(Result::class); $requestArguments = $this->request->getArguments(); $propertyPathsForWhichPropertyMappingShouldHappen = []; $registerPropertyPaths = static function ($propertyPath) use (&$propertyPathsForWhichPropertyMappingShouldHappen) { $propertyPathParts = explode('.', $propertyPath); $accumulatedPropertyPathParts = []; foreach ($propertyPathParts as $propertyPathPart) { $accumulatedPropertyPathParts[] = $propertyPathPart; $temporaryPropertyPath = implode('.', $accumulatedPropertyPathParts); $propertyPathsForWhichPropertyMappingShouldHappen[$temporaryPropertyPath] = $temporaryPropertyPath; } }; $this->eventDispatcher->dispatch( new BeforeRenderableIsValidatedEvent( null, $this, $page, $this->request, ) ); foreach ($page->getElementsRecursively() as $element) { if (!$this->isRenderableEnabled($element)) { continue; } try { $value = ArrayUtility::getValueByPath($requestArguments, $element->getIdentifier(), '.'); } catch (MissingArrayPathException $exception) { $value = null; } $event = $this->eventDispatcher->dispatch( new BeforeRenderableIsValidatedEvent( $value, $this, $element, $this->request, ) ); $value = $event->value; $this->formState->setFormValue($element->getIdentifier(), $value); $registerPropertyPaths($element->getIdentifier()); } // The more parts the path has, the more early it is processed usort($propertyPathsForWhichPropertyMappingShouldHappen, static function ($a, $b) { return substr_count($b, '.') - substr_count($a, '.'); }); $processingRules = $this->formDefinition->getProcessingRules(); foreach ($propertyPathsForWhichPropertyMappingShouldHappen as $propertyPath) { if (isset($processingRules[$propertyPath])) { $processingRule = $processingRules[$propertyPath]; $value = $this->formState->getFormValue($propertyPath); try { $value = $processingRule->process($value); } catch (PropertyException $exception) { throw new PropertyMappingException( 'Failed to process FormValue at "' . $propertyPath . '" from "' . gettype($value) . '" to "' . $processingRule->getDataType() . '"', 1480024933, $exception ); } $result->forProperty($this->getIdentifier() . '.' . $propertyPath)->merge($processingRule->getProcessingMessages()); $this->formState->setFormValue($propertyPath, $value); } } return $result; } /** * Override the current page taken from the request, rendering the page with index $pageIndex instead. * * This is typically not needed in production code, but it is very helpful when displaying * some kind of "preview" of the form (e.g. form editor). * * @param int $pageIndex */ public function overrideCurrentPage(int $pageIndex) { $this->currentPage = $this->formDefinition->getPageByIndex($pageIndex); } /** * Render this form. * * @return string|null rendered form * @throws RenderingException */ public function render() { if ($this->isAfterLastPage()) { return $this->invokeFinishers(); } $this->processVariants(); $this->formState->setLastDisplayedPageIndex($this->currentPage->getIndex()); if ($this->formDefinition->getRendererClassName() === '') { throw new RenderingException(sprintf('The form definition "%s" does not have a rendererClassName set.', $this->formDefinition->getIdentifier()), 1326095912); } $rendererClassName = $this->formDefinition->getRendererClassName(); $renderer = $this->container->get($rendererClassName); if (!($renderer instanceof RendererInterface)) { throw new RenderingException(sprintf('The renderer "%s" des not implement RendererInterface', $rendererClassName), 1326096024); } $renderer->setFormRuntime($this); return $renderer->render(); } /** * Executes all finishers of this form */ protected function invokeFinishers(): string { $finisherContext = GeneralUtility::makeInstance( FinisherContext::class, $this, $this->request ); $output = ''; $this->response->getBody()->rewind(); $originalContent = $this->response->getBody()->getContents(); $this->response->getBody()->write(''); foreach ($this->formDefinition->getFinishers() as $finisher) { $this->currentFinisher = $finisher; $this->processVariants(); $finisherOutput = $finisher->execute($finisherContext); if (is_string($finisherOutput) && !empty($finisherOutput)) { $output .= $finisherOutput; } else { $this->response->getBody()->rewind(); $output .= $this->response->getBody()->getContents(); $this->response->getBody()->write(''); } if ($finisherContext->isCancelled()) { break; } } $this->response->getBody()->rewind(); $this->response->getBody()->write($originalContent); return $output; } /** * @return string The identifier of underlying form */ public function getIdentifier(): string { return $this->formDefinition->getIdentifier(); } /** * Get the request this object is bound to. * * This is mostly relevant inside Finishers, where you f.e. want to redirect * the user to another page. * * @return RequestInterface The request this object is bound to */ public function getRequest(): RequestInterface { return $this->request; } /** * Get the response this object is bound to. * * This is mostly relevant inside Finishers, where you f.e. want to set response * headers or output content. * * @return ResponseInterface the response this object is bound to */ public function getResponse(): ResponseInterface { return $this->response; } /** * Only process values if there is a post request and if the * surrounding content object is uncached. * Is this not the case, all possible submitted values will be discarded * and the first form step will be shown with an empty form state. * * @internal */ public function canProcessFormSubmission(): bool { return $this->isPostRequest() && !$this->isRenderedCached(); } /** * @internal */ public function getFormSession(): ?FormSession { return $this->formSession; } /** * Returns the currently selected page */ public function getCurrentPage(): ?Page { return $this->currentPage; } /** * Returns the previous page of the currently selected one or NULL if there is no previous page */ public function getPreviousPage(): ?Page { $previousPageIndex = $this->currentPage->getIndex() - 1; if ($this->formDefinition->hasPageWithIndex($previousPageIndex)) { return $this->formDefinition->getPageByIndex($previousPageIndex); } return null; } /** * Returns the next page of the currently selected one or NULL if there is no next page */ public function getNextPage(): ?Page { $nextPageIndex = $this->currentPage->getIndex() + 1; if ($this->formDefinition->hasPageWithIndex($nextPageIndex)) { return $this->formDefinition->getPageByIndex($nextPageIndex); } return null; } /** * Returns the previous enabled page of the currently selected one * or NULL if there is no previous page */ public function getPreviousEnabledPage(): ?Page { $previousPage = null; $previousPageIndex = $this->currentPage->getIndex() - 1; while ($previousPageIndex >= 0) { if ($this->formDefinition->hasPageWithIndex($previousPageIndex)) { $previousPage = $this->formDefinition->getPageByIndex($previousPageIndex); if ($previousPage->isEnabled()) { break; } $previousPage = null; $previousPageIndex--; } else { $previousPage = null; break; } } return $previousPage; } /** * Returns the next enabled page of the currently selected one or * NULL if there is no next page */ public function getNextEnabledPage(): ?Page { $nextPage = null; $pageCount = count($this->formDefinition->getPages()); $nextPageIndex = $this->currentPage->getIndex() + 1; while ($nextPageIndex < $pageCount) { if ($this->formDefinition->hasPageWithIndex($nextPageIndex)) { $nextPage = $this->formDefinition->getPageByIndex($nextPageIndex); $renderingOptions = $nextPage->getRenderingOptions(); if ( !isset($renderingOptions['enabled']) || (bool)$renderingOptions['enabled'] ) { break; } $nextPage = null; $nextPageIndex++; } else { $nextPage = null; break; } } return $nextPage; } /** * Abstract "type" of this Renderable. Is used during the rendering process * to determine the template file or the View PHP class being used to render * the particular element. */ public function getType(): string { return $this->formDefinition->getType(); } /** * @param string $identifier * @internal */ public function offsetExists(mixed $identifier): bool { $identifier = (string)$identifier; if ($this->getElementValue($identifier) !== null) { return true; } if (is_callable([$this, 'get' . ucfirst($identifier)])) { return true; } if (is_callable([$this, 'has' . ucfirst($identifier)])) { return true; } if (is_callable([$this, 'is' . ucfirst($identifier)])) { return true; } if (property_exists($this, $identifier)) { $propertyReflection = new \ReflectionProperty($this, $identifier); return $propertyReflection->isPublic(); } return false; } /** * @param string $identifier * @internal */ public function offsetGet(mixed $identifier): mixed { $identifier = (string)$identifier; if ($this->getElementValue($identifier) !== null) { return $this->getElementValue($identifier); } $getterMethodName = 'get' . ucfirst($identifier); if (is_callable([$this, $getterMethodName])) { return $this->{$getterMethodName}(); } return null; } /** * @param string $identifier * @internal */ public function offsetSet(mixed $identifier, mixed $value): void { $identifier = (string)$identifier; $this->formState->setFormValue($identifier, $value); } /** * @param string $identifier * @internal */ public function offsetUnset(mixed $identifier): void { $identifier = (string)$identifier; $this->formState->setFormValue($identifier, null); } /** * Returns the value of the specified element * * @return mixed */ public function getElementValue(string $identifier) { $formValue = $this->formState->getFormValue($identifier); if ($formValue !== null) { return $formValue; } return $this->formDefinition->getElementDefaultValueByIdentifier($identifier); } /** * @return array|Page[] The Form's pages in the correct order */ public function getPages(): array { return $this->formDefinition->getPages(); } /** * @internal */ public function getFormState(): ?FormState { return $this->formState; } /** * Get all rendering options * * @return array associative array of rendering options */ public function getRenderingOptions(): array { return $this->formDefinition->getRenderingOptions(); } /** * Get the renderer class name to be used to display this renderable; * must implement RendererInterface * * @return string the renderer class name */ public function getRendererClassName(): string { return $this->formDefinition->getRendererClassName(); } /** * Get the label which shall be displayed next to the form element */ public function getLabel(): string { return $this->formDefinition->getLabel(); } /** * Get the template name of the renderable */ public function getTemplateName(): string { return $this->formDefinition->getTemplateName(); } /** * Get the underlying form definition from the runtime */ public function getFormDefinition(): FormDefinition { return $this->formDefinition; } /** * Get the current site language configuration. * * @return SiteLanguage */ public function getCurrentSiteLanguage(): ?SiteLanguage { return $this->currentSiteLanguage; } /** * Override the current site language configuration. * * This is typically not needed in production code, but it is very * helpful when displaying some kind of "preview" of the form (e.g. form editor). * * @param SiteLanguage $currentSiteLanguage */ public function setCurrentSiteLanguage(SiteLanguage $currentSiteLanguage): void { $this->currentSiteLanguage = $currentSiteLanguage; } /** * Initialize the SiteLanguage object. * This is mainly used by the condition matcher. */ protected function initializeCurrentSiteLanguage(): void { if ($this->request->getAttribute('language') instanceof SiteLanguage) { $this->currentSiteLanguage = $this->request->getAttribute('language'); } else { $languageId = (int)$this->context->getPropertyFromAspect('language', 'id', 0); $pageId = $this->request->getAttribute('frontend.page.information')?->getId() ?? 0; $fakeSiteConfiguration = [ 'languages' => [ [ 'languageId' => $languageId, 'title' => 'Dummy', 'navigationTitle' => '', 'flag' => '', 'locale' => '', ], ], ]; $this->currentSiteLanguage = GeneralUtility::makeInstance(Site::class, 'form-dummy', $pageId, $fakeSiteConfiguration) ->getLanguageById($languageId); } } /** * Reference to the current running finisher */ public function getCurrentFinisher(): ?FinisherInterface { return $this->currentFinisher; } protected function getConditionResolver(): Resolver { $formValues = array_replace_recursive( $this->getFormState()->getFormValues(), $this->getRequest()->getArguments() ); $page = $this->getCurrentPage(); $stepIdentifier = $page !== null ? $page->getIdentifier() : ''; $stepType = $page !== null ? $page->getType() : ''; $finisherIdentifier = ''; if ($this->getCurrentFinisher() !== null) { if (method_exists($this->getCurrentFinisher(), 'getFinisherIdentifier')) { $finisherIdentifier = $this->getCurrentFinisher()->getFinisherIdentifier(); } else { $finisherIdentifier = (new \ReflectionClass($this->getCurrentFinisher()))->getShortName(); $finisherIdentifier = preg_replace('/Finisher$/', '', $finisherIdentifier); } } $contentObjectData = $this->request->getAttribute('currentContentObject')->data ?? []; return GeneralUtility::makeInstance( Resolver::class, 'form', [ 'formRuntime' => $this, 'formValues' => $formValues, 'stepIdentifier' => $stepIdentifier, 'stepType' => $stepType, 'finisherIdentifier' => $finisherIdentifier, 'contentObject' => $contentObjectData, 'request' => new RequestWrapper($this->getRequest()), 'site' => $this->getRequest()->getAttribute('site'), 'siteLanguage' => $this->getRequest()->getAttribute('language'), ] ); } protected function getFrontendUser(): FrontendUserAuthentication { return $this->request->getAttribute('frontend.user'); } protected function isRenderableEnabled(RenderableInterface $renderable): bool { if (!$renderable->isEnabled()) { return false; } while ($renderable = $renderable->getParentRenderable()) { if ($renderable instanceof RenderableInterface && !$renderable->isEnabled()) { return false; } } return true; } }