[TASK] EXT:form - set identifier for renderables
[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 the identifier of the element
115 *
116 * @param string $identifier
117 * @return void
118 * @api
119 */
120 public function setIdentifier(string $identifier)
121 {
122 $this->identifier = $identifier;
123 }
124
125 /**
126 * Set multiple properties of this object at once.
127 * Every property which has a corresponding set* method can be set using
128 * the passed $options array.
129 *
130 * @param array $options
131 * @return void
132 * @api
133 */
134 public function setOptions(array $options)
135 {
136 if (isset($options['label'])) {
137 $this->setLabel($options['label']);
138 }
139
140 if (isset($options['defaultValue'])) {
141 $this->setDefaultValue($options['defaultValue']);
142 }
143
144 if (isset($options['properties'])) {
145 if (isset($options['properties']['placeholder'])) {
146 GeneralUtility::deprecationLog('EXT:form - "properties.placeholder" is deprecated since TYPO3 v8 and will be removed in TYPO3 v9. Use "properties.fluidAdditionalAttributes.placeholder."');
147 $options['properties']['fluidAdditionalAttributes']['placeholder'] = $options['properties']['placeholder'];
148 unset($options['properties']['placeholder']);
149 }
150
151 foreach ($options['properties'] as $key => $value) {
152 $this->setProperty($key, $value);
153 }
154 }
155
156 if (isset($options['renderingOptions'])) {
157 foreach ($options['renderingOptions'] as $key => $value) {
158 if (is_array($value)) {
159 $currentValue = isset($this->getRenderingOptions()[$key]) ? $this->getRenderingOptions()[$key] : [];
160 ArrayUtility::mergeRecursiveWithOverrule($currentValue, $value);
161 $this->setRenderingOption($key, $currentValue);
162 } else {
163 $this->setRenderingOption($key, $value);
164 }
165 }
166 }
167
168 if (isset($options['validators'])) {
169 foreach ($options['validators'] as $validatorConfiguration) {
170 $this->createValidator($validatorConfiguration['identifier'], isset($validatorConfiguration['options']) ? $validatorConfiguration['options'] : []);
171 }
172 }
173
174 ArrayUtility::assertAllArrayKeysAreValid(
175 $options,
176 ['label', 'defaultValue', 'properties', 'renderingOptions', 'validators', 'formEditor']
177 );
178 }
179
180 /**
181 * Create a validator for the element
182 *
183 * @param string $validatorIdentifier
184 * @param array $options
185 * @return mixed
186 * @throws ValidatorPresetNotFoundException
187 * @api
188 */
189 public function createValidator(string $validatorIdentifier, array $options = [])
190 {
191 $validatorsDefinition = $this->getRootForm()->getValidatorsDefinition();
192 if (isset($validatorsDefinition[$validatorIdentifier]) && is_array($validatorsDefinition[$validatorIdentifier]) && isset($validatorsDefinition[$validatorIdentifier]['implementationClassName'])) {
193 $implementationClassName = $validatorsDefinition[$validatorIdentifier]['implementationClassName'];
194 $defaultOptions = isset($validatorsDefinition[$validatorIdentifier]['options']) ? $validatorsDefinition[$validatorIdentifier]['options'] : [];
195
196 ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
197
198 $validator = GeneralUtility::makeInstance(ObjectManager::class)
199 ->get($implementationClassName, $defaultOptions);
200 $this->addValidator($validator);
201 return $validator;
202 } else {
203 throw new ValidatorPresetNotFoundException('The validator preset identified by "' . $validatorIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328710202);
204 }
205 }
206
207 /**
208 * Add a validator to the element
209 *
210 * @param ValidatorInterface $validator
211 * @return void
212 * @api
213 */
214 public function addValidator(ValidatorInterface $validator)
215 {
216 $formDefinition = $this->getRootForm();
217 $formDefinition->getProcessingRule($this->getIdentifier())->addValidator($validator);
218 }
219
220 /**
221 * Get all validators on the element
222 *
223 * @return \SplObjectStorage
224 * @internal
225 */
226 public function getValidators(): \SplObjectStorage
227 {
228 $formDefinition = $this->getRootForm();
229 return $formDefinition->getProcessingRule($this->getIdentifier())->getValidators();
230 }
231
232 /**
233 * Set the datatype
234 *
235 * @param string $dataType
236 * @return void
237 * @api
238 */
239 public function setDataType(string $dataType)
240 {
241 $formDefinition = $this->getRootForm();
242 $formDefinition->getProcessingRule($this->getIdentifier())->setDataType($dataType);
243 }
244
245 /**
246 * Get the classname of the renderer
247 *
248 * @return string
249 * @api
250 */
251 public function getRendererClassName(): string
252 {
253 return $this->getRootForm()->getRendererClassName();
254 }
255
256 /**
257 * Get all rendering options
258 *
259 * @return array
260 * @api
261 */
262 public function getRenderingOptions(): array
263 {
264 return $this->renderingOptions;
265 }
266
267 /**
268 * Set the rendering option $key to $value.
269 *
270 * @param string $key
271 * @param mixed $value
272 * @return mixed
273 * @api
274 */
275 public function setRenderingOption(string $key, $value)
276 {
277 $this->renderingOptions[$key] = $value;
278 }
279
280 /**
281 * Get the parent renderable
282 *
283 * @return null|CompositeRenderableInterface
284 * @return void
285 * @api
286 */
287 public function getParentRenderable()
288 {
289 return $this->parentRenderable;
290 }
291
292 /**
293 * Set the parent renderable
294 *
295 * @param CompositeRenderableInterface $parentRenderable
296 * @return void
297 * @api
298 */
299 public function setParentRenderable(CompositeRenderableInterface $parentRenderable)
300 {
301 $this->parentRenderable = $parentRenderable;
302 $this->registerInFormIfPossible();
303 }
304
305 /**
306 * Get the root form this element belongs to
307 *
308 * @return FormDefinition
309 * @throws FormDefinitionConsistencyException
310 * @api
311 */
312 public function getRootForm(): FormDefinition
313 {
314 $rootRenderable = $this->parentRenderable;
315 while ($rootRenderable !== null && !($rootRenderable instanceof FormDefinition)) {
316 $rootRenderable = $rootRenderable->getParentRenderable();
317 }
318 if ($rootRenderable === null) {
319 throw new FormDefinitionConsistencyException(sprintf('The form element "%s" is not attached to a parent form.', $this->identifier), 1326803398);
320 }
321
322 return $rootRenderable;
323 }
324
325 /**
326 * Register this element at the parent form, if there is a connection to the parent form.
327 *
328 * @return void
329 * @internal
330 */
331 public function registerInFormIfPossible()
332 {
333 try {
334 $rootForm = $this->getRootForm();
335 $rootForm->registerRenderable($this);
336 } catch (FormDefinitionConsistencyException $exception) {
337 }
338 }
339
340 /**
341 * Triggered when the renderable is removed from it's parent
342 *
343 * @return void
344 * @internal
345 */
346 public function onRemoveFromParentRenderable()
347 {
348 try {
349 $rootForm = $this->getRootForm();
350 $rootForm->unregisterRenderable($this);
351 } catch (FormDefinitionConsistencyException $exception) {
352 }
353 $this->parentRenderable = null;
354 }
355
356 /**
357 * Get the index of the renderable
358 *
359 * @return int
360 * @internal
361 */
362 public function getIndex(): int
363 {
364 return $this->index;
365 }
366
367 /**
368 * Set the index of the renderable
369 *
370 * @param int $index
371 * @return void
372 * @internal
373 */
374 public function setIndex(int $index)
375 {
376 $this->index = $index;
377 }
378
379 /**
380 * Get the label of the renderable
381 *
382 * @return string
383 * @api
384 */
385 public function getLabel(): string
386 {
387 return $this->label;
388 }
389
390 /**
391 * Set the label which shall be displayed next to the form element
392 *
393 * @param string $label
394 * @return void
395 * @api
396 */
397 public function setLabel(string $label)
398 {
399 $this->label = $label;
400 }
401
402 /**
403 * Get the templateName name of the renderable
404 *
405 * @return string
406 * @api
407 */
408 public function getTemplateName(): string
409 {
410 return empty($this->renderingOptions['templateName'])
411 ? $this->type
412 : $this->renderingOptions['templateName'];
413 }
414
415 /**
416 * Override this method in your custom Renderable if needed
417 *
418 * @param FormRuntime $formRuntime
419 * @return void
420 * @api
421 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
422 */
423 public function beforeRendering(FormRuntime $formRuntime)
424 {
425 GeneralUtility::logDeprecatedFunction();
426 }
427
428 /**
429 * This is a callback that is invoked by the Form Factory after the whole form has been built.
430 * It can be used to add new form elements as children for complex form elements.
431 *
432 * Override this method in your custom Renderable if needed.
433 *
434 * @return void
435 * @api
436 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
437 */
438 public function onBuildingFinished()
439 {
440 }
441 }