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