TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
|
||||
use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
|
||||
use TYPO3\CMS\Extbase\Error\Result;
|
||||
use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters;
|
||||
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
|
||||
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper;
|
||||
|
||||
/**
|
||||
* Abstract Form ViewHelper. Bundles functionality related to direct property access of objects in other Form ViewHelpers.
|
||||
*
|
||||
* If you set the "property" attribute to the name of the property to resolve from the object, this class will
|
||||
* automatically set the name and value of a form element.
|
||||
*
|
||||
* Note this set of ViewHelpers is tailored to be used only in extbase context.
|
||||
*/
|
||||
abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper
|
||||
{
|
||||
protected ConfigurationManagerInterface $configurationManager;
|
||||
protected bool $respectSubmittedDataValue = false;
|
||||
|
||||
public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void
|
||||
{
|
||||
$this->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 <f:form object="...">, 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 '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="" />';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Type\DocType;
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject;
|
||||
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
|
||||
use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
|
||||
|
||||
/**
|
||||
* Abstract Form ViewHelper. Bundles functionality related to direct property access of objects in other Form ViewHelpers.
|
||||
*
|
||||
* If you set the "property" attribute to the name of the property to resolve from the object, this class will
|
||||
* automatically set the name and value of a form element.
|
||||
*
|
||||
* Note this set of ViewHelpers is tailored to be used only in extbase context.
|
||||
*/
|
||||
abstract class AbstractFormViewHelper extends AbstractTagBasedViewHelper
|
||||
{
|
||||
protected PersistenceManagerInterface $persistenceManager;
|
||||
|
||||
public function injectPersistenceManager(PersistenceManagerInterface $persistenceManager): void
|
||||
{
|
||||
$this->persistenceManager = $persistenceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefixes / namespaces the given name with the form field prefix
|
||||
*/
|
||||
protected function prefixFieldName(string $fieldName): string
|
||||
{
|
||||
if ($fieldName === '') {
|
||||
return '';
|
||||
}
|
||||
$viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
|
||||
if (!$viewHelperVariableContainer->exists(FormViewHelper::class, 'fieldNamePrefix')) {
|
||||
return $fieldName;
|
||||
}
|
||||
$fieldNamePrefix = (string)$viewHelperVariableContainer->get(FormViewHelper::class, 'fieldNamePrefix');
|
||||
if ($fieldNamePrefix === '') {
|
||||
return $fieldName;
|
||||
}
|
||||
$fieldNameSegments = explode('[', $fieldName, 2);
|
||||
$fieldName = $fieldNamePrefix . '[' . $fieldNameSegments[0] . ']';
|
||||
if (count($fieldNameSegments) > 1) {
|
||||
$fieldName .= '[' . $fieldNameSegments[1];
|
||||
}
|
||||
return $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a hidden form field containing the technical identity of the given object.
|
||||
*
|
||||
* @param mixed $object Object to create the identity field for. Non-objects are ignored.
|
||||
* @param string|null $name Name
|
||||
* @return string A hidden field containing the Identity (uid) of the given object
|
||||
* @see \TYPO3\CMS\Extbase\Mvc\Controller\Argument::setValue()
|
||||
*/
|
||||
protected function renderHiddenIdentityField(mixed $object, ?string $name): string
|
||||
{
|
||||
if ($object instanceof LazyLoadingProxy) {
|
||||
$object = $object->_loadRealInstance();
|
||||
}
|
||||
if (!is_object($object)
|
||||
|| !($object instanceof AbstractDomainObject)
|
||||
|| ($object->_isNew() && !$object->_isClone())) {
|
||||
return '';
|
||||
}
|
||||
// Intentionally NOT using PersistenceManager::getIdentifierByObject here.
|
||||
// Using that one breaks re-submission of data in forms in case of an error.
|
||||
$identifier = $object->getUid();
|
||||
if ($identifier === null) {
|
||||
return LF . '<!-- Object of type ' . get_class($object) . ' is without identity -->' . LF;
|
||||
}
|
||||
$name = $this->prefixFieldName($name ?? '') . '[__identity]';
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
|
||||
$endingSlash = ($this->shouldUseXHtmlSlash() ? '/' : '');
|
||||
return LF . '<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars((string)$identifier) . '" ' . $endingSlash . '>' . LF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a field name for inclusion in the HMAC / Form Token generation
|
||||
*/
|
||||
protected function registerFieldNameForFormTokenGeneration(string $fieldName): void
|
||||
{
|
||||
$viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
|
||||
if ($viewHelperVariableContainer->exists(FormViewHelper::class, 'formFieldNames')) {
|
||||
$formFieldNames = $viewHelperVariableContainer->get(FormViewHelper::class, 'formFieldNames');
|
||||
} else {
|
||||
$formFieldNames = [];
|
||||
}
|
||||
$formFieldNames[] = $fieldName;
|
||||
$viewHelperVariableContainer->addOrUpdate(FormViewHelper::class, 'formFieldNames', $formFieldNames);
|
||||
}
|
||||
|
||||
protected function shouldUseXHtmlSlash(): bool
|
||||
{
|
||||
return DocType::createFromRequest($this->renderingContext->getAttribute(ServerRequestInterface::class))->isXmlCompliant();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a form button.
|
||||
*
|
||||
* ```
|
||||
* <f:form.button type="reset" disabled="disabled"
|
||||
* name="buttonName" value="buttonValue"
|
||||
* formmethod="post" formnovalidate="formnovalidate"
|
||||
* >Cancel</f:form.button>
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-button
|
||||
*/
|
||||
final class ButtonViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'button';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('type', 'string', 'Specifies the type of button (e.g. "button", "reset" or "submit")', false, 'submit');
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$type = $this->arguments['type'];
|
||||
$name = $this->getName();
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
|
||||
$this->tag->addAttribute('type', $type);
|
||||
$this->tag->addAttribute('name', $name);
|
||||
$this->tag->addAttribute('value', (string)$this->getValueAttribute());
|
||||
$this->tag->setContent((string)$this->renderChildren());
|
||||
$this->tag->forceClosingTag(true);
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a simple checkbox `<input type="checkbox">`.
|
||||
*
|
||||
* ```
|
||||
* <f:form.checkbox property="interests" value="TYPO3" multiple="1" />
|
||||
* <f:form.checkbox name="interest" value="TYPO3" checked="{object.interest} == 'TYPO3'" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-checkbox
|
||||
*/
|
||||
final class CheckboxViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'input';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument(
|
||||
'errorClass',
|
||||
'string',
|
||||
'CSS class to set if there are errors for this ViewHelper',
|
||||
false,
|
||||
'f3-form-error'
|
||||
);
|
||||
$this->registerArgument('value', 'string', 'Value of input tag. Required for checkboxes', true);
|
||||
$this->registerArgument('checked', 'bool', 'Specifies that the input element should be preselected');
|
||||
$this->registerArgument('multiple', 'bool', 'Specifies whether this checkbox belongs to a multivalue (is part of a checkbox group)', false, false);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$checked = $this->arguments['checked'];
|
||||
$multiple = $this->arguments['multiple'];
|
||||
|
||||
$this->tag->addAttribute('type', 'checkbox');
|
||||
|
||||
$nameAttribute = $this->getName();
|
||||
$valueAttribute = $this->getValueAttribute();
|
||||
$propertyValue = null;
|
||||
if ($this->hasMappingErrorOccurred()) {
|
||||
$propertyValue = $this->getLastSubmittedFormData();
|
||||
}
|
||||
if ($checked === null && $propertyValue === null) {
|
||||
$propertyValue = $this->getPropertyValue();
|
||||
}
|
||||
|
||||
if ($propertyValue instanceof \Traversable) {
|
||||
$propertyValue = iterator_to_array($propertyValue);
|
||||
}
|
||||
if (is_array($propertyValue)) {
|
||||
$propertyValue = array_map($this->convertToPlainValue(...), $propertyValue);
|
||||
if ($checked === null) {
|
||||
$checked = in_array($valueAttribute, $propertyValue);
|
||||
}
|
||||
$nameAttribute .= '[]';
|
||||
} elseif ($multiple === true) {
|
||||
$nameAttribute .= '[]';
|
||||
} elseif ($propertyValue !== null) {
|
||||
$checked = (bool)$propertyValue === (bool)$valueAttribute;
|
||||
}
|
||||
|
||||
$this->registerFieldNameForFormTokenGeneration($nameAttribute);
|
||||
$this->tag->addAttribute('name', $nameAttribute);
|
||||
$this->tag->addAttribute('value', (string)$valueAttribute);
|
||||
if ($checked === true) {
|
||||
$this->tag->addAttribute('checked', 'checked');
|
||||
}
|
||||
|
||||
$this->setErrorClassAttribute();
|
||||
$hiddenField = $this->renderHiddenFieldForEmptyValue();
|
||||
return $hiddenField . $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
use TYPO3\CMS\Core\Country\Country;
|
||||
use TYPO3\CMS\Core\Country\CountryFilter;
|
||||
use TYPO3\CMS\Core\Country\CountryProvider;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\InvalidArgumentValueException;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a `<select>` tag with all or specific countries as options.
|
||||
*
|
||||
* ```
|
||||
* <f:form.countrySelect name="country" value="AT" />
|
||||
* <f:form.countrySelect name="country" value="DE"
|
||||
* optionLabelField="localizedOfficialName"
|
||||
* prioritizedCountries="{0: 'DE', 1: 'AT', 2: 'CH'}"
|
||||
* alternativeLanguage="fr"
|
||||
* sortByOptionLabel="true"
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-countryselect
|
||||
*/
|
||||
final class CountrySelectViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'select';
|
||||
|
||||
public function __construct(
|
||||
private readonly CountryProvider $countryProvider
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('excludeCountries', 'array', 'Array with country codes that should not be shown.', false, []);
|
||||
$this->registerArgument('onlyCountries', 'array', 'If set, only the country codes in the list are rendered.', false, []);
|
||||
$this->registerArgument('optionLabelField', 'string', 'If specified, will call the appropriate getter on each object to determine the label. Use "name", "localizedName", "officialName" or "localizedOfficialName"', false, 'localizedName');
|
||||
$this->registerArgument('sortByOptionLabel', 'boolean', 'If true, List will be sorted by label.', false, false);
|
||||
$this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error');
|
||||
$this->registerArgument('prependOptionLabel', 'string', 'If specified, will provide an option at first position with the specified label.');
|
||||
$this->registerArgument('prependOptionValue', 'string', 'If specified, will provide an option at first position with the specified value.');
|
||||
$this->registerArgument('multiple', 'boolean', 'If set multiple options may be selected.', false, false);
|
||||
$this->registerArgument('required', 'boolean', 'If set no empty value is allowed.', false, false);
|
||||
$this->registerArgument('prioritizedCountries', 'array', 'A list of country codes which should be listed on top of the list.', false, []);
|
||||
$this->registerArgument('alternativeLanguage', 'string', 'If specified, the country list will be shown in the given language.');
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
if ($this->arguments['required']) {
|
||||
$this->tag->addAttribute('required', 'required');
|
||||
}
|
||||
$name = $this->getName();
|
||||
if ($this->arguments['multiple']) {
|
||||
$this->tag->addAttribute('multiple', 'multiple');
|
||||
$name .= '[]';
|
||||
}
|
||||
$this->addAdditionalIdentityPropertiesIfNeeded();
|
||||
$this->setErrorClassAttribute();
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
$this->setRespectSubmittedDataValue(true);
|
||||
|
||||
$this->tag->addAttribute('name', $name);
|
||||
|
||||
$validCountries = $this->getCountryList();
|
||||
$options = $this->createOptions($validCountries);
|
||||
$selectedValue = $this->getValueAttribute();
|
||||
|
||||
$tagContent = $this->renderPrependOptionTag();
|
||||
foreach ($options as $value => $label) {
|
||||
$tagContent .= $this->renderOptionTag($value, $label, $value === $selectedValue);
|
||||
}
|
||||
|
||||
$this->tag->forceClosingTag(true);
|
||||
$this->tag->setContent($tagContent);
|
||||
return $this->tag->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Country[] $countries
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function createOptions(array $countries): array
|
||||
{
|
||||
$options = [];
|
||||
foreach ($countries as $code => $country) {
|
||||
switch ($this->arguments['optionLabelField']) {
|
||||
case 'localizedName':
|
||||
$options[$code] = $this->translate($country->getLocalizedNameLabel());
|
||||
break;
|
||||
case 'name':
|
||||
$options[$code] = $country->getName();
|
||||
break;
|
||||
case 'officialName':
|
||||
$options[$code] = $country->getOfficialName();
|
||||
break;
|
||||
case 'localizedOfficialName':
|
||||
$name = $this->translate($country->getLocalizedOfficialNameLabel());
|
||||
if (!$name) {
|
||||
$name = $this->translate($country->getLocalizedNameLabel());
|
||||
}
|
||||
$options[$code] = $name;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentValueException('Argument "optionLabelField" of <f:form.countrySelect> must either be set to "localizedName", "name", "officialName", or "localizedOfficialName".', 1674076708);
|
||||
}
|
||||
}
|
||||
if ($this->arguments['sortByOptionLabel']) {
|
||||
asort($options, SORT_LOCALE_STRING);
|
||||
} else {
|
||||
ksort($options, SORT_NATURAL);
|
||||
}
|
||||
if (($this->arguments['prioritizedCountries'] ?? []) !== []) {
|
||||
$finalOptions = [];
|
||||
foreach ($this->arguments['prioritizedCountries'] as $countryCode) {
|
||||
if (isset($options[$countryCode])) {
|
||||
$label = $options[$countryCode];
|
||||
$finalOptions[$countryCode] = $label;
|
||||
unset($options[$countryCode]);
|
||||
}
|
||||
}
|
||||
foreach ($options as $countryCode => $label) {
|
||||
$finalOptions[$countryCode] = $label;
|
||||
}
|
||||
$options = $finalOptions;
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function translate(string $label): string
|
||||
{
|
||||
if ($this->arguments['alternativeLanguage']) {
|
||||
return (string)LocalizationUtility::translate($label, null, null, $this->arguments['alternativeLanguage']);
|
||||
}
|
||||
return (string)LocalizationUtility::translate($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render prepended option tag
|
||||
*/
|
||||
private function renderPrependOptionTag(): string
|
||||
{
|
||||
if ($this->hasArgument('prependOptionLabel')) {
|
||||
$value = $this->hasArgument('prependOptionValue') ? $this->arguments['prependOptionValue'] : '';
|
||||
$label = $this->arguments['prependOptionLabel'];
|
||||
return $this->renderOptionTag((string)$value, (string)$label, false) . LF;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render one option tag
|
||||
*
|
||||
* @param string $value value attribute of the option tag (will be escaped)
|
||||
* @param string $label content of the option tag (will be escaped)
|
||||
* @param bool $isSelected specifies whether to add selected attribute
|
||||
* @return string the rendered option tag
|
||||
*/
|
||||
private function renderOptionTag(string $value, string $label, bool $isSelected): string
|
||||
{
|
||||
$output = '<option value="' . htmlspecialchars($value) . '"';
|
||||
if ($isSelected) {
|
||||
$output .= ' selected="selected"';
|
||||
}
|
||||
if (($this->arguments['prioritizedCountries'] ?? []) !== []
|
||||
&& in_array($value, $this->arguments['prioritizedCountries'], true)
|
||||
) {
|
||||
$output .= ' data-prioritized="1"';
|
||||
}
|
||||
$output .= '>' . htmlspecialchars($label) . '</option>';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Country[]
|
||||
*/
|
||||
private function getCountryList(): array
|
||||
{
|
||||
$filter = new CountryFilter();
|
||||
$filter->setOnlyCountries($this->arguments['onlyCountries'] ?? [])
|
||||
->setExcludeCountries($this->arguments['excludeCountries'] ?? []);
|
||||
return $this->countryProvider->getFiltered($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an arbitrary value to a plain value.
|
||||
* Evaluates possible direct "Country" type properties.
|
||||
*
|
||||
* @param mixed $value The value to convert
|
||||
* @return mixed
|
||||
*/
|
||||
protected function convertToPlainValue($value)
|
||||
{
|
||||
if ($value instanceof Country) {
|
||||
return $value->getAlpha2IsoCode();
|
||||
}
|
||||
return parent::convertToPlainValue($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders an `<input type="hidden" ...>` tag.
|
||||
*
|
||||
* ```
|
||||
* <f:form.hidden name="myHiddenValue" value="42" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-hidden
|
||||
*/
|
||||
final class HiddenViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'input';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument(
|
||||
'respectSubmittedDataValue',
|
||||
'bool',
|
||||
'enable or disable the usage of the submitted values',
|
||||
false,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$name = $this->getName();
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
$this->setRespectSubmittedDataValue($this->arguments['respectSubmittedDataValue']);
|
||||
|
||||
$this->tag->addAttribute('type', 'hidden');
|
||||
$this->tag->addAttribute('name', $name);
|
||||
$this->tag->addAttribute('value', (string)$this->getValueAttribute());
|
||||
|
||||
$this->addAdditionalIdentityPropertiesIfNeeded();
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a simple password text box `<input type="password">`.
|
||||
*
|
||||
* ```
|
||||
* <f:form.password name="myPassword" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-password
|
||||
*/
|
||||
final class PasswordViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'input';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error');
|
||||
$this->registerArgument(
|
||||
'respectSubmittedDataValue',
|
||||
'bool',
|
||||
'If set to false (default), any user-submitted data is not displayed in the output. If set to true, the password is emitted as clear text in the response. This is not recommended from a security point of view.',
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$name = $this->getName();
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
$this->setRespectSubmittedDataValue($this->arguments['respectSubmittedDataValue']);
|
||||
|
||||
$this->tag->addAttribute('type', 'password');
|
||||
$this->tag->addAttribute('name', $name);
|
||||
$this->tag->addAttribute('value', (string)$this->getValueAttribute());
|
||||
|
||||
$this->addAdditionalIdentityPropertiesIfNeeded();
|
||||
$this->setErrorClassAttribute();
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a simple radio button `<input type="radio">`.
|
||||
*
|
||||
* ```
|
||||
* <f:form.radio name="myRadioButton" value="someValue" />
|
||||
* <f:form.radio property="newsletter" value="1" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-radio
|
||||
*/
|
||||
final class RadioViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'input';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error');
|
||||
$this->registerArgument('checked', 'bool', 'Specifies that the input element should be preselected');
|
||||
$this->registerArgument('value', 'string', 'Value of input tag. Required for radio buttons', true);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$checked = $this->arguments['checked'];
|
||||
|
||||
$this->tag->addAttribute('type', 'radio');
|
||||
|
||||
$nameAttribute = $this->getName();
|
||||
$valueAttribute = $this->getValueAttribute();
|
||||
|
||||
$propertyValue = null;
|
||||
if ($this->hasMappingErrorOccurred()) {
|
||||
$propertyValue = $this->getLastSubmittedFormData();
|
||||
}
|
||||
if ($checked === null && $propertyValue === null) {
|
||||
$propertyValue = $this->getPropertyValue();
|
||||
$propertyValue = $this->convertToPlainValue($propertyValue);
|
||||
}
|
||||
|
||||
if ($propertyValue !== null) {
|
||||
// no type-safe comparison by intention
|
||||
$checked = $propertyValue == $valueAttribute;
|
||||
}
|
||||
|
||||
$this->registerFieldNameForFormTokenGeneration($nameAttribute);
|
||||
$this->tag->addAttribute('name', $nameAttribute);
|
||||
$this->tag->addAttribute('value', (string)$valueAttribute);
|
||||
if ($checked === true) {
|
||||
$this->tag->addAttribute('checked', 'checked');
|
||||
}
|
||||
|
||||
$this->setErrorClassAttribute();
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form\Select;
|
||||
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper;
|
||||
|
||||
/**
|
||||
* ViewHelper for adding custom `<optgroup>` tags inside a `<f:form.select>`,
|
||||
* supports further child `<f:form.select.option>` tags.
|
||||
*
|
||||
* ```
|
||||
* <f:form.select name="mySelect">
|
||||
* <f:form.select.option value="1">Option one</f:form.select.option>
|
||||
* <f:form.select.optgroup>
|
||||
* <f:form.select.option value="3">Grouped option one</f:form.select.option>
|
||||
* <f:form.select.option value="4">Grouped option two</f:form.select.option>
|
||||
* </f:form.select.optgroup>
|
||||
* </f:form.select>>
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-select-optgroup
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-select-option
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-select
|
||||
*/
|
||||
final class OptgroupViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'optgroup';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
$this->registerArgument('additionalAttributes', 'array', 'Additional tag attributes. They will be added directly to the resulting HTML tag.');
|
||||
$this->registerArgument('data', 'array', 'Additional data-* attributes. They will each be added with a "data-" prefix.');
|
||||
$this->registerArgument('disabled', 'boolean', 'If true, option group is rendered as disabled', false, false);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
if ($this->arguments['disabled']) {
|
||||
$this->tag->addAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
$this->tag->setContent($this->renderChildren());
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form\Select;
|
||||
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper;
|
||||
|
||||
/**
|
||||
* ViewHelper for adding custom `<option>` tags inside a `<f:form.select>`.
|
||||
*
|
||||
* ```
|
||||
* <f:form.select name="mySelect">
|
||||
* <f:form.select.option value="1">Option one</f:form.select.option>
|
||||
* <f:form.select.optgroup>
|
||||
* <f:form.select.option value="3">Grouped option one</f:form.select.option>
|
||||
* <f:form.select.option value="4">Grouped option two</f:form.select.option>
|
||||
* </f:form.select.optgroup>
|
||||
* </f:form.select>>
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-select-option
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-select
|
||||
*/
|
||||
final class OptionViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'option';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
$this->registerArgument('selected', 'boolean', 'If set, overrides automatic detection of selected state for this option.');
|
||||
$this->registerArgument('additionalAttributes', 'array', 'Additional tag attributes. They will be added directly to the resulting HTML tag.');
|
||||
$this->registerArgument('data', 'array', 'Additional data-* attributes. They will each be added with a "data-" prefix.');
|
||||
$this->registerArgument('value', 'mixed', 'Value to be inserted in HTML tag - must be convertible to string!');
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$childContent = $this->renderChildren();
|
||||
$this->tag->setContent((string)$childContent);
|
||||
$value = $this->arguments['value'] ?? $childContent;
|
||||
if ($this->arguments['selected'] ?? $this->isValueSelected((string)$value)) {
|
||||
$this->tag->addAttribute('selected', 'selected');
|
||||
}
|
||||
$this->tag->addAttribute('value', (string)$value);
|
||||
$parentRequestedFormTokenFieldName = $this->renderingContext->getViewHelperVariableContainer()->get(
|
||||
SelectViewHelper::class,
|
||||
'registerFieldNameForFormTokenGeneration'
|
||||
);
|
||||
if ($parentRequestedFormTokenFieldName) {
|
||||
// parent (select field) has requested this option must add one more
|
||||
// entry in the token generation registry for one additional potential
|
||||
// value of the field. Happens when "multiple" is true on parent.
|
||||
$this->registerFieldNameForFormTokenGeneration($parentRequestedFormTokenFieldName);
|
||||
}
|
||||
return $this->tag->render();
|
||||
}
|
||||
|
||||
private function isValueSelected(string $value): bool
|
||||
{
|
||||
$selectedValue = $this->renderingContext->getViewHelperVariableContainer()->get(SelectViewHelper::class, 'selectedValue');
|
||||
if (is_array($selectedValue)) {
|
||||
return in_array($value, array_map(strval(...), $selectedValue), true);
|
||||
}
|
||||
if ($selectedValue instanceof \Iterator) {
|
||||
return in_array($value, array_map(strval(...), iterator_to_array($selectedValue)), true);
|
||||
}
|
||||
return $value === (string)$selectedValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
|
||||
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\InvalidArgumentValueException;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\MissingArgumentException;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a `<select>` dropdown list for use within a form.
|
||||
*
|
||||
* ```
|
||||
* <f:form.select name="paymentOptions" options="{payPal: 'PayPal International Services', visa: 'VISA Card'}" value="visa" />
|
||||
* <f:form.select property="users" options="{userList}" optionValueField="id" optionLabelField="firstName" multiple="true" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-select
|
||||
*/
|
||||
final class SelectViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'select';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('options', 'array', 'Associative array with internal IDs as key, and the values are displayed in the select box. Can be combined with or replaced by child f:form.select.* nodes.');
|
||||
$this->registerArgument('optionsAfterContent', 'boolean', 'If true, places auto-generated option tags after those rendered in the tag content. If false, automatic options come first.', false, false);
|
||||
$this->registerArgument('optionValueField', 'string', 'If specified, will call the appropriate getter on each object to determine the value.');
|
||||
$this->registerArgument('optionLabelField', 'string', 'If specified, will call the appropriate getter on each object to determine the label.');
|
||||
$this->registerArgument('sortByOptionLabel', 'boolean', 'If true, List will be sorted by label.', false, false);
|
||||
$this->registerArgument('selectAllByDefault', 'boolean', 'If specified options are selected if none was set before.', false, false);
|
||||
$this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error');
|
||||
$this->registerArgument('prependOptionLabel', 'string', 'If specified, will provide an option at first position with the specified label.');
|
||||
$this->registerArgument('prependOptionValue', 'string', 'If specified, will provide an option at first position with the specified value.');
|
||||
$this->registerArgument('multiple', 'boolean', 'If set multiple options may be selected.', false, false);
|
||||
$this->registerArgument('required', 'boolean', 'If set no empty value is allowed.', false, false);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
if ($this->arguments['required']) {
|
||||
$this->tag->addAttribute('required', 'required');
|
||||
}
|
||||
$name = $this->getName();
|
||||
if ($this->arguments['multiple']) {
|
||||
$this->tag->addAttribute('multiple', 'multiple');
|
||||
$name .= '[]';
|
||||
}
|
||||
$this->tag->addAttribute('name', $name);
|
||||
$options = $this->getOptions();
|
||||
|
||||
$viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
|
||||
|
||||
$this->addAdditionalIdentityPropertiesIfNeeded();
|
||||
$this->setErrorClassAttribute();
|
||||
$content = '';
|
||||
|
||||
// register field name for token generation.
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
// in case it is a multi-select, we need to register the field name
|
||||
// as often as there are elements in the box
|
||||
if ($this->arguments['multiple']) {
|
||||
$content .= $this->renderHiddenFieldForEmptyValue();
|
||||
// Register the field name additional times as required by the total number of
|
||||
// options. Since we already registered it once above, we start the counter at 1
|
||||
// instead of 0.
|
||||
$optionsCount = count($options);
|
||||
for ($i = 1; $i < $optionsCount; $i++) {
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
}
|
||||
// save the parent field name so that any child f:form.select.option
|
||||
// tag will know to call registerFieldNameForFormTokenGeneration
|
||||
// this is the reason why "self::class" is used instead of static::class (no LSB)
|
||||
$viewHelperVariableContainer->addOrUpdate(
|
||||
self::class,
|
||||
'registerFieldNameForFormTokenGeneration',
|
||||
$name
|
||||
);
|
||||
}
|
||||
|
||||
$viewHelperVariableContainer->addOrUpdate(self::class, 'selectedValue', $this->getSelectedValue());
|
||||
$prependContent = $this->renderPrependOptionTag();
|
||||
$tagContent = $this->renderOptionTags($options);
|
||||
$childContent = $this->renderChildren();
|
||||
$viewHelperVariableContainer->remove(self::class, 'selectedValue');
|
||||
$viewHelperVariableContainer->remove(self::class, 'registerFieldNameForFormTokenGeneration');
|
||||
if (isset($this->arguments['optionsAfterContent']) && $this->arguments['optionsAfterContent']) {
|
||||
$tagContent = $childContent . $tagContent;
|
||||
} else {
|
||||
$tagContent .= $childContent;
|
||||
}
|
||||
$tagContent = $prependContent . $tagContent;
|
||||
|
||||
$this->tag->forceClosingTag(true);
|
||||
$this->tag->setContent($tagContent);
|
||||
$content .= $this->tag->render();
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render prepended option tag
|
||||
*/
|
||||
private function renderPrependOptionTag(): string
|
||||
{
|
||||
$output = '';
|
||||
if ($this->hasArgument('prependOptionLabel')) {
|
||||
$value = $this->hasArgument('prependOptionValue') ? $this->arguments['prependOptionValue'] : '';
|
||||
$label = $this->arguments['prependOptionLabel'];
|
||||
$output .= $this->renderOptionTag((string)$value, (string)$label, false) . LF;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the option tags.
|
||||
*/
|
||||
private function renderOptionTags(array $options): string
|
||||
{
|
||||
$output = '';
|
||||
foreach ($options as $value => $label) {
|
||||
$isSelected = $this->isSelected($value);
|
||||
$output .= $this->renderOptionTag((string)$value, (string)$label, $isSelected) . LF;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the option tags.
|
||||
*
|
||||
* @return array An associative array of options, key will be the value of the option tag
|
||||
*/
|
||||
private function getOptions(): array
|
||||
{
|
||||
if (!is_array($this->arguments['options']) && !$this->arguments['options'] instanceof \Traversable) {
|
||||
return [];
|
||||
}
|
||||
$options = [];
|
||||
$optionsArgument = $this->arguments['options'];
|
||||
foreach ($optionsArgument as $key => $value) {
|
||||
if (!is_object($value) && !is_array($value)) {
|
||||
$options[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
if (!$this->hasArgument('optionValueField')) {
|
||||
throw new MissingArgumentException('Missing parameter "optionValueField" in SelectViewHelper for array value options.', 1682693720);
|
||||
}
|
||||
if (!$this->hasArgument('optionLabelField')) {
|
||||
throw new MissingArgumentException('Missing parameter "optionLabelField" in SelectViewHelper for array value options.', 1682693721);
|
||||
}
|
||||
$key = ObjectAccess::getPropertyPath($value, (string)$this->arguments['optionValueField']);
|
||||
$value = ObjectAccess::getPropertyPath($value, (string)$this->arguments['optionLabelField']);
|
||||
$options[$key ?? ''] = $value;
|
||||
continue;
|
||||
}
|
||||
if ($this->hasArgument('optionValueField')) {
|
||||
$key = ObjectAccess::getPropertyPath($value, $this->arguments['optionValueField']);
|
||||
if (is_object($key)) {
|
||||
if (method_exists($key, '__toString')) {
|
||||
$key = (string)$key;
|
||||
} else {
|
||||
throw new InvalidArgumentValueException('Identifying value for object of class "' . get_debug_type($value) . '" was an object.', 1247827428);
|
||||
}
|
||||
}
|
||||
} elseif (!$this->persistenceManager->isNewObject($value)) {
|
||||
$key = $this->persistenceManager->getIdentifierByObject($value);
|
||||
} elseif (is_object($value) && method_exists($value, '__toString')) {
|
||||
$key = (string)$value;
|
||||
} elseif (is_object($value)) {
|
||||
throw new InvalidArgumentValueException('No identifying value for object of class "' . get_class($value) . '" found.', 1247826696);
|
||||
}
|
||||
if ($this->hasArgument('optionLabelField')) {
|
||||
$value = ObjectAccess::getPropertyPath($value, $this->arguments['optionLabelField']);
|
||||
if (is_object($value)) {
|
||||
if (method_exists($value, '__toString')) {
|
||||
$value = (string)$value;
|
||||
} else {
|
||||
throw new InvalidArgumentValueException('Label value for object of class "' . get_class($value) . '" was an object without a __toString() method.', 1247827553);
|
||||
}
|
||||
}
|
||||
} elseif (is_object($value) && method_exists($value, '__toString')) {
|
||||
$value = (string)$value;
|
||||
} elseif (!$this->persistenceManager->isNewObject($value)) {
|
||||
$value = $this->persistenceManager->getIdentifierByObject($value);
|
||||
}
|
||||
$options[$key ?? ''] = $value;
|
||||
}
|
||||
if ($this->arguments['sortByOptionLabel']) {
|
||||
asort($options, SORT_LOCALE_STRING);
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the option tags.
|
||||
*
|
||||
* @param mixed $value Value to check for
|
||||
* @return bool True if the value should be marked as selected.
|
||||
*/
|
||||
private function isSelected($value): bool
|
||||
{
|
||||
$selectedValue = $this->getSelectedValue();
|
||||
if ($value === $selectedValue || (string)$value === $selectedValue) {
|
||||
return true;
|
||||
}
|
||||
if ($this->hasArgument('multiple')) {
|
||||
if ($selectedValue === null && $this->arguments['selectAllByDefault'] === true) {
|
||||
return true;
|
||||
}
|
||||
if (is_array($selectedValue) && in_array($value, $selectedValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the selected value(s)
|
||||
*
|
||||
* @return mixed value string or an array of strings
|
||||
*/
|
||||
private function getSelectedValue()
|
||||
{
|
||||
$this->setRespectSubmittedDataValue(true);
|
||||
$value = $this->getValueAttribute();
|
||||
if (!is_array($value) && !$value instanceof \Traversable) {
|
||||
return $this->getOptionValueScalar($value);
|
||||
}
|
||||
$selectedValues = [];
|
||||
foreach ($value as $selectedValueElement) {
|
||||
$selectedValues[] = $this->getOptionValueScalar($selectedValueElement);
|
||||
}
|
||||
return $selectedValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the option value for an object
|
||||
*
|
||||
* @param mixed $valueElement
|
||||
* @return string @todo: Does not always return string ...
|
||||
*/
|
||||
private function getOptionValueScalar($valueElement)
|
||||
{
|
||||
if (is_object($valueElement)) {
|
||||
if ($this->hasArgument('optionValueField')) {
|
||||
return ObjectAccess::getPropertyPath($valueElement, $this->arguments['optionValueField']);
|
||||
}
|
||||
if (!$this->persistenceManager->isNewObject($valueElement)) {
|
||||
if ($valueElement instanceof DomainObjectInterface) {
|
||||
// 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 AbstractFormFieldViewHelper->convertToPlainValue()
|
||||
return $valueElement->getUid() ?? $this->persistenceManager->getIdentifierByObject($valueElement);
|
||||
}
|
||||
return $this->persistenceManager->getIdentifierByObject($valueElement);
|
||||
}
|
||||
if ($valueElement instanceof \BackedEnum) {
|
||||
return $valueElement->value;
|
||||
}
|
||||
if ($valueElement instanceof \UnitEnum) {
|
||||
return $valueElement->name;
|
||||
}
|
||||
return (string)$valueElement;
|
||||
}
|
||||
return $valueElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render one option tag
|
||||
*
|
||||
* @param string $value value attribute of the option tag (will be escaped)
|
||||
* @param string $label content of the option tag (will be escaped)
|
||||
* @param bool $isSelected specifies whether to add selected attribute
|
||||
* @return string the rendered option tag
|
||||
*/
|
||||
private function renderOptionTag(string $value, string $label, bool $isSelected): string
|
||||
{
|
||||
$output = '<option value="' . htmlspecialchars($value) . '"';
|
||||
if ($isSelected) {
|
||||
$output .= ' selected="selected"';
|
||||
}
|
||||
$output .= '>' . htmlspecialchars($label) . '</option>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a form submit button.
|
||||
*
|
||||
* ```
|
||||
* <f:form.submit value="Send Mail" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-submit
|
||||
*/
|
||||
final class SubmitViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'input';
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$name = $this->getName();
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
|
||||
$this->tag->addAttribute('type', 'submit');
|
||||
$this->tag->addAttribute('value', (string)$this->getValueAttribute());
|
||||
if (!empty($name)) {
|
||||
$this->tag->addAttribute('name', $name);
|
||||
}
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a `<textarea>` large text input area inside a form.
|
||||
*
|
||||
* The value of the text area needs to be set via the `value` attribute, as with all other f:form ViewHelpers.
|
||||
*
|
||||
* ```
|
||||
* <f:form.textarea name="myTextArea" value="This is shown inside the textarea" />
|
||||
* <f:form.textarea property="myProperty" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-textarea
|
||||
*/
|
||||
final class TextareaViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'textarea';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error');
|
||||
$this->registerArgument('required', 'bool', 'Specifies whether the textarea is required', false, false);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$required = $this->arguments['required'];
|
||||
$name = $this->getName();
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
$this->setRespectSubmittedDataValue(true);
|
||||
|
||||
$this->tag->forceClosingTag(true);
|
||||
$this->tag->addAttribute('name', $name);
|
||||
if ($required === true) {
|
||||
$this->tag->addAttribute('required', 'required');
|
||||
}
|
||||
$this->tag->setContent(htmlspecialchars((string)$this->getValueAttribute()));
|
||||
$this->addAdditionalIdentityPropertiesIfNeeded();
|
||||
$this->setErrorClassAttribute();
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a text field `<input type="text">`.
|
||||
*
|
||||
* ```
|
||||
* <f:form.textfield name="myTextBox" value="default value" />
|
||||
* <f:form.textfield property="ownerMail" required="true" type="email" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-textfield
|
||||
*/
|
||||
final class TextfieldViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'input';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error');
|
||||
$this->registerArgument('required', 'bool', 'If the field is required or not', false, false);
|
||||
$this->registerArgument('type', 'string', 'The field type, e.g. "text", "email", "url" etc.', false, 'text');
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$required = $this->arguments['required'];
|
||||
$type = $this->arguments['type'];
|
||||
|
||||
$name = $this->getName();
|
||||
$this->registerFieldNameForFormTokenGeneration($name);
|
||||
$this->setRespectSubmittedDataValue(true);
|
||||
|
||||
$this->tag->addAttribute('type', $type);
|
||||
$this->tag->addAttribute('name', $name);
|
||||
|
||||
$value = $this->getValueAttribute();
|
||||
|
||||
if ($value !== null) {
|
||||
$this->tag->addAttribute('value', $value);
|
||||
}
|
||||
|
||||
if ($required !== false) {
|
||||
$this->tag->addAttribute('required', 'required');
|
||||
}
|
||||
|
||||
$this->addAdditionalIdentityPropertiesIfNeeded();
|
||||
$this->setErrorClassAttribute();
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
|
||||
use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters;
|
||||
use TYPO3\CMS\Extbase\Service\ExtensionService;
|
||||
use TYPO3\CMS\Extbase\Service\FileHandlingService;
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a checkbox field used for file upload deletion in Extbase forms.
|
||||
*
|
||||
* ```
|
||||
* <f:form.uploadDeleteCheckbox id="file" property="file" fileReference="{myModel.file}" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-uploaddeletecheckbox
|
||||
*/
|
||||
final class UploadDeleteCheckboxViewHelper extends AbstractTagBasedViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'input';
|
||||
|
||||
public function __construct(
|
||||
private readonly HashService $hashService,
|
||||
private readonly ExtensionService $extensionService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('id', 'string', 'ID of the generated checkbox element');
|
||||
$this->registerArgument('property', 'string', 'Name of object property', true);
|
||||
$this->registerArgument('fileReference', FileReference::class, 'The file reference object', true);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
/** @var ?FileReference $fileReference */
|
||||
$fileReference = $this->arguments['fileReference'];
|
||||
$property = $this->arguments['property'];
|
||||
$idAttribute = $this->arguments['id'] ?? '';
|
||||
|
||||
// Early return, if no file reference given
|
||||
if (!$fileReference instanceof FileReference) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->tag->addAttribute('type', 'checkbox');
|
||||
$request = ($this->renderingContext->getAttribute(ServerRequestInterface::class));
|
||||
$extbaseRequestParams = $request->getAttribute('extbase');
|
||||
|
||||
$extensionName = $extbaseRequestParams->getControllerExtensionName();
|
||||
$pluginName = $extbaseRequestParams->getPluginName();
|
||||
if ($extensionName === '' || $pluginName === '') {
|
||||
throw new \RuntimeException('ExtensionName or PluginName not set in Extbase request', 1719660837);
|
||||
}
|
||||
|
||||
$deleteData = [
|
||||
'property' => $property,
|
||||
'fileReference' => $fileReference->getUid(),
|
||||
];
|
||||
|
||||
$pluginNamespace = $this->extensionService->getPluginNamespace($extensionName, $pluginName);
|
||||
$formObjectName = $this->getFormObjectName();
|
||||
$fileReferenceIdentifier = $this->hashService->hmac($property . $fileReference->getUid(), self::class);
|
||||
$nameAttribute = $pluginNamespace . '[' . FileHandlingService::DELETE_IDENTIFIER . ']'
|
||||
. '[' . $formObjectName . ']' . '[' . $fileReferenceIdentifier . ']';
|
||||
$valueAttribute = $this->hashService->appendHmac(
|
||||
json_encode($deleteData, JSON_THROW_ON_ERROR),
|
||||
FileHandlingService::DELETE_IDENTIFIER
|
||||
);
|
||||
|
||||
$checked = false;
|
||||
if ($this->hasMappingErrorOccurred($extbaseRequestParams)) {
|
||||
$checked = $this->getCheckedState($request, $pluginNamespace, $formObjectName, $fileReferenceIdentifier);
|
||||
}
|
||||
|
||||
$this->tag->addAttribute('id', $idAttribute);
|
||||
$this->tag->addAttribute('name', $nameAttribute);
|
||||
$this->tag->addAttribute('value', (string)$valueAttribute);
|
||||
if ($checked === true) {
|
||||
$this->tag->addAttribute('checked', 'checked');
|
||||
}
|
||||
|
||||
return $this->tag->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the boolean checked state for the given identifier evaluated from POST data.
|
||||
*/
|
||||
private function getCheckedState(
|
||||
ServerRequestInterface $request,
|
||||
string $fieldNamePrefix,
|
||||
string $formObjectName,
|
||||
mixed $identifier
|
||||
): bool {
|
||||
return (bool)($request->getParsedBody()[$fieldNamePrefix][FileHandlingService::DELETE_IDENTIFIER][$formObjectName][$identifier] ?? false);
|
||||
}
|
||||
|
||||
private function getFormObjectName(): string
|
||||
{
|
||||
$formObjectName = $this->renderingContext->getViewHelperVariableContainer()->get(
|
||||
FormViewHelper::class,
|
||||
'formObjectName'
|
||||
);
|
||||
|
||||
if (empty($formObjectName)) {
|
||||
throw new \RuntimeException('UploadDeleteCheckboxViewHelper can only be used on Fluid form context', 1719655880);
|
||||
}
|
||||
|
||||
return $formObjectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a property mapping error has occurred in the last request.
|
||||
*/
|
||||
private function hasMappingErrorOccurred(ExtbaseRequestParameters $extbaseRequest): bool
|
||||
{
|
||||
return $extbaseRequest->getOriginalRequest() !== null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders an `<input type="file">` file upload HTML form element.
|
||||
* Make sure to set the `enctype="multipart/form-data"` attribute on the surrounding form!
|
||||
*
|
||||
* ```
|
||||
* <f:form.upload name="file" />
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-upload
|
||||
*/
|
||||
final class UploadViewHelper extends AbstractFormFieldViewHelper
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tagName = 'input';
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
parent::initializeArguments();
|
||||
$this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error');
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$multiple = isset($this->additionalArguments['multiple']);
|
||||
$name = $this->getName();
|
||||
$allowedFields = ['name', 'type', 'tmp_name', 'error', 'size'];
|
||||
foreach ($allowedFields as $fieldName) {
|
||||
if ($multiple) {
|
||||
$formTokenFieldName = sprintf('%s[*][%s]', $name, $fieldName);
|
||||
} else {
|
||||
$formTokenFieldName = $name . '[' . $fieldName . ']';
|
||||
}
|
||||
$this->registerFieldNameForFormTokenGeneration($formTokenFieldName);
|
||||
}
|
||||
$this->tag->addAttribute('type', 'file');
|
||||
|
||||
if ($multiple) {
|
||||
$this->tag->addAttribute('name', $name . '[]');
|
||||
} else {
|
||||
$this->tag->addAttribute('name', $name);
|
||||
}
|
||||
|
||||
$this->setErrorClassAttribute();
|
||||
return $this->tag->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
|
||||
use TYPO3Fluid\Fluid\Core\Variables\ScopedVariableProvider;
|
||||
use TYPO3Fluid\Fluid\Core\Variables\StandardVariableProvider;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders form validation results.
|
||||
*
|
||||
* ```
|
||||
* <f:form.validationResults>
|
||||
* <f:if condition="{validationResults.flattenedErrors}">
|
||||
* <ul>
|
||||
* <f:for each="{validationResults.flattenedErrors}" as="errors" key="propertyPath">
|
||||
* <li>{propertyPath}
|
||||
* <ul>
|
||||
* <f:for each="{errors}" as="error">
|
||||
* <li>{error.code}: {error}</li>
|
||||
* </f:for>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </f:for>
|
||||
* </ul>
|
||||
* </f:if>
|
||||
* </f:form.validationResults>
|
||||
*
|
||||
* <f:form.validationResults for="someProperty">
|
||||
* <f:for each="{validationResults.flattenedErrors}" as="errors" key="propertyPath">
|
||||
* <f:for each="{errors}" as="error">
|
||||
* <p data-property-path="{propertyPath}">{error.code}: {error}</p>
|
||||
* </f:for>
|
||||
* </f:for>
|
||||
* </f:form.validationResults>
|
||||
* ```
|
||||
*
|
||||
* @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-validationresults
|
||||
*/
|
||||
final class ValidationResultsViewHelper extends AbstractViewHelper
|
||||
{
|
||||
/**
|
||||
* As this ViewHelper renders HTML, the output must not be escaped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $escapeOutput = false;
|
||||
|
||||
public function initializeArguments(): void
|
||||
{
|
||||
$this->registerArgument('for', 'string', 'The name of the error name (e.g. argument name or property name). This can also be a property path (like blog.title), and will then only display the validation errors of that property.', false, '');
|
||||
$this->registerArgument('as', 'string', 'The name of the variable to store the current error', false, 'validationResults');
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$for = $this->arguments['for'];
|
||||
$as = $this->arguments['as'];
|
||||
if (!$this->renderingContext->hasAttribute(ServerRequestInterface::class)
|
||||
|| !$this->renderingContext->getAttribute(ServerRequestInterface::class) instanceof RequestInterface
|
||||
) {
|
||||
throw new \RuntimeException('ValidationResultsViewHelper needs an extbase request to work.', 1724244193);
|
||||
}
|
||||
$extbaseRequestParameters = $this->renderingContext->getAttribute(ServerRequestInterface::class)->getAttribute('extbase');
|
||||
$validationResults = $extbaseRequestParameters->getOriginalRequestMappingResults();
|
||||
if ($validationResults !== null && $for !== '') {
|
||||
$validationResults = $validationResults->forProperty($for);
|
||||
}
|
||||
$variableProvider = new ScopedVariableProvider($this->renderingContext->getVariableProvider(), new StandardVariableProvider([$as => $validationResults]));
|
||||
$this->renderingContext->setVariableProvider($variableProvider);
|
||||
$output = (string)$this->renderChildren();
|
||||
$this->renderingContext->setVariableProvider($variableProvider->getGlobalVariableProvider());
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user