2f1e603d61afa6ba0fdce27695348a9017cee97b
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Domain / Model / Renderable / AbstractRenderable.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Form\Domain\Model\Renderable;
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
20 use TYPO3\CMS\Core\Utility\ArrayUtility;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Extbase\Object\ObjectManager;
23 use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
24 use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
25 use TYPO3\CMS\Form\Domain\Model\Exception\ValidatorPresetNotFoundException;
26 use TYPO3\CMS\Form\Domain\Model\FormDefinition;
27
28 /**
29 * Convenience base class which implements common functionality for most
30 * classes which implement RenderableInterface.
31 *
32 * Scope: frontend
33 * **This class is NOT meant to be sub classed by developers.**
34 * @internal
35 */
36 abstract class AbstractRenderable implements RenderableInterface
37 {
38
39 /**
40 * Abstract "type" of this Renderable. Is used during the rendering process
41 * to determine the template file or the View PHP class being used to render
42 * the particular element.
43 *
44 * @var string
45 */
46 protected $type;
47
48 /**
49 * The identifier of this renderable
50 *
51 * @var string
52 */
53 protected $identifier;
54
55 /**
56 * The parent renderable
57 *
58 * @var CompositeRenderableInterface
59 */
60 protected $parentRenderable;
61
62 /**
63 * The label of this renderable
64 *
65 * @var string
66 */
67 protected $label = '';
68
69 /**
70 * associative array of rendering options
71 *
72 * @var array
73 */
74 protected $renderingOptions = [];
75
76 /**
77 * The position of this renderable inside the parent renderable.
78 *
79 * @var int
80 */
81 protected $index = 0;
82
83 /**
84 * The name of the template file of the renderable.
85 *
86 * @var string
87 */
88 protected $templateName = '';
89
90 /**
91 * Get the type of the renderable
92 *
93 * @return string
94 * @api
95 */
96 public function getType(): string
97 {
98 return $this->type;
99 }
100
101 /**
102 * Get the identifier of the element
103 *
104 * @return string
105 * @api
106 */
107 public function getIdentifier(): string
108 {
109 return $this->identifier;
110 }
111
112 /**
113 * Set the identifier of the element
114 *
115 * @param string $identifier
116 * @api
117 */
118 public function setIdentifier(string $identifier)
119 {
120 $this->identifier = $identifier;
121 }
122
123 /**
124 * Set multiple properties of this object at once.
125 * Every property which has a corresponding set* method can be set using
126 * the passed $options array.
127 *
128 * @param array $options
129 * @api
130 */
131 public function setOptions(array $options)
132 {
133 if (isset($options['label'])) {
134 $this->setLabel($options['label']);
135 }
136
137 if (isset($options['defaultValue'])) {
138 $this->setDefaultValue($options['defaultValue']);
139 }
140
141 if (isset($options['properties'])) {
142 foreach ($options['properties'] as $key => $value) {
143 $this->setProperty($key, $value);
144 }
145 }
146
147 if (isset($options['renderingOptions'])) {
148 foreach ($options['renderingOptions'] as $key => $value) {
149 if (is_array($value)) {
150 $currentValue = isset($this->getRenderingOptions()[$key]) ? $this->getRenderingOptions()[$key] : [];
151 ArrayUtility::mergeRecursiveWithOverrule($currentValue, $value);
152 $this->setRenderingOption($key, $currentValue);
153 } else {
154 $this->setRenderingOption($key, $value);
155 }
156 }
157 }
158
159 if (isset($options['validators'])) {
160 foreach ($options['validators'] as $validatorConfiguration) {
161 $this->createValidator($validatorConfiguration['identifier'], isset($validatorConfiguration['options']) ? $validatorConfiguration['options'] : []);
162 }
163 }
164
165 ArrayUtility::assertAllArrayKeysAreValid(
166 $options,
167 ['label', 'defaultValue', 'properties', 'renderingOptions', 'validators', 'formEditor']
168 );
169 }
170
171 /**
172 * Create a validator for the element
173 *
174 * @param string $validatorIdentifier
175 * @param array $options
176 * @return mixed
177 * @throws ValidatorPresetNotFoundException
178 * @api
179 */
180 public function createValidator(string $validatorIdentifier, array $options = [])
181 {
182 $validatorsDefinition = $this->getRootForm()->getValidatorsDefinition();
183 if (isset($validatorsDefinition[$validatorIdentifier]) && is_array($validatorsDefinition[$validatorIdentifier]) && isset($validatorsDefinition[$validatorIdentifier]['implementationClassName'])) {
184 $implementationClassName = $validatorsDefinition[$validatorIdentifier]['implementationClassName'];
185 $defaultOptions = isset($validatorsDefinition[$validatorIdentifier]['options']) ? $validatorsDefinition[$validatorIdentifier]['options'] : [];
186
187 ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
188
189 $validator = GeneralUtility::makeInstance(ObjectManager::class)
190 ->get($implementationClassName, $defaultOptions);
191 $this->addValidator($validator);
192 return $validator;
193 }
194 throw new ValidatorPresetNotFoundException('The validator preset identified by "' . $validatorIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328710202);
195 }
196
197 /**
198 * Add a validator to the element
199 *
200 * @param ValidatorInterface $validator
201 * @api
202 */
203 public function addValidator(ValidatorInterface $validator)
204 {
205 $formDefinition = $this->getRootForm();
206 $formDefinition->getProcessingRule($this->getIdentifier())->addValidator($validator);
207 }
208
209 /**
210 * Get all validators on the element
211 *
212 * @return \SplObjectStorage
213 * @internal
214 */
215 public function getValidators(): \SplObjectStorage
216 {
217 $formDefinition = $this->getRootForm();
218 return $formDefinition->getProcessingRule($this->getIdentifier())->getValidators();
219 }
220
221 /**
222 * Set the datatype
223 *
224 * @param string $dataType
225 * @api
226 */
227 public function setDataType(string $dataType)
228 {
229 $formDefinition = $this->getRootForm();
230 $formDefinition->getProcessingRule($this->getIdentifier())->setDataType($dataType);
231 }
232
233 /**
234 * Get the classname of the renderer
235 *
236 * @return string
237 * @api
238 */
239 public function getRendererClassName(): string
240 {
241 return $this->getRootForm()->getRendererClassName();
242 }
243
244 /**
245 * Get all rendering options
246 *
247 * @return array
248 * @api
249 */
250 public function getRenderingOptions(): array
251 {
252 return $this->renderingOptions;
253 }
254
255 /**
256 * Set the rendering option $key to $value.
257 *
258 * @param string $key
259 * @param mixed $value
260 * @return mixed
261 * @api
262 */
263 public function setRenderingOption(string $key, $value)
264 {
265 $this->renderingOptions[$key] = $value;
266 }
267
268 /**
269 * Get the parent renderable
270 *
271 * @return CompositeRenderableInterface|null
272 * @api
273 */
274 public function getParentRenderable()
275 {
276 return $this->parentRenderable;
277 }
278
279 /**
280 * Set the parent renderable
281 *
282 * @param CompositeRenderableInterface $parentRenderable
283 * @api
284 */
285 public function setParentRenderable(CompositeRenderableInterface $parentRenderable)
286 {
287 $this->parentRenderable = $parentRenderable;
288 $this->registerInFormIfPossible();
289 }
290
291 /**
292 * Get the root form this element belongs to
293 *
294 * @return FormDefinition
295 * @throws FormDefinitionConsistencyException
296 * @api
297 */
298 public function getRootForm(): FormDefinition
299 {
300 $rootRenderable = $this->parentRenderable;
301 while ($rootRenderable !== null && !($rootRenderable instanceof FormDefinition)) {
302 $rootRenderable = $rootRenderable->getParentRenderable();
303 }
304 if ($rootRenderable === null) {
305 throw new FormDefinitionConsistencyException(sprintf('The form element "%s" is not attached to a parent form.', $this->identifier), 1326803398);
306 }
307
308 return $rootRenderable;
309 }
310
311 /**
312 * Register this element at the parent form, if there is a connection to the parent form.
313 *
314 * @internal
315 */
316 public function registerInFormIfPossible()
317 {
318 try {
319 $rootForm = $this->getRootForm();
320 $rootForm->registerRenderable($this);
321 } catch (FormDefinitionConsistencyException $exception) {
322 }
323 }
324
325 /**
326 * Triggered when the renderable is removed from it's parent
327 *
328 * @internal
329 */
330 public function onRemoveFromParentRenderable()
331 {
332 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRemoveFromParentRenderable'] ?? [] as $className) {
333 $hookObj = GeneralUtility::makeInstance($className);
334 if (method_exists($hookObj, 'beforeRemoveFromParentRenderable')) {
335 $hookObj->beforeRemoveFromParentRenderable(
336 $this
337 );
338 }
339 }
340
341 try {
342 $rootForm = $this->getRootForm();
343 $rootForm->unregisterRenderable($this);
344 } catch (FormDefinitionConsistencyException $exception) {
345 }
346 $this->parentRenderable = null;
347 }
348
349 /**
350 * Get the index of the renderable
351 *
352 * @return int
353 * @internal
354 */
355 public function getIndex(): int
356 {
357 return $this->index;
358 }
359
360 /**
361 * Set the index of the renderable
362 *
363 * @param int $index
364 * @internal
365 */
366 public function setIndex(int $index)
367 {
368 $this->index = $index;
369 }
370
371 /**
372 * Get the label of the renderable
373 *
374 * @return string
375 * @api
376 */
377 public function getLabel(): string
378 {
379 return $this->label;
380 }
381
382 /**
383 * Set the label which shall be displayed next to the form element
384 *
385 * @param string $label
386 * @api
387 */
388 public function setLabel(string $label)
389 {
390 $this->label = $label;
391 }
392
393 /**
394 * Get the templateName name of the renderable
395 *
396 * @return string
397 * @api
398 */
399 public function getTemplateName(): string
400 {
401 return empty($this->renderingOptions['templateName'])
402 ? $this->type
403 : $this->renderingOptions['templateName'];
404 }
405 }