1<?php
2declare(strict_types = 1);
3namespace TYPO3\CMS\Form\Domain\Model\FormElements;
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It originated from the Neos.Form package (www.neos.io)
9 *
10 * It is free software; you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License, either version 2
12 * of the License, or any later version.
13 *
14 * For the full copyright and license information, please read the
15 * LICENSE.txt file that was distributed with this source code.
16 *
17 * The TYPO3 project - inspiring people to share!
18 */
19
20use TYPO3\CMS\Core\Utility\GeneralUtility;
21use TYPO3\CMS\Extbase\Object\ObjectManager;
22use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
23use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotFoundException;
24use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotValidException;
25use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
26use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractCompositeRenderable;
27
28/**
29 * A base class for "section-like" form parts like "Page" or "Section" (which
30 * is rendered as "Fieldset")
31 *
32 * This class contains multiple FormElements ({@link FormElementInterface}).
33 *
34 * Please see {@link FormDefinition} for an in-depth explanation.
35 *
36 * **This class is NOT meant to be sub classed by developers.**
37 * Scope: frontend
38 */
39abstract class AbstractSection extends AbstractCompositeRenderable
40{
41
42    /**
43     * Constructor. Needs the identifier and type of this element
44     *
45     * @param string $identifier The Section identifier
46     * @param string $type The Section type
47     * @throws IdentifierNotValidException if the identifier was no non-empty string
48     */
49    public function __construct(string $identifier, string $type)
50    {
51        if (!is_string($identifier) || strlen($identifier) === 0) {
52            throw new IdentifierNotValidException('The given identifier was not a string or the string was empty.', 1477082501);
53        }
54
55        $this->identifier = $identifier;
56        $this->type = $type;
57    }
58
59    /**
60     * Get the child Form Elements
61     *
62     * @return FormElementInterface[] The Page's elements
63     */
64    public function getElements(): array
65    {
66        return $this->renderables;
67    }
68
69    /**
70     * Get the child Form Elements
71     *
72     * @return FormElementInterface[] The Page's elements
73     */
74    public function getElementsRecursively(): array
75    {
76        return $this->getRenderablesRecursively();
77    }
78
79    /**
80     * Add a new form element at the end of the section
81     *
82     * @param FormElementInterface $formElement The form element to add
83     * @throws FormDefinitionConsistencyException if FormElement is already added to a section
84     */
85    public function addElement(FormElementInterface $formElement)
86    {
87        $this->addRenderable($formElement);
88    }
89
90    /**
91     * Create a form element with the given $identifier and attach it to this section/page.
92     *
93     * - Create Form Element object based on the given $typeName
94     * - set defaults inside the Form Element (based on the parent form's field defaults)
95     * - attach Form Element to this Section/Page
96     * - return the newly created Form Element object
97     *
98     *
99     * @param string $identifier Identifier of the new form element
100     * @param string $typeName type of the new form element
101     * @return FormElementInterface the newly created form element
102     * @throws TypeDefinitionNotFoundException
103     * @throws TypeDefinitionNotValidException
104     */
105    public function createElement(string $identifier, string $typeName): FormElementInterface
106    {
107        $formDefinition = $this->getRootForm();
108
109        $typeDefinitions = $formDefinition->getTypeDefinitions();
110        if (isset($typeDefinitions[$typeName])) {
111            $typeDefinition = $typeDefinitions[$typeName];
112        } else {
113            $renderingOptions = $formDefinition->getRenderingOptions();
114            $skipUnknownElements = isset($renderingOptions['skipUnknownElements']) && $renderingOptions['skipUnknownElements'] === true;
115            if (!$skipUnknownElements) {
116                throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $typeName), 1382364019);
117            }
118
119            $element = GeneralUtility::makeInstance(ObjectManager::class)
120                ->get(UnknownFormElement::class, $identifier, $typeName);
121            $this->addElement($element);
122            return $element;
123        }
124
125        if (!isset($typeDefinition['implementationClassName'])) {
126            throw new TypeDefinitionNotFoundException(sprintf('The "implementationClassName" was not set in type definition "%s".', $typeName), 1325689855);
127        }
128
129        $implementationClassName = $typeDefinition['implementationClassName'];
130        $element = GeneralUtility::makeInstance(ObjectManager::class)
131            ->get($implementationClassName, $identifier, $typeName);
132        if (!$element instanceof FormElementInterface) {
133            throw new TypeDefinitionNotValidException(sprintf('The "implementationClassName" for element "%s" ("%s") does not implement the FormElementInterface.', $identifier, $implementationClassName), 1327318156);
134        }
135        unset($typeDefinition['implementationClassName']);
136
137        $this->addElement($element);
138        $element->setOptions($typeDefinition);
139
140        $element->initializeFormElement();
141        return $element;
142    }
143
144    /**
145     * Move FormElement $element before $referenceElement.
146     *
147     * Both $element and $referenceElement must be direct descendants of this Section/Page.
148     *
149     * @param FormElementInterface $elementToMove
150     * @param FormElementInterface $referenceElement
151     */
152    public function moveElementBefore(FormElementInterface $elementToMove, FormElementInterface $referenceElement)
153    {
154        $this->moveRenderableBefore($elementToMove, $referenceElement);
155    }
156
157    /**
158     * Move FormElement $element after $referenceElement
159     *
160     * Both $element and $referenceElement must be direct descendants of this Section/Page.
161     *
162     * @param FormElementInterface $elementToMove
163     * @param FormElementInterface $referenceElement
164     */
165    public function moveElementAfter(FormElementInterface $elementToMove, FormElementInterface $referenceElement)
166    {
167        $this->moveRenderableAfter($elementToMove, $referenceElement);
168    }
169
170    /**
171     * Remove $elementToRemove from this Section/Page
172     *
173     * @param FormElementInterface $elementToRemove
174     */
175    public function removeElement(FormElementInterface $elementToRemove)
176    {
177        $this->removeRenderable($elementToRemove);
178    }
179}
180