4a1eb9f2878e847a4c9e65f9bbf33a2efa29c9ed
[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 use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
28
29 /**
30 * Convenience base class which implements common functionality for most
31 * classes which implement RenderableInterface.
32 *
33 * Scope: frontend
34 * **This class is NOT meant to be sub classed by developers.**
35 * @internal
36 */
37 abstract class AbstractRenderable implements RenderableInterface
38 {
39
40 /**
41 * Abstract "type" of this Renderable. Is used during the rendering process
42 * to determine the template file or the View PHP class being used to render
43 * the particular element.
44 *
45 * @var string
46 */
47 protected $type;
48
49 /**
50 * The identifier of this renderable
51 *
52 * @var string
53 */
54 protected $identifier;
55
56 /**
57 * The parent renderable
58 *
59 * @var CompositeRenderableInterface
60 */
61 protected $parentRenderable;
62
63 /**
64 * The label of this renderable
65 *
66 * @var string
67 */
68 protected $label = '';
69
70 /**
71 * associative array of rendering options
72 *
73 * @var array
74 */
75 protected $renderingOptions = [];
76
77 /**
78 * The position of this renderable inside the parent renderable.
79 *
80 * @var int
81 */
82 protected $index = 0;
83
84 /**
85 * The name of the template file of the renderable.
86 *
87 * @var string
88 */
89 protected $templateName = '';
90
91 /**
92 * Get the type of the renderable
93 *
94 * @return string
95 * @api
96 */
97 public function getType(): string
98 {
99 return $this->type;
100 }
101
102 /**
103 * Get the identifier of the element
104 *
105 * @return string
106 * @api
107 */
108 public function getIdentifier(): string
109 {
110 return $this->identifier;
111 }
112
113 /**
114 * Set multiple properties of this object at once.
115 * Every property which has a corresponding set* method can be set using
116 * the passed $options array.
117 *
118 * @param array $options
119 * @return void
120 * @api
121 */
122 public function setOptions(array $options)
123 {
124 if (isset($options['label'])) {
125 $this->setLabel($options['label']);
126 }
127
128 if (isset($options['defaultValue'])) {
129 $this->setDefaultValue($options['defaultValue']);
130 }
131
132 if (isset($options['properties'])) {
133 if (isset($options['properties']['placeholder'])) {
134 GeneralUtility::deprecationLog('EXT:form - "properties.placeholder" is deprecated since TYPO3 v8 and will be removed in TYPO3 v9. Use "properties.fluidAdditionalAttributes.placeholder."');
135 $options['properties']['fluidAdditionalAttributes']['placeholder'] = $options['properties']['placeholder'];
136 unset($options['properties']['placeholder']);
137 }
138
139 foreach ($options['properties'] as $key => $value) {
140 $this->setProperty($key, $value);
141 }
142 }
143
144 if (isset($options['renderingOptions'])) {
145 foreach ($options['renderingOptions'] as $key => $value) {
146 if (is_array($value)) {
147 $currentValue = isset($this->getRenderingOptions()[$key]) ? $this->getRenderingOptions()[$key] : [];
148 ArrayUtility::mergeRecursiveWithOverrule($currentValue, $value);
149 $this->setRenderingOption($key, $currentValue);
150 } else {
151 $this->setRenderingOption($key, $value);
152 }
153 }
154 }
155
156 if (isset($options['validators'])) {
157 foreach ($options['validators'] as $validatorConfiguration) {
158 $this->createValidator($validatorConfiguration['identifier'], isset($validatorConfiguration['options']) ? $validatorConfiguration['options'] : []);
159 }
160 }
161
162 ArrayUtility::assertAllArrayKeysAreValid(
163 $options,
164 ['label', 'defaultValue', 'properties', 'renderingOptions', 'validators', 'formEditor']
165 );
166 }
167
168 /**
169 * Create a validator for the element
170 *
171 * @param string $validatorIdentifier
172 * @param array $options
173 * @return mixed
174 * @throws ValidatorPresetNotFoundException
175 * @api
176 */
177 public function createValidator(string $validatorIdentifier, array $options = [])
178 {
179 $validatorsDefinition = $this->getRootForm()->getValidatorsDefinition();
180 if (isset($validatorsDefinition[$validatorIdentifier]) && is_array($validatorsDefinition[$validatorIdentifier]) && isset($validatorsDefinition[$validatorIdentifier]['implementationClassName'])) {
181 $implementationClassName = $validatorsDefinition[$validatorIdentifier]['implementationClassName'];
182 $defaultOptions = isset($validatorsDefinition[$validatorIdentifier]['options']) ? $validatorsDefinition[$validatorIdentifier]['options'] : [];
183
184 ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
185
186 $validator = GeneralUtility::makeInstance(ObjectManager::class)
187 ->get($implementationClassName, $defaultOptions);
188 $this->addValidator($validator);
189 return $validator;
190 } else {
191 throw new ValidatorPresetNotFoundException('The validator preset identified by "' . $validatorIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328710202);
192 }
193 }
194
195 /**
196 * Add a validator to the element
197 *
198 * @param ValidatorInterface $validator
199 * @return void
200 * @api
201 */
202 public function addValidator(ValidatorInterface $validator)
203 {
204 $formDefinition = $this->getRootForm();
205 $formDefinition->getProcessingRule($this->getIdentifier())->addValidator($validator);
206 }
207
208 /**
209 * Get all validators on the element
210 *
211 * @return \SplObjectStorage
212 * @internal
213 */
214 public function getValidators(): \SplObjectStorage
215 {
216 $formDefinition = $this->getRootForm();
217 return $formDefinition->getProcessingRule($this->getIdentifier())->getValidators();
218 }
219
220 /**
221 * Set the datatype
222 *
223 * @param string $dataType
224 * @return void
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 null|CompositeRenderableInterface
272 * @return void
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 * @return void
285 * @api
286 */
287 public function setParentRenderable(CompositeRenderableInterface $parentRenderable)
288 {
289 $this->parentRenderable = $parentRenderable;
290 $this->registerInFormIfPossible();
291 }
292
293 /**
294 * Get the root form this element belongs to
295 *
296 * @return FormDefinition
297 * @throws FormDefinitionConsistencyException
298 * @api
299 */
300 public function getRootForm(): FormDefinition
301 {
302 $rootRenderable = $this->parentRenderable;
303 while ($rootRenderable !== null && !($rootRenderable instanceof FormDefinition)) {
304 $rootRenderable = $rootRenderable->getParentRenderable();
305 }
306 if ($rootRenderable === null) {
307 throw new FormDefinitionConsistencyException(sprintf('The form element "%s" is not attached to a parent form.', $this->identifier), 1326803398);
308 }
309
310 return $rootRenderable;
311 }
312
313 /**
314 * Register this element at the parent form, if there is a connection to the parent form.
315 *
316 * @return void
317 * @internal
318 */
319 public function registerInFormIfPossible()
320 {
321 try {
322 $rootForm = $this->getRootForm();
323 $rootForm->registerRenderable($this);
324 } catch (FormDefinitionConsistencyException $exception) {
325 }
326 }
327
328 /**
329 * Triggered when the renderable is removed from it's parent
330 *
331 * @return void
332 * @internal
333 */
334 public function onRemoveFromParentRenderable()
335 {
336 try {
337 $rootForm = $this->getRootForm();
338 $rootForm->unregisterRenderable($this);
339 } catch (FormDefinitionConsistencyException $exception) {
340 }
341 $this->parentRenderable = null;
342 }
343
344 /**
345 * Get the index of the renderable
346 *
347 * @return int
348 * @internal
349 */
350 public function getIndex(): int
351 {
352 return $this->index;
353 }
354
355 /**
356 * Set the index of the renderable
357 *
358 * @param int $index
359 * @return void
360 * @internal
361 */
362 public function setIndex(int $index)
363 {
364 $this->index = $index;
365 }
366
367 /**
368 * Get the label of the renderable
369 *
370 * @return string
371 * @api
372 */
373 public function getLabel(): string
374 {
375 return $this->label;
376 }
377
378 /**
379 * Set the label which shall be displayed next to the form element
380 *
381 * @param string $label
382 * @return void
383 * @api
384 */
385 public function setLabel(string $label)
386 {
387 $this->label = $label;
388 }
389
390 /**
391 * Get the templateName name of the renderable
392 *
393 * @return string
394 * @api
395 */
396 public function getTemplateName(): string
397 {
398 return empty($this->renderingOptions['templateName'])
399 ? $this->type
400 : $this->renderingOptions['templateName'];
401 }
402
403 /**
404 * Override this method in your custom Renderable if needed
405 *
406 * @param FormRuntime $formRuntime
407 * @return void
408 * @api
409 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
410 */
411 public function beforeRendering(FormRuntime $formRuntime)
412 {
413 GeneralUtility::logDeprecatedFunction();
414 }
415
416 /**
417 * This is a callback that is invoked by the Form Factory after the whole form has been built.
418 * It can be used to add new form elements as children for complex form elements.
419 *
420 * Override this method in your custom Renderable if needed.
421 *
422 * @return void
423 * @api
424 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
425 */
426 public function onBuildingFinished()
427 {
428 }
429 }