configurationManager = $configurationManager; } public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('name', 'string', 'Name of input tag'); $this->registerArgument('value', 'mixed', 'Value of input tag'); $this->registerArgument('property', 'string', 'Name of Object Property. If used in conjunction with , the "name" property will be ignored, while "value" can be used to specify a default field value instead of the object property value.'); } /** * Getting the current configuration for respectSubmittedDataValue. */ public function getRespectSubmittedDataValue(): bool { return $this->respectSubmittedDataValue; } /** * Define respectSubmittedDataValue to enable or disable the usage of the submitted values in the viewhelper. */ public function setRespectSubmittedDataValue(bool $respectSubmittedDataValue): void { $this->respectSubmittedDataValue = $respectSubmittedDataValue; } /** * Get the name of this form element. * Either returns arguments['name'], or the correct name for Object Access. * In case property is something like bla.blubb (hierarchical), then [bla][blubb] is generated. */ protected function getName(): string { $name = $this->getNameWithoutPrefix(); return $this->prefixFieldName($name); } /** * Shortcut for retrieving the request from the controller context * * @return RequestInterface The extbase (!) request. All these VH's are extbase-only. */ protected function getRequest(): RequestInterface { if (!$this->renderingContext->hasAttribute(ServerRequestInterface::class) || !$this->renderingContext->getAttribute(ServerRequestInterface::class) instanceof RequestInterface ) { throw new \RuntimeException( 'Form ViewHelpers are Extbase specific and need an Extbase Request to work', 1663617170 ); } return $this->renderingContext->getAttribute(ServerRequestInterface::class); } /** * Get the name of this form element, without prefix. */ protected function getNameWithoutPrefix(): string { if ($this->isObjectAccessorMode()) { $formObjectName = $this->renderingContext->getViewHelperVariableContainer()->get( FormViewHelper::class, 'formObjectName' ); if (!empty($formObjectName)) { $propertySegments = explode('.', (string)($this->arguments['property'] ?? '')); $propertyPath = ''; foreach ($propertySegments as $segment) { $propertyPath .= '[' . $segment . ']'; } $name = $formObjectName . $propertyPath; } else { $name = $this->arguments['property'] ?? ''; } } else { $name = $this->arguments['name'] ?? ''; } if ($this->hasArgument('value') && is_object($this->arguments['value']) && !$this->persistenceManager->isNewObject($this->arguments['value']) ) { $name .= '[__identity]'; } return (string)$name; } /** * Returns the current value of this Form ViewHelper and converts it to an identifier string in case it's an object * The value is determined as follows: * * If property mapping errors occurred and the form is re-displayed, the *last submitted* value is returned * * If a "value" attribute was specified, this value is used (preferring an "override" from integrators) * * Else the bound property value is returned (only in objectAccessor-mode) * * Note: This method should *not* be used for form elements that must not change the value attribute, e.g. (radio) buttons and checkboxes. * * @return mixed Value */ protected function getValueAttribute() { $value = null; if ($this->respectSubmittedDataValue) { $value = $this->getValueFromSubmittedFormData($value); } elseif ($this->hasArgument('value')) { $value = $this->arguments['value']; } elseif ($this->isObjectAccessorMode()) { $value = $this->getPropertyValue(); } $value = $this->convertToPlainValue($value); return $value; } /** * If property mapping errors occurred and the form is re-displayed, the *last submitted* value is returned by this * method. * * Note: * This method should *not* be used for form elements that must not change the value attribute, e.g. (radio) * buttons and checkboxes. The default behaviour is not to use this method. You need to set * respectSubmittedDataValue to TRUE to enable the form data handling for the viewhelper. * * @param mixed $value * @return mixed Value */ protected function getValueFromSubmittedFormData($value) { $submittedFormData = null; if ($this->hasMappingErrorOccurred()) { $submittedFormData = $this->getLastSubmittedFormData(); } if ($submittedFormData !== null) { $value = $submittedFormData; } elseif ($this->hasArgument('value')) { $value = $this->arguments['value']; } elseif ($this->isObjectAccessorMode()) { $value = $this->getPropertyValue(); } return $value; } /** * Converts an arbitrary value to a plain value * * @param mixed $value The value to convert * @return mixed */ protected function convertToPlainValue($value) { if (is_object($value)) { if ($value instanceof DomainObjectInterface && $value->getUid() !== null) { // We prefer to use the `getUid()` method because this returns the properly overlaid identifier (defaultLanguageRecordUid). // Otherwise, an identifier would contain '[defaultLanguageRecordUid]_[localizedRecordUid]'. This in turn // will not properly trigger the select option "is selected" comparison. // @see SelectViewHelper->getOptionValueScalar() return $value->getUid(); } $identifier = $this->persistenceManager->getIdentifierByObject($value); if ($identifier !== null) { return $identifier; } } return $value; } /** * Checks if a property mapping error has occurred in the last request. */ protected function hasMappingErrorOccurred(): bool { /** @var ExtbaseRequestParameters $extbaseRequestParameters */ $extbaseRequestParameters = $this->getRequest()->getAttribute('extbase'); return $extbaseRequestParameters->getOriginalRequest() !== null; } /** * Get the form data which has last been submitted; only returns valid data in case * a property mapping error has occurred. Check with hasMappingErrorOccurred() before! * * @return mixed */ protected function getLastSubmittedFormData() { $propertyPath = rtrim(preg_replace('/(\\]\\[|\\[|\\])/', '.', $this->getNameWithoutPrefix()) ?? '', '.'); /** @var ExtbaseRequestParameters $extbaseRequestParameters */ $extbaseRequestParameters = $this->getRequest()->getAttribute('extbase'); $value = ObjectAccess::getPropertyPath( $extbaseRequestParameters->getOriginalRequest()->getArguments(), $propertyPath ); return $value; } /** * Add additional identity properties in case the current property is hierarchical (of the form "bla.blubb"). * Then, [bla][__identity] has to be generated as well. */ protected function addAdditionalIdentityPropertiesIfNeeded(): void { if (!$this->isObjectAccessorMode()) { return; } $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); if (!$viewHelperVariableContainer->exists( FormViewHelper::class, 'formObject' ) ) { return; } $propertySegments = explode('.', (string)($this->arguments['property'] ?? '')); // hierarchical property. If there is no "." inside (thus $propertySegments == 1), we do not need to do anything if (count($propertySegments) < 2) { return; } $formObject = $viewHelperVariableContainer->get( FormViewHelper::class, 'formObject' ); $objectName = $viewHelperVariableContainer->get( FormViewHelper::class, 'formObjectName' ); // If count == 2 -> we need to go through the for-loop exactly once $propertySegmentsCount = count($propertySegments); for ($i = 1; $i < $propertySegmentsCount; $i++) { $object = ObjectAccess::getPropertyPath($formObject, implode('.', array_slice($propertySegments, 0, $i))); if (!is_object($object)) { $object = null; } $objectName .= '[' . $propertySegments[$i - 1] . ']'; $hiddenIdentityField = $this->renderHiddenIdentityField($object, $objectName); // Add the hidden identity field to the ViewHelperVariableContainer $additionalIdentityProperties = $viewHelperVariableContainer->get( FormViewHelper::class, 'additionalIdentityProperties' ); $additionalIdentityProperties[$objectName] = $hiddenIdentityField; $viewHelperVariableContainer->addOrUpdate( FormViewHelper::class, 'additionalIdentityProperties', $additionalIdentityProperties ); } } /** * Get the current property of the object bound to this form. * * @return mixed Value */ protected function getPropertyValue() { if (!isset($this->arguments['property'])) { return null; } $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); if (!$viewHelperVariableContainer->exists( FormViewHelper::class, 'formObject' ) ) { return null; } $formObject = $viewHelperVariableContainer->get( FormViewHelper::class, 'formObject' ); return ObjectAccess::getPropertyPath($formObject, (string)$this->arguments['property']); } /** * Internal method which checks if we should evaluate a domain object or just output arguments['name'] * and arguments['value']. Returns true if domain object should be evaluated. */ protected function isObjectAccessorMode(): bool { return $this->hasArgument('property') && $this->renderingContext->getViewHelperVariableContainer()->exists( FormViewHelper::class, 'formObjectName' ); } /** * Add a CSS class if this ViewHelper has errors */ protected function setErrorClassAttribute(): void { if (isset($this->additionalArguments['class'])) { $cssClass = $this->additionalArguments['class'] . ' '; } else { $cssClass = ''; } $mappingResultsForProperty = $this->getMappingResultsForProperty(); if ($mappingResultsForProperty->hasErrors()) { if ($this->hasArgument('errorClass')) { $cssClass .= $this->arguments['errorClass']; } else { $cssClass .= 'error'; } $this->tag->addAttribute('class', $cssClass); } } /** * Get errors for the property and form name of this ViewHelper */ protected function getMappingResultsForProperty(): Result { if (!$this->isObjectAccessorMode()) { return new Result(); } /** @var ExtbaseRequestParameters $extbaseRequestParameters */ $extbaseRequestParameters = $this->getRequest()->getAttribute('extbase'); $originalRequestMappingResults = $extbaseRequestParameters->getOriginalRequestMappingResults(); $formObjectName = $this->renderingContext->getViewHelperVariableContainer()->get( FormViewHelper::class, 'formObjectName' ); return $originalRequestMappingResults->forProperty($formObjectName)->forProperty((string)$this->arguments['property']); } /** * Renders a hidden field with the same name as the element, to make sure the empty value is submitted * in case nothing is selected. This is needed for checkbox and multiple select fields */ protected function renderHiddenFieldForEmptyValue(): string { $hiddenFieldNames = []; $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer(); if ($viewHelperVariableContainer->exists( FormViewHelper::class, 'renderedHiddenFields' ) ) { $hiddenFieldNames = $viewHelperVariableContainer->get( FormViewHelper::class, 'renderedHiddenFields' ); } $fieldName = $this->getName(); if (substr($fieldName, -2) === '[]') { $fieldName = substr($fieldName, 0, -2); } if (!in_array($fieldName, $hiddenFieldNames, true)) { $hiddenFieldNames[] = $fieldName; $viewHelperVariableContainer->addOrUpdate( FormViewHelper::class, 'renderedHiddenFields', $hiddenFieldNames ); return ''; } return ''; } }