TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:24 +02:00
commit aad9daaefd
1506 changed files with 94005 additions and 0 deletions
@@ -0,0 +1,166 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator;
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable;
/**
* A base form element, which is the starting point for creating custom (PHP-based)
* Form Elements.
*
* A *FormElement* is a part of a *Page*, which in turn is part of a FormDefinition.
* See {@link FormDefinition} for an in-depth explanation.
*
* Subclassing this class is a good starting-point for implementing custom PHP-based
* Form Elements.
*
* Most of the functionality and API is implemented in {@link \TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable}, so
* make sure to check out this class as well.
*
* Still, it is quite rare that you need to subclass this class; often
* you can just use the {@link \TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement} and replace some templates.
*
* Scope: frontend
* **This class is meant to be sub classed by developers.**
*/
abstract class AbstractFormElement extends AbstractRenderable implements FormElementInterface
{
/**
* @var array
*/
protected $properties = [];
/**
* Constructor. Needs this FormElement's identifier and the FormElement type
*
* @param string $identifier The FormElement's identifier
* @param string $type The Form Element Type
* @throws IdentifierNotValidException
*/
public function __construct(string $identifier, string $type)
{
if (strlen($identifier) === 0) {
throw new IdentifierNotValidException('The given identifier string is empty.', 1477082502);
}
$this->identifier = $identifier;
$this->type = $type;
}
/**
* Override this method in your custom FormElements if needed
*/
public function initializeFormElement() {}
/**
* Get the global unique identifier of the element
*/
public function getUniqueIdentifier(): string
{
$formDefinition = $this->getRootForm();
$uniqueIdentifier = sprintf('%s-%s', $formDefinition->getIdentifier(), $this->identifier);
$uniqueIdentifier = (string)preg_replace('/[^a-zA-Z0-9_-]/', '_', $uniqueIdentifier);
return lcfirst($uniqueIdentifier);
}
public function setOptions(array $options, bool $resetValidators = false)
{
if (isset($options['defaultValue'])) {
$this->setDefaultValue($options['defaultValue']);
}
if (isset($options['properties'])) {
foreach ($options['properties'] as $key => $value) {
$this->setProperty($key, $value);
}
}
parent::setOptions($options, $resetValidators);
}
/**
* Get the default value of the element
*
* @return mixed
*/
public function getDefaultValue()
{
$formDefinition = $this->getRootForm();
return $formDefinition->getElementDefaultValueByIdentifier($this->identifier);
}
/**
* Set the default value of the element
*
* @param mixed $defaultValue
*/
public function setDefaultValue($defaultValue)
{
$formDefinition = $this->getRootForm();
$currentDefaultValue = $formDefinition->getElementDefaultValueByIdentifier($this->identifier);
if (is_array($currentDefaultValue) && is_array($defaultValue)) {
ArrayUtility::mergeRecursiveWithOverrule($currentDefaultValue, $defaultValue);
$defaultValue = ArrayUtility::removeNullValuesRecursive($currentDefaultValue);
}
$formDefinition->addElementDefaultValue($this->identifier, $defaultValue);
}
/**
* Check if the element is required
*/
public function isRequired(): bool
{
foreach ($this->getValidators() as $validator) {
if ($validator instanceof NotEmptyValidator) {
return true;
}
}
return false;
}
/**
* Set a property of the element
*
* @param mixed $value
*/
public function setProperty(string $key, $value)
{
if (is_array($value) && isset($this->properties[$key]) && is_array($this->properties[$key])) {
ArrayUtility::mergeRecursiveWithOverrule($this->properties[$key], $value);
$this->properties[$key] = ArrayUtility::removeNullValuesRecursive($this->properties[$key]);
} elseif ($value === null) {
unset($this->properties[$key]);
} else {
$this->properties[$key] = $value;
}
}
/**
* Get all properties
*/
public function getProperties(): array
{
return $this->properties;
}
}
@@ -0,0 +1,182 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotFoundException;
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotValidException;
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractCompositeRenderable;
/**
* A base class for "section-like" form parts like "Page" or "Section" (which
* is rendered as "Fieldset")
*
* This class contains multiple FormElements ({@link FormElementInterface}).
*
* Please see {@link FormDefinition} for an in-depth explanation.
*
* **This class is NOT meant to be sub classed by developers.**
* Scope: frontend
*/
abstract class AbstractSection extends AbstractCompositeRenderable
{
/**
* @var FormElementInterface[]
*/
protected $renderables = [];
/**
* Constructor. Needs the identifier and type of this element
*
* @param string $identifier The Section identifier
* @param string $type The Section type
* @throws IdentifierNotValidException if the identifier was no non-empty string
*/
public function __construct(string $identifier, string $type)
{
if ($identifier === '') {
throw new IdentifierNotValidException('The given identifier was empty.', 1477082501);
}
$this->identifier = $identifier;
$this->type = $type;
}
/**
* Get the child Form Elements
*
* @return FormElementInterface[] The Page's elements
*/
public function getElements(): array
{
return $this->renderables;
}
/**
* Get the child Form Elements
*
* @return FormElementInterface[] The Page's elements
*/
public function getElementsRecursively(): array
{
return $this->getRenderablesRecursively();
}
/**
* Add a new form element at the end of the section
*
* @param FormElementInterface $formElement The form element to add
* @throws FormDefinitionConsistencyException if FormElement is already added to a section
*/
public function addElement(FormElementInterface $formElement)
{
$this->addRenderable($formElement);
}
/**
* Create a form element with the given $identifier and attach it to this section/page.
*
* - Create Form Element object based on the given $typeName
* - set defaults inside the Form Element (based on the parent form's field defaults)
* - attach Form Element to this Section/Page
* - return the newly created Form Element object
*
*
* @param string $identifier Identifier of the new form element
* @param string $typeName type of the new form element
* @return FormElementInterface the newly created form element
* @throws TypeDefinitionNotFoundException
* @throws TypeDefinitionNotValidException
*/
public function createElement(string $identifier, string $typeName): FormElementInterface
{
$formDefinition = $this->getRootForm();
$typeDefinitions = $formDefinition->getTypeDefinitions();
if (isset($typeDefinitions[$typeName])) {
$typeDefinition = $typeDefinitions[$typeName];
} else {
$renderingOptions = $formDefinition->getRenderingOptions();
$skipUnknownElements = isset($renderingOptions['skipUnknownElements']) && $renderingOptions['skipUnknownElements'] === true;
if (!$skipUnknownElements) {
throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $typeName), 1382364019);
}
$element = GeneralUtility::makeInstance(UnknownFormElement::class, $identifier, $typeName);
$this->addElement($element);
return $element;
}
if (!isset($typeDefinition['implementationClassName'])) {
throw new TypeDefinitionNotFoundException(sprintf('The "implementationClassName" was not set in type definition "%s".', $typeName), 1325689855);
}
$implementationClassName = $typeDefinition['implementationClassName'];
$element = GeneralUtility::makeInstance($implementationClassName, $identifier, $typeName);
if (!$element instanceof FormElementInterface) {
throw new TypeDefinitionNotValidException(sprintf('The "implementationClassName" for element "%s" ("%s") does not implement the FormElementInterface.', $identifier, $implementationClassName), 1327318156);
}
unset($typeDefinition['implementationClassName']);
$this->addElement($element);
$element->setOptions($typeDefinition);
$element->initializeFormElement();
return $element;
}
/**
* Move FormElement $element before $referenceElement.
*
* Both $element and $referenceElement must be direct descendants of this Section/Page.
*
* @param FormElementInterface $elementToMove
* @param FormElementInterface $referenceElement
*/
public function moveElementBefore(FormElementInterface $elementToMove, FormElementInterface $referenceElement)
{
$this->moveRenderableBefore($elementToMove, $referenceElement);
}
/**
* Move FormElement $element after $referenceElement
*
* Both $element and $referenceElement must be direct descendants of this Section/Page.
*
* @param FormElementInterface $elementToMove
* @param FormElementInterface $referenceElement
*/
public function moveElementAfter(FormElementInterface $elementToMove, FormElementInterface $referenceElement)
{
$this->moveRenderableAfter($elementToMove, $referenceElement);
}
/**
* Remove $elementToRemove from this Section/Page
*/
public function removeElement(FormElementInterface $elementToRemove)
{
$this->removeRenderable($elementToRemove);
}
}
@@ -0,0 +1,56 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter;
/**
* A date form element
*
* Scope: frontend
*/
class Date extends AbstractFormElement implements StringableFormElementInterface
{
/**
* Initializes the Form Element by setting the data type to "DateTime"
* @internal
*/
public function initializeFormElement()
{
$this->setDataType(\DateTime::class);
/** @var \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration $propertyMappingConfiguration */
$propertyMappingConfiguration = $this->getRootForm()->getProcessingRule($this->getIdentifier())->getPropertyMappingConfiguration();
// @see https://www.w3.org/TR/2011/WD-html-markup-20110405/input.date.html#input.date.attrs.value
// 'Y-m-d' = https://tools.ietf.org/html/rfc3339#section-5.6 -> full-date
$propertyMappingConfiguration->setTypeConverterOption(DateTimeConverter::class, DateTimeConverter::CONFIGURATION_DATE_FORMAT, 'Y-m-d');
}
/**
* @param \DateTime $value
*/
public function valueToString($value): string
{
$dateFormat = $this->properties['displayFormat'] ?? 'Y-m-d';
return $value->format($dateFormat);
}
}
@@ -0,0 +1,107 @@
<?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\Form\Domain\Model\FormElements;
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter;
/**
* A generic file upload form element
*
* Scope: frontend
*/
class FileUpload extends AbstractFormElement
{
/**
* Initializes the Form Element by setting the data type to an Extbase File Reference
* @internal
*/
public function initializeFormElement()
{
$this->setDataType(FileReference::class);
// Set the property mapping configuration for the file upload element.
// * Add the UploadedFileReferenceConverter to convert an uploaded file to a
// FileReference (single upload) or ObjectStorage (multiple uploads).
// * Setup the storage:
// If the property "saveToFileMount" exist for this element it will be used.
// If this file mount or the property "saveToFileMount" does not exist
// the default storage "1:/user_uploads/" will be used. Uploads are placed
// in a dedicated sub-folder (e.g. ".../form_<40-chars-hash>/actual.file").
$typeConverter = GeneralUtility::makeInstance(UploadedFileReferenceConverter::class);
/** @var \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration $propertyMappingConfiguration */
$propertyMappingConfiguration = $this->getRootForm()
->getProcessingRule($this->getIdentifier())
->getPropertyMappingConfiguration()
->setTypeConverter($typeConverter);
$uploadConfiguration = [
UploadedFileReferenceConverter::CONFIGURATION_UPLOAD_CONFLICT_MODE => 'rename',
];
// In preview mode (Form Editor backend module), skip upload folder resolution
// entirely. File uploads are non-functional during preview and resolving the
// target folder may throw access permission exceptions for backend users who
// do not have access to the configured upload storage.
if (!($this->getRootForm()->getRenderingOptions()['previewMode'] ?? false)) {
$saveToFileMountIdentifier = $this->getProperties()['saveToFileMount'] ?? '';
if ($this->checkSaveFileMountAccess($saveToFileMountIdentifier)) {
$uploadConfiguration[UploadedFileReferenceConverter::CONFIGURATION_UPLOAD_FOLDER] = $saveToFileMountIdentifier;
} else {
// @todo Why should uploaded files be stored to the same directory as the *.form.yaml definitions?
$persistenceIdentifier = $this->getRootForm()->getPersistenceIdentifier();
if (!empty($persistenceIdentifier)) {
$pathinfo = PathUtility::pathinfo($persistenceIdentifier);
$saveToFileMountIdentifier = $pathinfo['dirname'];
if ($this->checkSaveFileMountAccess($saveToFileMountIdentifier)) {
$uploadConfiguration[UploadedFileReferenceConverter::CONFIGURATION_UPLOAD_FOLDER] = $saveToFileMountIdentifier;
}
}
}
}
$propertyMappingConfiguration->setTypeConverterOptions(UploadedFileReferenceConverter::class, $uploadConfiguration);
}
/**
* @internal
*/
protected function checkSaveFileMountAccess(string $saveToFileMountIdentifier): bool
{
if (empty($saveToFileMountIdentifier)) {
return false;
}
if (PathUtility::isExtensionPath($saveToFileMountIdentifier)) {
return false;
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
try {
$resourceFactory->getFolderObjectFromCombinedIdentifier($saveToFileMountIdentifier);
return true;
} catch (\InvalidArgumentException|InsufficientFolderAccessPermissionsException|FolderDoesNotExistException $e) {
return false;
}
}
}
@@ -0,0 +1,117 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
/**
* A base form element interface, which can be the starting point for creating
* custom (PHP-based) Form Elements.
*
* A *FormElement* is a part of a *Page*, which in turn is part of a FormDefinition.
* See {@link FormDefinition} for an in-depth explanation.
*
* **Often, you should rather subclass {@link AbstractFormElement} instead of
* implementing this interface.**
*
* Scope: frontend
*/
interface FormElementInterface extends RenderableInterface
{
/**
* Will be called as soon as the element is (tried to be) added to a form
* @see registerInFormIfPossible()
*
* @internal
*/
public function initializeFormElement();
/**
* Returns a unique identifier of this element.
* While element identifiers are only unique within one form,
* this includes the identifier of the form itself, making it "globally" unique
*
* @return string the "globally" unique identifier of this element
*/
public function getUniqueIdentifier(): string;
/**
* Get the default value with which the Form Element should be initialized
* during display.
*
* @return mixed the default value for this Form Element
*/
public function getDefaultValue();
/**
* Set the default value with which the Form Element should be initialized
* during display.
*
* @param mixed $defaultValue the default value for this Form Element
*/
public function setDefaultValue($defaultValue);
/**
* Set an element-specific configuration property.
*
* @param mixed $value
*/
public function setProperty(string $key, $value);
/**
* Get all element-specific configuration properties
*/
public function getProperties(): array;
/**
* Set a rendering option
*
* @param mixed $value
*/
public function setRenderingOption(string $key, $value);
/**
* Returns the child validators of the ConjunctionValidator that is registered for this element
*
* @return \SplObjectStorage<ValidatorInterface>
* @internal
*/
public function getValidators(): \SplObjectStorage;
/**
* Registers a validator for this element
*/
public function addValidator(ValidatorInterface $validator);
/**
* Set the target data type for this element
*
* @param string $dataType the target data type
*/
public function setDataType(string $dataType);
/**
* Whether or not this element is required
*/
public function isRequired(): bool;
}
@@ -0,0 +1,29 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
/**
* A generic form element
*
* Scope: frontend
*/
class GenericFormElement extends AbstractFormElement {}
@@ -0,0 +1,30 @@
<?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\Form\Domain\Model\FormElements;
/**
* A grid column, being part of a grid container
*
* This class contains multiple FormElements ({@link FormElementInterface}).
*
* Please see {@link FormDefinition} for an in-depth explanation.
*
* Scope: frontend
* **This class is NOT meant to be sub classed by developers.**
*/
class GridColumn extends Section implements GridColumnInterface {}
@@ -0,0 +1,23 @@
<?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\Form\Domain\Model\FormElements;
/**
* Scope: frontend
*/
interface GridColumnInterface extends FormElementInterface {}
@@ -0,0 +1,30 @@
<?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\Form\Domain\Model\FormElements;
/**
* A grid row, being part of a grid container
*
* This class contains multiple FormElements ({@link FormElementInterface}).
*
* Please see {@link FormDefinition} for an in-depth explanation.
*
* Scope: frontend
* **This class is NOT meant to be sub classed by developers.**
*/
class GridRow extends Section implements GridRowInterface {}
@@ -0,0 +1,23 @@
<?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\Form\Domain\Model\FormElements;
/**
* Scope: frontend
*/
interface GridRowInterface extends FormElementInterface {}
@@ -0,0 +1,69 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
use TYPO3\CMS\Form\Domain\Model\Renderable\CompositeRenderableInterface;
use TYPO3\CMS\Form\Exception as FormException;
/**
* A Page, being part of a bigger FormDefinition. It contains numerous FormElements
* as children.
*
* A FormDefinition consists of multiple Pages, where only one page is visible
* at any given time.
*
* Most of the API of this object is implemented in {@link AbstractSection},
* so make sure to review this class as well.
*
* Please see {@link FormDefinition} for an in-depth explanation.
*
* Scope: frontend
* **This class is NOT meant to be sub classed by developers.**
*/
class Page extends AbstractSection
{
/**
* Constructor. Needs this Page's identifier
*
* @param string $identifier The Page's identifier
* @param string $type The Page's type
* @throws \TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException if the identifier was no non-empty string
*/
public function __construct(string $identifier, string $type = 'Page')
{
parent::__construct($identifier, $type);
}
/**
* Set the parent renderable
*
* @throws FormException
*/
public function setParentRenderable(CompositeRenderableInterface $parentRenderable)
{
if (!($parentRenderable instanceof FormDefinition)) {
throw new FormException(sprintf('The specified parentRenderable must be a FormDefinition, got "%s"', get_debug_type($parentRenderable)), 1329233747);
}
parent::setParentRenderable($parentRenderable);
}
}
@@ -0,0 +1,134 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator;
/**
* A Section, being part of a bigger Page
*
* This class contains multiple FormElements ({@link FormElementInterface}).
*
* Please see {@link FormDefinition} for an in-depth explanation.
*
* Scope: frontend
* **This class is NOT meant to be sub classed by developers.**
*/
class Section extends AbstractSection implements FormElementInterface
{
/**
* @var array
*/
protected $properties = [];
/**
* Will be called as soon as the element is (tried to be) added to a form
* @see registerInFormIfPossible()
*
* @internal
*/
public function initializeFormElement() {}
public function setOptions(array $options, bool $resetValidators = false)
{
if (isset($options['properties'])) {
foreach ($options['properties'] as $key => $value) {
$this->setProperty($key, $value);
}
}
parent::setOptions($options, $resetValidators);
}
/**
* Returns a unique identifier of this element.
* While element identifiers are only unique within one form,
* this includes the identifier of the form itself, making it "globally" unique
*
* @return string the "globally" unique identifier of this element
*/
public function getUniqueIdentifier(): string
{
$formDefinition = $this->getRootForm();
return sprintf('%s-%s', $formDefinition->getIdentifier(), $this->identifier);
}
/**
* Get the default value with which the Form Element should be initialized
* during display.
* Note: This is currently not used for section elements
*
* @return mixed the default value for this Form Element
*/
public function getDefaultValue()
{
return null;
}
/**
* Set the default value with which the Form Element should be initialized
* during display.
* Note: This is currently ignored for section elements
*
* @param mixed $defaultValue the default value for this Form Element
*/
public function setDefaultValue($defaultValue) {}
/**
* Get all element-specific configuration properties
*/
public function getProperties(): array
{
return $this->properties;
}
/**
* Set an element-specific configuration property.
*
* @param mixed $value
*/
public function setProperty(string $key, $value)
{
if (is_array($value) && isset($this->properties[$key]) && is_array($this->properties[$key])) {
ArrayUtility::mergeRecursiveWithOverrule($this->properties[$key], $value);
$this->properties[$key] = ArrayUtility::removeNullValuesRecursive($this->properties[$key]);
} elseif ($value === null) {
unset($this->properties[$key]);
} else {
$this->properties[$key] = $value;
}
}
/**
* Whether or not this element is required
*/
public function isRequired(): bool
{
foreach ($this->getValidators() as $validator) {
if ($validator instanceof NotEmptyValidator) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,32 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
/**
* Interface for form elements capable of converting their complex values to string
*
* @internal
*/
interface StringableFormElementInterface
{
public function valueToString($value): string;
}
@@ -0,0 +1,118 @@
<?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!
*/
/*
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
*/
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable;
/**
* A Form Element that has no definition.
*
* Scope: frontend
*/
class UnknownFormElement extends AbstractRenderable implements FormElementInterface
{
/**
* Constructor. Needs this FormElement's identifier and the FormElement type
*
* @param string $identifier The FormElement's identifier
* @param string $type The Form Element Type
* @throws IdentifierNotValidException
*/
public function __construct(string $identifier, string $type)
{
if ($identifier === '') {
throw new IdentifierNotValidException('The given identifier was empty.', 1382364370);
}
$this->identifier = $identifier;
$this->type = $type;
}
/**
* Sets up the form element
*/
public function initializeFormElement() {}
/**
* Returns a unique identifier of this element.
* While element identifiers are only unique within one form,
* this includes the identifier of the form itself, making it "globally" unique
*
* @return string the "globally" unique identifier of this element
*/
public function getUniqueIdentifier(): string
{
$formDefinition = $this->getRootForm();
$uniqueIdentifier = sprintf('%s-%s', $formDefinition->getIdentifier(), $this->identifier);
$uniqueIdentifier = (string)preg_replace('/[^a-zA-Z0-9-_]/', '_', $uniqueIdentifier);
return lcfirst($uniqueIdentifier);
}
/**
* Get the template name of the renderable
*/
public function getTemplateName(): string
{
return 'UnknownElement';
}
/**
* @return mixed the default value for this Form Element
* @internal
*/
public function getDefaultValue()
{
return null;
}
/**
* Not used in this implementation
*
* @param mixed $defaultValue the default value for this Form Element
* @internal
*/
public function setDefaultValue($defaultValue) {}
/**
* Not used in this implementation
*
* @param mixed $value
* @internal
*/
public function setProperty(string $key, $value) {}
/**
* @internal
*/
public function getProperties(): array
{
return [];
}
/**
* @internal
*/
public function isRequired(): bool
{
return false;
}
}