f1b1298249af84c2731814d6d000cc8638e58f45
[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 } else {
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 /**
199 * Add a validator to the element
200 *
201 * @param ValidatorInterface $validator
202 * @api
203 */
204 public function addValidator(ValidatorInterface $validator)
205 {
206 $formDefinition = $this->getRootForm();
207 $formDefinition->getProcessingRule($this->getIdentifier())->addValidator($validator);
208 }
209
210 /**
211 * Get all validators on the element
212 *
213 * @return \SplObjectStorage
214 * @internal
215 */
216 public function getValidators(): \SplObjectStorage
217 {
218 $formDefinition = $this->getRootForm();
219 return $formDefinition->getProcessingRule($this->getIdentifier())->getValidators();
220 }
221
222 /**
223 * Set the datatype
224 *
225 * @param string $dataType
226 * @api
227 */
228 public function setDataType(string $dataType)
229 {
230 $formDefinition = $this->getRootForm();
231 $formDefinition->getProcessingRule($this->getIdentifier())->setDataType($dataType);
232 }
233
234 /**
235 * Get the classname of the renderer
236 *
237 * @return string
238 * @api
239 */
240 public function getRendererClassName(): string
241 {
242 return $this->getRootForm()->getRendererClassName();
243 }
244
245 /**
246 * Get all rendering options
247 *
248 * @return array
249 * @api
250 */
251 public function getRenderingOptions(): array
252 {
253 return $this->renderingOptions;
254 }
255
256 /**
257 * Set the rendering option $key to $value.
258 *
259 * @param string $key
260 * @param mixed $value
261 * @return mixed
262 * @api
263 */
264 public function setRenderingOption(string $key, $value)
265 {
266 $this->renderingOptions[$key] = $value;
267 }
268
269 /**
270 * Get the parent renderable
271 *
272 * @return null|CompositeRenderableInterface
273 * @api
274 */
275 public function getParentRenderable()
276 {
277 return $this->parentRenderable;
278 }
279
280 /**
281 * Set the parent renderable
282 *
283 * @param CompositeRenderableInterface $parentRenderable
284 * @api
285 */
286 public function setParentRenderable(CompositeRenderableInterface $parentRenderable)
287 {
288 $this->parentRenderable = $parentRenderable;
289 $this->registerInFormIfPossible();
290 }
291
292 /**
293 * Get the root form this element belongs to
294 *
295 * @return FormDefinition
296 * @throws FormDefinitionConsistencyException
297 * @api
298 */
299 public function getRootForm(): FormDefinition
300 {
301 $rootRenderable = $this->parentRenderable;
302 while ($rootRenderable !== null && !($rootRenderable instanceof FormDefinition)) {
303 $rootRenderable = $rootRenderable->getParentRenderable();
304 }
305 if ($rootRenderable === null) {
306 throw new FormDefinitionConsistencyException(sprintf('The form element "%s" is not attached to a parent form.', $this->identifier), 1326803398);
307 }
308
309 return $rootRenderable;
310 }
311
312 /**
313 * Register this element at the parent form, if there is a connection to the parent form.
314 *
315 * @internal
316 */
317 public function registerInFormIfPossible()
318 {
319 try {
320 $rootForm = $this->getRootForm();
321 $rootForm->registerRenderable($this);
322 } catch (FormDefinitionConsistencyException $exception) {
323 }
324 }
325
326 /**
327 * Triggered when the renderable is removed from it's parent
328 *
329 * @internal
330 */
331 public function onRemoveFromParentRenderable()
332 {
333 if (
334 isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRemoveFromParentRenderable'])
335 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRemoveFromParentRenderable'])
336 ) {
337 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRemoveFromParentRenderable'] as $className) {
338 $hookObj = GeneralUtility::makeInstance($className);
339 if (method_exists($hookObj, 'beforeRemoveFromParentRenderable')) {
340 $hookObj->beforeRemoveFromParentRenderable(
341 $this
342 );
343 }
344 }
345 }
346
347 try {
348 $rootForm = $this->getRootForm();
349 $rootForm->unregisterRenderable($this);
350 } catch (FormDefinitionConsistencyException $exception) {
351 }
352 $this->parentRenderable = null;
353 }
354
355 /**
356 * Get the index of the renderable
357 *
358 * @return int
359 * @internal
360 */
361 public function getIndex(): int
362 {
363 return $this->index;
364 }
365
366 /**
367 * Set the index of the renderable
368 *
369 * @param int $index
370 * @internal
371 */
372 public function setIndex(int $index)
373 {
374 $this->index = $index;
375 }
376
377 /**
378 * Get the label of the renderable
379 *
380 * @return string
381 * @api
382 */
383 public function getLabel(): string
384 {
385 return $this->label;
386 }
387
388 /**
389 * Set the label which shall be displayed next to the form element
390 *
391 * @param string $label
392 * @api
393 */
394 public function setLabel(string $label)
395 {
396 $this->label = $label;
397 }
398
399 /**
400 * Get the templateName name of the renderable
401 *
402 * @return string
403 * @api
404 */
405 public function getTemplateName(): string
406 {
407 return empty($this->renderingOptions['templateName'])
408 ? $this->type
409 : $this->renderingOptions['templateName'];
410 }
411 }