TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
<?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;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception as DomainException;
|
||||
|
||||
/**
|
||||
* A generic Form model Exception
|
||||
*/
|
||||
class Exception extends DomainException {}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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\Exception;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if two Form Elements with the same Identifier are added
|
||||
* to a form.
|
||||
*/
|
||||
class DuplicateFormElementException extends Exception {}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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\Exception;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a Finisher Preset was not found,
|
||||
* or if the implementationClassName was not set.
|
||||
*/
|
||||
class FinisherPresetNotFoundException extends Exception {}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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\Exception;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if the form definition would get an inconsistent state, like
|
||||
* adding a page to two different forms
|
||||
*/
|
||||
class FormDefinitionConsistencyException extends Exception {}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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\Exception;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a Validator Preset was not found,
|
||||
* or if the implementationClassName was not set.
|
||||
*/
|
||||
class ValidatorPresetNotFoundException extends Exception {}
|
||||
@@ -0,0 +1,698 @@
|
||||
<?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;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
|
||||
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
|
||||
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
|
||||
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Finishers\FinisherInterface;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\DuplicateFormElementException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FinisherPresetNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormElements\Page;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractCompositeRenderable;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\VariableRenderableInterface;
|
||||
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
|
||||
use TYPO3\CMS\Form\Exception as FormException;
|
||||
use TYPO3\CMS\Form\Mvc\ProcessingRule;
|
||||
|
||||
/**
|
||||
* This class encapsulates a complete *Form Definition*, with all of its pages,
|
||||
* form elements, validation rules which apply and finishers which should be
|
||||
* executed when the form is completely filled in.
|
||||
*
|
||||
* It is *not modified* when the form executes.
|
||||
*
|
||||
* The Anatomy Of A Form
|
||||
* =====================
|
||||
*
|
||||
* A FormDefinition consists of multiple *Page* ({@link Page}) objects. When a
|
||||
* form is displayed to the user, only one *Page* is visible at any given time,
|
||||
* and there is a navigation to go back and forth between the pages.
|
||||
*
|
||||
* A *Page* consists of multiple *FormElements* ({@link FormElementInterface}, {@link AbstractFormElement}),
|
||||
* which represent the input fields, textareas, checkboxes shown inside the page.
|
||||
*
|
||||
* *FormDefinition*, *Page* and *FormElement* have *identifier* properties, which
|
||||
* must be unique for each given type (i.e. it is allowed that the FormDefinition and
|
||||
* a FormElement have the *same* identifier, but two FormElements are not allowed to
|
||||
* have the same identifier.
|
||||
*
|
||||
* Simple Example
|
||||
* --------------
|
||||
*
|
||||
* Generally, you can create a FormDefinition manually by just calling the API
|
||||
* methods on it, or you use a *Form Definition Factory* to build the form from
|
||||
* another representation format such as YAML.
|
||||
*
|
||||
* /---code php
|
||||
* $formDefinition = GeneralUtility::makeInstance(FormDefinition::class, 'myForm');
|
||||
*
|
||||
* $page1 = GeneralUtility::makeInstance(Page::class, 'page1');
|
||||
* $formDefinition->addPage($page);
|
||||
*
|
||||
* $element1 = GeneralUtility::makeInstance(GenericFormElement::class, 'title', 'Textfield'); # the second argument is the type of the form element
|
||||
* $page1->addElement($element1);
|
||||
* \---
|
||||
*
|
||||
* Creating a Form, Using Abstract Form Element Types
|
||||
* =====================================================
|
||||
*
|
||||
* While you can use the {@link FormDefinition::addPage} or {@link Page::addElement}
|
||||
* methods and create the Page and FormElement objects manually, it is often better
|
||||
* to use the corresponding create* methods ({@link FormDefinition::createPage}
|
||||
* and {@link Page::createElement}), as you pass them an abstract *Form Element Type*
|
||||
* such as *Text* or *Page*, and the system **automatically
|
||||
* resolves the implementation class name and sets default values**.
|
||||
*
|
||||
* So the simple example from above should be rewritten as follows:
|
||||
*
|
||||
* /---code php
|
||||
* $prototypeConfiguration = []; // We'll talk about this later
|
||||
*
|
||||
* $formDefinition = GeneralUtility::makeInstance(FormDefinition::class, 'myForm', $prototypeConfiguration);
|
||||
* $page1 = $formDefinition->createPage('page1');
|
||||
* $element1 = $page1->addElement('title', 'Textfield');
|
||||
* \---
|
||||
*
|
||||
* Now, you might wonder how the system knows that the element *Textfield*
|
||||
* is implemented using a GenericFormElement: **This is configured in the $prototypeConfiguration**.
|
||||
*
|
||||
* To make the example from above actually work, we need to add some sensible
|
||||
* values to *$prototypeConfiguration*:
|
||||
*
|
||||
* <pre>
|
||||
* $prototypeConfiguration = [
|
||||
* 'formElementsDefinition' => [
|
||||
* 'Page' => [
|
||||
* 'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page'
|
||||
* ],
|
||||
* 'Textfield' => [
|
||||
* 'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* </pre>
|
||||
*
|
||||
* For each abstract *Form Element Type* we add some configuration; in the above
|
||||
* case only the *implementation class name*. Still, it is possible to set defaults
|
||||
* for *all* configuration options of such an element, as the following example
|
||||
* shows:
|
||||
*
|
||||
* <pre>
|
||||
* $prototypeConfiguration = [
|
||||
* 'formElementsDefinition' => [
|
||||
* 'Page' => [
|
||||
* 'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page',
|
||||
* 'label' => 'this is the label of the page if nothing is specified'
|
||||
* ],
|
||||
* 'Textfield' => [
|
||||
* 'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement',
|
||||
* 'label' = >'Default Label',
|
||||
* 'defaultValue' => 'Default form element value',
|
||||
* 'properties' => [
|
||||
* 'placeholder' => 'Text which is shown if element is empty'
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* </pre>
|
||||
*
|
||||
* Using Preconfigured $prototypeConfiguration
|
||||
* ---------------------------------
|
||||
*
|
||||
* Often, it is not really useful to manually create the $prototypeConfiguration array.
|
||||
*
|
||||
* Most of it comes pre-configured inside the YAML settings of the extensions,
|
||||
* and the {@link \TYPO3\CMS\Form\Domain\Configuration\ConfigurationService} contains helper methods
|
||||
* which return the ready-to-use *$prototypeConfiguration*.
|
||||
*
|
||||
* Property Mapping and Validation Rules
|
||||
* =====================================
|
||||
*
|
||||
* Besides Pages and FormElements, the FormDefinition can contain information
|
||||
* about the *format of the data* which is inputted into the form. This generally means:
|
||||
*
|
||||
* - expected Data Types
|
||||
* - Property Mapping Configuration to be used
|
||||
* - Validation Rules which should apply
|
||||
*
|
||||
* Background Info
|
||||
* ---------------
|
||||
* You might wonder why Data Types and Validation Rules are *not attached
|
||||
* to each FormElement itself*.
|
||||
*
|
||||
* If the form should create a *hierarchical output structure* such as a multi-
|
||||
* dimensional array or a PHP object, your expected data structure might look as follows:
|
||||
* <pre>
|
||||
* - person
|
||||
* -- firstName
|
||||
* -- lastName
|
||||
* -- address
|
||||
* --- street
|
||||
* --- city
|
||||
* </pre>
|
||||
*
|
||||
* Now, let's imagine you want to edit *person.address.street* and *person.address.city*,
|
||||
* but want to validate that the *combination* of *street* and *city* is valid
|
||||
* according to some address database.
|
||||
*
|
||||
* In this case, the form elements would be configured to fill *street* and *city*,
|
||||
* but the *validator* needs to be attached to the *compound object* *address*,
|
||||
* as both parts need to be validated together.
|
||||
*
|
||||
* Connecting FormElements to the output data structure
|
||||
* ====================================================
|
||||
*
|
||||
* The *identifier* of the *FormElement* is most important, as it determines
|
||||
* where in the output structure the value which is entered by the user is placed,
|
||||
* and thus also determines which validation rules need to apply.
|
||||
*
|
||||
* Using the above example, if you want to create a FormElement for the *street*,
|
||||
* you should use the identifier *person.address.street*.
|
||||
*
|
||||
* Rendering a FormDefinition
|
||||
* ==========================
|
||||
*
|
||||
* In order to trigger *rendering* on a FormDefinition,
|
||||
* the current {@link \TYPO3\CMS\Extbase\Mvc\Request} needs to be bound to the FormDefinition,
|
||||
* resulting in a {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime} object which contains the *Runtime State* of the form
|
||||
* (such as the currently inserted values).
|
||||
*
|
||||
* /---code php
|
||||
* # $currentRequest and $currentResponse need to be available, f.e. inside a controller you would
|
||||
* # use $this->request. Inside a ViewHelper you would use $this->renderingContext->getRequest()
|
||||
* $form = $formDefinition->bind($currentRequest);
|
||||
*
|
||||
* # now, you can use the $form object to get information about the currently
|
||||
* # entered values into the form, etc.
|
||||
* \---
|
||||
*
|
||||
* Refer to the {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime} API doc for further information.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*
|
||||
* @internal May change any time, use FormFactoryInterface to select a different FormDefinition if needed
|
||||
* @todo: Declare final in v12
|
||||
*/
|
||||
class FormDefinition extends AbstractCompositeRenderable implements VariableRenderableInterface
|
||||
{
|
||||
/**
|
||||
* The Form's pages
|
||||
*
|
||||
* @var array<int, Page>
|
||||
*/
|
||||
protected $renderables = [];
|
||||
|
||||
/**
|
||||
* The finishers for this form
|
||||
*
|
||||
* @var list<FinisherInterface>
|
||||
*/
|
||||
protected array $finishers = [];
|
||||
|
||||
/**
|
||||
* Property Mapping Rules, indexed by element identifier
|
||||
*
|
||||
* @var array<string, ProcessingRule>
|
||||
*/
|
||||
protected array $processingRules = [];
|
||||
|
||||
/**
|
||||
* Contains all elements of the form, indexed by identifier.
|
||||
* Is used as internal cache as we need this really often.
|
||||
*
|
||||
* @var array<string, FormElementInterface>
|
||||
*/
|
||||
protected array $elementsByIdentifier = [];
|
||||
|
||||
/**
|
||||
* Form element default values in the format ['elementIdentifier' => 'default value']
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $elementDefaultValues = [];
|
||||
|
||||
/**
|
||||
* Renderer class name to be used.
|
||||
*/
|
||||
protected string $rendererClassName = '';
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
protected array $typeDefinitions = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
protected array $validatorsDefinition = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
protected array $finishersDefinition = [];
|
||||
|
||||
/**
|
||||
* The persistence identifier of the form
|
||||
*/
|
||||
protected string $persistenceIdentifier = '';
|
||||
|
||||
/**
|
||||
* Constructor. Creates a new FormDefinition with the given identifier.
|
||||
*
|
||||
* @param string $identifier The Form Definition's identifier, must be a non-empty string.
|
||||
* @param array $prototypeConfiguration overrides form defaults of this definition
|
||||
* @param string $type element type of this form
|
||||
* @param string|null $persistenceIdentifier the persistence identifier of the form
|
||||
* @throws IdentifierNotValidException if the identifier was not valid
|
||||
*/
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
array $prototypeConfiguration = [],
|
||||
string $type = 'Form',
|
||||
?string $persistenceIdentifier = null
|
||||
) {
|
||||
$this->typeDefinitions = $prototypeConfiguration['formElementsDefinition'] ?? [];
|
||||
$this->validatorsDefinition = $prototypeConfiguration['validatorsDefinition'] ?? [];
|
||||
$this->finishersDefinition = $prototypeConfiguration['finishersDefinition'] ?? [];
|
||||
|
||||
if ($identifier === '') {
|
||||
throw new IdentifierNotValidException('The given identifier was empty.', 1477082503);
|
||||
}
|
||||
|
||||
$this->identifier = $identifier;
|
||||
$this->type = $type;
|
||||
$this->persistenceIdentifier = (string)$persistenceIdentifier;
|
||||
|
||||
if ($prototypeConfiguration !== []) {
|
||||
$this->initializeFromFormDefaults();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the form defaults of the current type
|
||||
*
|
||||
* @throws TypeDefinitionNotFoundException
|
||||
* @internal
|
||||
*/
|
||||
protected function initializeFromFormDefaults()
|
||||
{
|
||||
if (!isset($this->typeDefinitions[$this->type])) {
|
||||
throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $this->type), 1474905835);
|
||||
}
|
||||
$typeDefinition = $this->typeDefinitions[$this->type];
|
||||
$this->setOptions($typeDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple properties of this object at once.
|
||||
* Every property which has a corresponding set* method can be set using
|
||||
* the passed $options array.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function setOptions(array $options, bool $resetFinishers = false)
|
||||
{
|
||||
if (isset($options['rendererClassName'])) {
|
||||
$this->setRendererClassName($options['rendererClassName']);
|
||||
}
|
||||
if (isset($options['label'])) {
|
||||
$this->setLabel($options['label']);
|
||||
}
|
||||
if (isset($options['renderingOptions'])) {
|
||||
foreach ($options['renderingOptions'] as $key => $value) {
|
||||
$this->setRenderingOption($key, $value);
|
||||
}
|
||||
}
|
||||
if (isset($options['finishers'])) {
|
||||
if ($resetFinishers) {
|
||||
$this->finishers = [];
|
||||
}
|
||||
foreach ($options['finishers'] as $finisherConfiguration) {
|
||||
$this->createFinisher($finisherConfiguration['identifier'], $finisherConfiguration['options'] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['variants'])) {
|
||||
foreach ($options['variants'] as $variantConfiguration) {
|
||||
$this->createVariant($variantConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayUtility::assertAllArrayKeysAreValid(
|
||||
$options,
|
||||
['rendererClassName', 'renderingOptions', 'finishers', 'formEditor', 'label', 'variants']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a page with the given $identifier and attach this page to the form.
|
||||
*
|
||||
* - Create Page object based on the given $typeName
|
||||
* - set defaults inside the Page object
|
||||
* - attach Page object to this form
|
||||
* - return the newly created Page object
|
||||
*
|
||||
* @param string $identifier Identifier of the new page
|
||||
* @param string $typeName Type of the new page
|
||||
* @return Page the newly created page
|
||||
* @throws TypeDefinitionNotFoundException
|
||||
*/
|
||||
public function createPage(string $identifier, string $typeName = 'Page'): Page
|
||||
{
|
||||
if (!isset($this->typeDefinitions[$typeName])) {
|
||||
throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $typeName), 1474905953);
|
||||
}
|
||||
|
||||
$typeDefinition = $this->typeDefinitions[$typeName];
|
||||
|
||||
if (!isset($typeDefinition['implementationClassName'])) {
|
||||
throw new TypeDefinitionNotFoundException(sprintf('The "implementationClassName" was not set in type definition "%s".', $typeName), 1477083126);
|
||||
}
|
||||
$implementationClassName = $typeDefinition['implementationClassName'];
|
||||
|
||||
/** @var Page $page */
|
||||
$page = GeneralUtility::makeInstance($implementationClassName, $identifier, $typeName);
|
||||
|
||||
if (isset($typeDefinition['label'])) {
|
||||
$page->setLabel($typeDefinition['label']);
|
||||
}
|
||||
|
||||
if (isset($typeDefinition['renderingOptions'])) {
|
||||
foreach ($typeDefinition['renderingOptions'] as $key => $value) {
|
||||
$page->setRenderingOption($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($typeDefinition['variants'])) {
|
||||
foreach ($typeDefinition['variants'] as $variantConfiguration) {
|
||||
$page->createVariant($variantConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayUtility::assertAllArrayKeysAreValid(
|
||||
$typeDefinition,
|
||||
['implementationClassName', 'label', 'renderingOptions', 'formEditor', 'variants']
|
||||
);
|
||||
|
||||
$this->addPage($page);
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new page at the end of the form.
|
||||
*
|
||||
* Instead of this method, you should often use {@link createPage} instead.
|
||||
*
|
||||
* @param Page $page
|
||||
* @throws FormDefinitionConsistencyException if Page is already added to a FormDefinition
|
||||
* @see createPage
|
||||
*/
|
||||
public function addPage(Page $page)
|
||||
{
|
||||
$this->addRenderable($page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Form's pages
|
||||
*
|
||||
* @return array<int, Page> The Form's pages in the correct order
|
||||
*/
|
||||
public function getPages(): array
|
||||
{
|
||||
return $this->renderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a page with the given $index exists
|
||||
*
|
||||
* @return bool TRUE if a page with the given $index exists, otherwise FALSE
|
||||
*/
|
||||
public function hasPageWithIndex(int $index): bool
|
||||
{
|
||||
return isset($this->renderables[$index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page with the passed index. The first page has index zero.
|
||||
*
|
||||
* If page at $index does not exist, an exception is thrown. @see hasPageWithIndex()
|
||||
*
|
||||
* @param int $index
|
||||
* @return Page the page
|
||||
* @throws FormException if the specified index does not exist
|
||||
*/
|
||||
public function getPageByIndex(int $index)
|
||||
{
|
||||
if (!$this->hasPageWithIndex($index)) {
|
||||
throw new FormException(sprintf('There is no page with an index of %d', $index), 1329233627);
|
||||
}
|
||||
return $this->renderables[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified finisher to this form
|
||||
*/
|
||||
public function addFinisher(FinisherInterface $finisher)
|
||||
{
|
||||
$this->finishers[] = $finisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $finisherIdentifier identifier of the finisher as registered in the current form (for example: "Redirect")
|
||||
* @param array $options options for this finisher in the format ['option1' => 'value1', 'option2' => 'value2', ...]
|
||||
* @throws FinisherPresetNotFoundException
|
||||
*/
|
||||
public function createFinisher(string $finisherIdentifier, array $options = []): FinisherInterface
|
||||
{
|
||||
if (isset($this->finishersDefinition[$finisherIdentifier]['implementationClassName'])) {
|
||||
$implementationClassName = $this->finishersDefinition[$finisherIdentifier]['implementationClassName'];
|
||||
$defaultOptions = $this->finishersDefinition[$finisherIdentifier]['options'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
|
||||
/** @var FinisherInterface $finisher */
|
||||
$finisher = GeneralUtility::makeInstance($implementationClassName);
|
||||
$finisher->setFinisherIdentifier($finisherIdentifier);
|
||||
$finisher->setOptions($defaultOptions);
|
||||
$this->addFinisher($finisher);
|
||||
return $finisher;
|
||||
}
|
||||
throw new FinisherPresetNotFoundException('The finisher preset identified by "' . $finisherIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328709784);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all finishers of this form
|
||||
*
|
||||
* @return list<FinisherInterface>
|
||||
*/
|
||||
public function getFinishers(): array
|
||||
{
|
||||
return $this->finishers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to the ElementsByIdentifier Cache.
|
||||
*
|
||||
* @throws DuplicateFormElementException
|
||||
* @internal
|
||||
*/
|
||||
public function registerRenderable(RenderableInterface $renderable)
|
||||
{
|
||||
if ($renderable instanceof FormElementInterface) {
|
||||
if (isset($this->elementsByIdentifier[$renderable->getIdentifier()])) {
|
||||
throw new DuplicateFormElementException(sprintf('A form element with identifier "%s" is already part of the form.', $renderable->getIdentifier()), 1325663761);
|
||||
}
|
||||
$this->elementsByIdentifier[$renderable->getIdentifier()] = $renderable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the ElementsByIdentifier cache
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function unregisterRenderable(RenderableInterface $renderable)
|
||||
{
|
||||
if ($renderable instanceof FormElementInterface) {
|
||||
unset($this->elementsByIdentifier[$renderable->getIdentifier()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all form elements with their identifiers as keys
|
||||
*
|
||||
* @return array<string, FormElementInterface>
|
||||
*/
|
||||
public function getElements(): array
|
||||
{
|
||||
return $this->elementsByIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Form Element by its identifier
|
||||
*
|
||||
* If identifier does not exist, returns NULL.
|
||||
*
|
||||
* @param string $elementIdentifier
|
||||
* @return FormElementInterface|null The element with the given $elementIdentifier or NULL if none found
|
||||
*/
|
||||
public function getElementByIdentifier(string $elementIdentifier)
|
||||
{
|
||||
return $this->elementsByIdentifier[$elementIdentifier] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value of a form element
|
||||
*
|
||||
* @param string $elementIdentifier identifier of the form element. This supports property paths!
|
||||
* @param mixed $defaultValue
|
||||
* @internal
|
||||
*/
|
||||
public function addElementDefaultValue(string $elementIdentifier, $defaultValue)
|
||||
{
|
||||
$this->elementDefaultValues = ArrayUtility::setValueByPath(
|
||||
$this->elementDefaultValues,
|
||||
$elementIdentifier,
|
||||
$defaultValue,
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the default value of the specified form element
|
||||
* or NULL if no default value was set
|
||||
*
|
||||
* @param string $elementIdentifier identifier of the form element. This supports property paths!
|
||||
* @return mixed The elements default value
|
||||
* @internal
|
||||
*/
|
||||
public function getElementDefaultValueByIdentifier(string $elementIdentifier)
|
||||
{
|
||||
return ObjectAccess::getPropertyPath($this->elementDefaultValues, $elementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move $pageToMove before $referencePage
|
||||
*/
|
||||
public function movePageBefore(Page $pageToMove, Page $referencePage)
|
||||
{
|
||||
$this->moveRenderableBefore($pageToMove, $referencePage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move $pageToMove after $referencePage
|
||||
*/
|
||||
public function movePageAfter(Page $pageToMove, Page $referencePage)
|
||||
{
|
||||
$this->moveRenderableAfter($pageToMove, $referencePage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove $pageToRemove from form
|
||||
*/
|
||||
public function removePage(Page $pageToRemove)
|
||||
{
|
||||
$this->removeRenderable($pageToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the current request & response to this form instance, effectively creating
|
||||
* a new "instance" of the Form.
|
||||
*/
|
||||
public function bind(RequestInterface $request): FormRuntime
|
||||
{
|
||||
$formRuntime = GeneralUtility::makeInstance(FormRuntime::class);
|
||||
$formRuntime->setFormDefinition($this);
|
||||
$formRuntime->setRequest($request);
|
||||
$formRuntime->initialize();
|
||||
return $formRuntime;
|
||||
}
|
||||
|
||||
public function getProcessingRule(string $propertyPath): ProcessingRule
|
||||
{
|
||||
if (!isset($this->processingRules[$propertyPath])) {
|
||||
$this->processingRules[$propertyPath] = GeneralUtility::makeInstance(ProcessingRule::class);
|
||||
}
|
||||
return $this->processingRules[$propertyPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all mapping rules
|
||||
*
|
||||
* @return array<string, ProcessingRule>
|
||||
* @internal
|
||||
*/
|
||||
public function getProcessingRules(): array
|
||||
{
|
||||
return $this->processingRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
* @internal
|
||||
*/
|
||||
public function getTypeDefinitions(): array
|
||||
{
|
||||
return $this->typeDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
* @internal
|
||||
*/
|
||||
public function getValidatorsDefinition(): array
|
||||
{
|
||||
return $this->validatorsDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the persistence identifier of the form
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getPersistenceIdentifier(): string
|
||||
{
|
||||
return $this->persistenceIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the renderer class name
|
||||
*/
|
||||
public function setRendererClassName(string $rendererClassName)
|
||||
{
|
||||
$this->rendererClassName = $rendererClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the classname of the renderer
|
||||
*/
|
||||
public function getRendererClassName(): string
|
||||
{
|
||||
return $this->rendererClassName;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?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\Renderable;
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
|
||||
|
||||
/**
|
||||
* Convenience base class which implements common functionality for most
|
||||
* classes which implement CompositeRenderableInterface, i.e. have **child renderable elements**.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
abstract class AbstractCompositeRenderable extends AbstractRenderable implements CompositeRenderableInterface
|
||||
{
|
||||
/**
|
||||
* array of child renderables
|
||||
*
|
||||
* @var \TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface[]
|
||||
*/
|
||||
protected $renderables = [];
|
||||
|
||||
/**
|
||||
* Add a renderable to the list of child renderables.
|
||||
*
|
||||
* This function will be wrapped by the subclasses, f.e. with an "addPage"
|
||||
* or "addElement" method with the correct type hint.
|
||||
*
|
||||
* @param RenderableInterface $renderable
|
||||
* @throws FormDefinitionConsistencyException
|
||||
* @internal
|
||||
*/
|
||||
protected function addRenderable(RenderableInterface $renderable)
|
||||
{
|
||||
if ($renderable->getParentRenderable() !== null) {
|
||||
throw new FormDefinitionConsistencyException(sprintf('The renderable with identifier "%s" is already added to another element (element identifier: "%s").', $renderable->getIdentifier(), $renderable->getParentRenderable()->getIdentifier()), 1325665144);
|
||||
}
|
||||
$renderable->setIndex(count($this->renderables));
|
||||
$renderable->setParentRenderable($this);
|
||||
$this->renderables[] = $renderable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move $renderableToMove before $referenceRenderable
|
||||
*
|
||||
* This function will be wrapped by the subclasses, f.e. with an "movePageBefore"
|
||||
* or "moveElementBefore" method with the correct type hint.
|
||||
*
|
||||
* @param RenderableInterface $renderableToMove
|
||||
* @param RenderableInterface $referenceRenderable
|
||||
* @throws FormDefinitionConsistencyException
|
||||
* @internal
|
||||
*/
|
||||
protected function moveRenderableBefore(RenderableInterface $renderableToMove, RenderableInterface $referenceRenderable)
|
||||
{
|
||||
if ($renderableToMove->getParentRenderable() !== $referenceRenderable->getParentRenderable() || $renderableToMove->getParentRenderable() !== $this) {
|
||||
throw new FormDefinitionConsistencyException('Moved renderables need to be part of the same parent element.', 1326089744);
|
||||
}
|
||||
|
||||
$reorderedRenderables = [];
|
||||
$i = 0;
|
||||
foreach ($this->renderables as $renderable) {
|
||||
if ($renderable === $renderableToMove) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($renderable === $referenceRenderable) {
|
||||
$reorderedRenderables[] = $renderableToMove;
|
||||
$renderableToMove->setIndex($i);
|
||||
$i++;
|
||||
}
|
||||
$reorderedRenderables[] = $renderable;
|
||||
$renderable->setIndex($i);
|
||||
$i++;
|
||||
}
|
||||
$this->renderables = $reorderedRenderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move $renderableToMove after $referenceRenderable
|
||||
*
|
||||
* This function will be wrapped by the subclasses, f.e. with an "movePageAfter"
|
||||
* or "moveElementAfter" method with the correct type hint.
|
||||
*
|
||||
* @param RenderableInterface $renderableToMove
|
||||
* @param RenderableInterface $referenceRenderable
|
||||
* @throws FormDefinitionConsistencyException
|
||||
* @internal
|
||||
*/
|
||||
protected function moveRenderableAfter(RenderableInterface $renderableToMove, RenderableInterface $referenceRenderable)
|
||||
{
|
||||
if ($renderableToMove->getParentRenderable() !== $referenceRenderable->getParentRenderable() || $renderableToMove->getParentRenderable() !== $this) {
|
||||
throw new FormDefinitionConsistencyException('Moved renderables need to be part of the same parent element.', 1477083145);
|
||||
}
|
||||
|
||||
$reorderedRenderables = [];
|
||||
$i = 0;
|
||||
foreach ($this->renderables as $renderable) {
|
||||
if ($renderable === $renderableToMove) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reorderedRenderables[] = $renderable;
|
||||
$renderable->setIndex($i);
|
||||
$i++;
|
||||
|
||||
if ($renderable === $referenceRenderable) {
|
||||
$reorderedRenderables[] = $renderableToMove;
|
||||
$renderableToMove->setIndex($i);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$this->renderables = $reorderedRenderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all RenderableInterface instances of this composite renderable recursively
|
||||
*
|
||||
* @return RenderableInterface[]
|
||||
* @internal
|
||||
*/
|
||||
public function getRenderablesRecursively(): array
|
||||
{
|
||||
$renderables = [];
|
||||
foreach ($this->renderables as $renderable) {
|
||||
$renderables[] = $renderable;
|
||||
if ($renderable instanceof CompositeRenderableInterface) {
|
||||
$renderables = array_merge($renderables, $renderable->getRenderablesRecursively());
|
||||
}
|
||||
}
|
||||
return $renderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a renderable from this renderable.
|
||||
*
|
||||
* This function will be wrapped by the subclasses, f.e. with an "removePage"
|
||||
* or "removeElement" method with the correct type hint.
|
||||
*
|
||||
* @param RenderableInterface $renderableToRemove
|
||||
* @throws FormDefinitionConsistencyException
|
||||
* @internal
|
||||
*/
|
||||
protected function removeRenderable(RenderableInterface $renderableToRemove)
|
||||
{
|
||||
if ($renderableToRemove->getParentRenderable() !== $this) {
|
||||
throw new FormDefinitionConsistencyException('The renderable to be removed must be part of the calling parent renderable.', 1326090127);
|
||||
}
|
||||
|
||||
$updatedRenderables = [];
|
||||
foreach ($this->renderables as $renderable) {
|
||||
if ($renderable === $renderableToRemove) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$updatedRenderables[] = $renderable;
|
||||
}
|
||||
$this->renderables = $updatedRenderables;
|
||||
|
||||
$renderableToRemove->onRemoveFromParentRenderable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this element at the parent form, if there is a connection to the parent form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function registerInFormIfPossible()
|
||||
{
|
||||
parent::registerInFormIfPossible();
|
||||
foreach ($this->renderables as $renderable) {
|
||||
$renderable->registerInFormIfPossible();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called after a renderable has been removed from its parent
|
||||
* renderable.
|
||||
* This just passes the event down to all child renderables of this composite renderable.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function onRemoveFromParentRenderable()
|
||||
{
|
||||
foreach ($this->renderables as $renderable) {
|
||||
$renderable->onRemoveFromParentRenderable();
|
||||
}
|
||||
parent::onRemoveFromParentRenderable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
<?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\Renderable;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Cache\CacheManager;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
|
||||
use TYPO3\CMS\Extbase\Validation\ValidatorResolver;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\ValidatorPresetNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
|
||||
use TYPO3\CMS\Form\Event\BeforeRenderableIsRemovedFromFormEvent;
|
||||
|
||||
/**
|
||||
* Convenience base class which implements common functionality for most
|
||||
* classes which implement RenderableInterface.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractRenderable implements RenderableInterface, VariableRenderableInterface
|
||||
{
|
||||
/**
|
||||
* Abstract "type" of this Renderable. Is used during the rendering process
|
||||
* to determine the template file or the View PHP class being used to render
|
||||
* the particular element.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The identifier of this renderable
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* The parent renderable
|
||||
*/
|
||||
protected ?CompositeRenderableInterface $parentRenderable = null;
|
||||
|
||||
/**
|
||||
* The label of this renderable
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label = '';
|
||||
|
||||
/**
|
||||
* associative array of rendering options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $renderingOptions = [];
|
||||
|
||||
/**
|
||||
* The position of this renderable inside the parent renderable.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $index = 0;
|
||||
|
||||
/**
|
||||
* The name of the template file of the renderable.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $templateName = '';
|
||||
|
||||
/**
|
||||
* associative array of rendering variants
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $variants = [];
|
||||
|
||||
protected ?ValidatorResolver $validatorResolver = null;
|
||||
|
||||
protected ?ServerRequestInterface $request = null;
|
||||
|
||||
/**
|
||||
* Get the type of the renderable
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifier of the element
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the identifier of the element
|
||||
*/
|
||||
public function setIdentifier(string $identifier)
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
}
|
||||
|
||||
public function getRequest(): ?ServerRequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
public function setRequest(?ServerRequestInterface $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple properties of this object at once.
|
||||
* Every property which has a corresponding set* method can be set using
|
||||
* the passed $options array.
|
||||
*/
|
||||
public function setOptions(array $options, bool $resetValidators = false)
|
||||
{
|
||||
if (isset($options['label'])) {
|
||||
$this->setLabel($options['label']);
|
||||
}
|
||||
|
||||
if (isset($options['renderingOptions'])) {
|
||||
foreach ($options['renderingOptions'] as $key => $value) {
|
||||
$this->setRenderingOption($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['validators'])) {
|
||||
$runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
|
||||
$configurationHashes = $runtimeCache->get('formAbstractRenderableConfigurationHashes') ?: [];
|
||||
|
||||
if ($resetValidators) {
|
||||
$this->getRootForm()->getProcessingRule($this->getIdentifier())->removeAllValidators();
|
||||
$configurationHashes = [];
|
||||
}
|
||||
|
||||
foreach ($options['validators'] as $validatorConfiguration) {
|
||||
$configurationHash = md5(
|
||||
spl_object_hash($this)
|
||||
. json_encode($validatorConfiguration)
|
||||
);
|
||||
if (in_array($configurationHash, $configurationHashes)) {
|
||||
continue;
|
||||
}
|
||||
$this->createValidator($validatorConfiguration['identifier'], $validatorConfiguration['options'] ?? []);
|
||||
$configurationHashes[] = $configurationHash;
|
||||
$runtimeCache->set('formAbstractRenderableConfigurationHashes', $configurationHashes);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['variants'])) {
|
||||
foreach ($options['variants'] as $variantConfiguration) {
|
||||
$this->createVariant($variantConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayUtility::assertAllArrayKeysAreValid(
|
||||
$options,
|
||||
['label', 'defaultValue', 'properties', 'renderingOptions', 'validators', 'formEditor', 'variants']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a validator for the element.
|
||||
*
|
||||
* @throws ValidatorPresetNotFoundException
|
||||
*/
|
||||
public function createValidator(string $validatorIdentifier, array $options = []): ?ValidatorInterface
|
||||
{
|
||||
$validatorsDefinition = $this->getRootForm()->getValidatorsDefinition();
|
||||
if (isset($validatorsDefinition[$validatorIdentifier]) && is_array($validatorsDefinition[$validatorIdentifier]) && isset($validatorsDefinition[$validatorIdentifier]['implementationClassName'])) {
|
||||
$implementationClassName = $validatorsDefinition[$validatorIdentifier]['implementationClassName'];
|
||||
$defaultOptions = $validatorsDefinition[$validatorIdentifier]['options'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
|
||||
// @todo: It would be great if Renderable's and FormElements could use DI, but especially
|
||||
// FormElements which extend AbstractRenderable pollute __construct() with manual
|
||||
// arguments. To retrieve the ValidatorResolver, we have to fall back to getContainer()
|
||||
// for now, until this has been resolved.
|
||||
if ($this->validatorResolver === null) {
|
||||
$container = GeneralUtility::getContainer();
|
||||
$this->validatorResolver = $container->get(ValidatorResolver::class);
|
||||
}
|
||||
$validator = $this->validatorResolver->createValidator($implementationClassName, $defaultOptions, $this->request);
|
||||
if ($validator !== null) {
|
||||
$this->addValidator($validator);
|
||||
}
|
||||
return $validator;
|
||||
}
|
||||
throw new ValidatorPresetNotFoundException('The validator preset identified by "' . $validatorIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328710202);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a validator to the element.
|
||||
*/
|
||||
public function addValidator(ValidatorInterface $validator)
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
$formDefinition->getProcessingRule($this->getIdentifier())->addValidator($validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all validators on the element
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getValidators(): \SplObjectStorage
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
return $formDefinition->getProcessingRule($this->getIdentifier())->getValidators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the datatype
|
||||
*/
|
||||
public function setDataType(string $dataType)
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
$formDefinition->getProcessingRule($this->getIdentifier())->setDataType($dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the classname of the renderer
|
||||
*/
|
||||
public function getRendererClassName(): string
|
||||
{
|
||||
return $this->getRootForm()->getRendererClassName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all rendering options
|
||||
*/
|
||||
public function getRenderingOptions(): array
|
||||
{
|
||||
return $this->renderingOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rendering option $key to $value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function setRenderingOption(string $key, $value)
|
||||
{
|
||||
if (is_array($value) && isset($this->renderingOptions[$key]) && is_array($this->renderingOptions[$key])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($this->renderingOptions[$key], $value);
|
||||
$this->renderingOptions[$key] = ArrayUtility::removeNullValuesRecursive($this->renderingOptions[$key]);
|
||||
} elseif ($value === null) {
|
||||
unset($this->renderingOptions[$key]);
|
||||
} else {
|
||||
$this->renderingOptions[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent renderable
|
||||
*
|
||||
* @return CompositeRenderableInterface|null
|
||||
*/
|
||||
public function getParentRenderable()
|
||||
{
|
||||
return $this->parentRenderable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent renderable
|
||||
*/
|
||||
public function setParentRenderable(CompositeRenderableInterface $parentRenderable)
|
||||
{
|
||||
$this->parentRenderable = $parentRenderable;
|
||||
$this->registerInFormIfPossible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root form this element belongs to
|
||||
*
|
||||
* @throws FormDefinitionConsistencyException
|
||||
*/
|
||||
public function getRootForm(): FormDefinition
|
||||
{
|
||||
$rootRenderable = $this->parentRenderable;
|
||||
while ($rootRenderable !== null && !($rootRenderable instanceof FormDefinition)) {
|
||||
$rootRenderable = $rootRenderable->getParentRenderable();
|
||||
}
|
||||
if ($rootRenderable === null) {
|
||||
throw new FormDefinitionConsistencyException(sprintf('The form element "%s" is not attached to a parent form.', $this->identifier), 1326803398);
|
||||
}
|
||||
|
||||
return $rootRenderable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this element at the parent form, if there is a connection to the parent form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function registerInFormIfPossible()
|
||||
{
|
||||
try {
|
||||
$rootForm = $this->getRootForm();
|
||||
$rootForm->registerRenderable($this);
|
||||
} catch (FormDefinitionConsistencyException $exception) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the renderable is removed from it's parent
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function onRemoveFromParentRenderable()
|
||||
{
|
||||
$event = GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch(
|
||||
new BeforeRenderableIsRemovedFromFormEvent($this)
|
||||
);
|
||||
if ($event->isPropagationStopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$rootForm = $this->getRootForm();
|
||||
$rootForm->unregisterRenderable($this);
|
||||
} catch (FormDefinitionConsistencyException $exception) {
|
||||
}
|
||||
$this->parentRenderable = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the renderable
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getIndex(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the index of the renderable
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function setIndex(int $index)
|
||||
{
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label of the renderable
|
||||
*/
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label which shall be displayed next to the form element
|
||||
*/
|
||||
public function setLabel(string $label)
|
||||
{
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the templateName name of the renderable
|
||||
*/
|
||||
public function getTemplateName(): string
|
||||
{
|
||||
return empty($this->renderingOptions['templateName'])
|
||||
? $this->type
|
||||
: $this->renderingOptions['templateName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this renderable is enabled
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return !isset($this->renderingOptions['enabled']) || (bool)$this->renderingOptions['enabled'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all rendering variants
|
||||
*
|
||||
* @return RenderableVariantInterface[]
|
||||
*/
|
||||
public function getVariants(): array
|
||||
{
|
||||
return $this->variants;
|
||||
}
|
||||
|
||||
public function createVariant(array $options): RenderableVariantInterface
|
||||
{
|
||||
$identifier = $options['identifier'] ?? '';
|
||||
unset($options['identifier']);
|
||||
|
||||
$variant = GeneralUtility::makeInstance(RenderableVariant::class, $identifier, $options, $this);
|
||||
|
||||
$this->addVariant($variant);
|
||||
return $variant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified variant to this form element
|
||||
*/
|
||||
public function addVariant(RenderableVariantInterface $variant)
|
||||
{
|
||||
$this->variants[$variant->getIdentifier()] = $variant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified variant to this form element
|
||||
* regardless of their conditions
|
||||
*/
|
||||
public function applyVariant(RenderableVariantInterface $variant)
|
||||
{
|
||||
$variant->apply();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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\Renderable;
|
||||
|
||||
/**
|
||||
* Interface which all Form Parts must adhere to **when they have sub elements**.
|
||||
* This includes especially "FormDefinition" and "Page".
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
interface CompositeRenderableInterface extends RenderableInterface
|
||||
{
|
||||
/**
|
||||
* Returns all RenderableInterface instances of this composite renderable recursively
|
||||
*
|
||||
* @return \TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface[]
|
||||
* @internal
|
||||
*/
|
||||
public function getRenderablesRecursively(): array;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?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\Renderable;
|
||||
|
||||
/**
|
||||
* Base interface which all Form Parts except the FormDefinition must adhere
|
||||
* to (i.e. all elements which are NOT the root of a Form).
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
interface RenderableInterface extends RootRenderableInterface
|
||||
{
|
||||
/**
|
||||
* Return the parent renderable
|
||||
*
|
||||
* @return CompositeRenderableInterface|null the parent renderable
|
||||
* @internal
|
||||
*/
|
||||
public function getParentRenderable();
|
||||
|
||||
/**
|
||||
* Set the new parent renderable. You should not call this directly;
|
||||
* it is automatically called by addRenderable.
|
||||
*
|
||||
* This method should also register itself at the parent form, if possible.
|
||||
*
|
||||
* @param CompositeRenderableInterface $renderable
|
||||
* @internal
|
||||
*/
|
||||
public function setParentRenderable(CompositeRenderableInterface $renderable);
|
||||
|
||||
/**
|
||||
* Set the index of this renderable inside the parent renderable
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function setIndex(int $index);
|
||||
|
||||
/**
|
||||
* Get the index inside the parent renderable
|
||||
*/
|
||||
public function getIndex(): int;
|
||||
|
||||
/**
|
||||
* This function is called after a renderable has been removed from its parent
|
||||
* renderable. The function should make sure to clean up the internal state,
|
||||
* like resetting $this->parentRenderable or deregistering the renderable
|
||||
* of the form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function onRemoveFromParentRenderable();
|
||||
|
||||
/**
|
||||
* Register this element at the parent form, if there is a connection to the parent form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function registerInFormIfPossible();
|
||||
|
||||
/**
|
||||
* Get the template name of the renderable
|
||||
*/
|
||||
public function getTemplateName(): string;
|
||||
|
||||
/**
|
||||
* Returns whether this renderable is enabled
|
||||
*/
|
||||
public function isEnabled(): bool;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?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\Renderable;
|
||||
|
||||
use TYPO3\CMS\Core\ExpressionLanguage\Resolver;
|
||||
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* @internal
|
||||
*/
|
||||
class RenderableVariant implements RenderableVariantInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var VariableRenderableInterface
|
||||
*/
|
||||
protected $renderable;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $condition = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $applied = false;
|
||||
|
||||
/**
|
||||
* @throws IdentifierNotValidException
|
||||
*/
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
array $options,
|
||||
VariableRenderableInterface $renderable
|
||||
) {
|
||||
if ($identifier === '') {
|
||||
throw new IdentifierNotValidException('The given variant identifier was empty.', 1519998923);
|
||||
}
|
||||
$this->identifier = $identifier;
|
||||
$this->renderable = $renderable;
|
||||
|
||||
if (isset($options['condition']) && is_string($options['condition'])) {
|
||||
$this->condition = $options['condition'];
|
||||
}
|
||||
|
||||
unset($options['condition'], $options['identifier'], $options['variants']);
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified variant to this form element
|
||||
* regardless of their conditions
|
||||
*/
|
||||
public function apply(): void
|
||||
{
|
||||
$this->renderable->setOptions($this->options, true);
|
||||
$this->applied = true;
|
||||
}
|
||||
|
||||
public function conditionMatches(Resolver $conditionResolver): bool
|
||||
{
|
||||
if (empty($this->condition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool)$conditionResolver->evaluate($this->condition, ['renderable' => $this->renderable]);
|
||||
}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
public function isApplied(): bool
|
||||
{
|
||||
return $this->applied;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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\Renderable;
|
||||
|
||||
use TYPO3\CMS\Core\ExpressionLanguage\Resolver;
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* @internal
|
||||
*/
|
||||
interface RenderableVariantInterface
|
||||
{
|
||||
public function getIdentifier(): string;
|
||||
|
||||
/**
|
||||
* Apply the specified variant to this form element
|
||||
* regardless of their conditions
|
||||
*/
|
||||
public function apply(): void;
|
||||
|
||||
public function isApplied(): bool;
|
||||
|
||||
public function conditionMatches(Resolver $conditionResolver): bool;
|
||||
}
|
||||
@@ -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!
|
||||
*/
|
||||
|
||||
/*
|
||||
* Inspired by and partially taken from the Neos.Form package (www.neos.io)
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/**
|
||||
* Base interface which all parts of a form must adhere to.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
interface RootRenderableInterface
|
||||
{
|
||||
/**
|
||||
* Abstract "type" of this Renderable. Is used during the rendering process
|
||||
* to determine the template file or the View PHP class being used to render
|
||||
* the particular element.
|
||||
*/
|
||||
public function getType(): string;
|
||||
|
||||
/**
|
||||
* The identifier of this renderable
|
||||
*/
|
||||
public function getIdentifier(): string;
|
||||
|
||||
/**
|
||||
* Get the label which shall be displayed next to the form element
|
||||
*/
|
||||
public function getLabel(): string;
|
||||
|
||||
/**
|
||||
* Get the renderer class name to be used to display this form;
|
||||
* must implement RendererInterface
|
||||
*
|
||||
* @return string the renderer class name
|
||||
*/
|
||||
public function getRendererClassName(): string;
|
||||
|
||||
/**
|
||||
* Get all rendering options
|
||||
*
|
||||
* @return array associative array of rendering options
|
||||
*/
|
||||
public function getRenderingOptions(): array;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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\Renderable;
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
interface VariableRenderableInterface
|
||||
{
|
||||
/**
|
||||
* Set multiple properties of this object at once.
|
||||
* Every property which has a corresponding set* method can be set using
|
||||
* the passed $options array.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function setOptions(array $options, bool $reset = false);
|
||||
|
||||
/**
|
||||
* Get all rendering variants
|
||||
*
|
||||
* @return RenderableVariantInterface[]
|
||||
*/
|
||||
public function getVariants(): array;
|
||||
|
||||
/**
|
||||
* Adds the specified variant to this form element
|
||||
*/
|
||||
public function addVariant(RenderableVariantInterface $variant);
|
||||
}
|
||||
Reference in New Issue
Block a user