Passed
Push — master ( 5527b0...962042 )
by
unknown
13:01
created

FormDefinition::addPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
/*
19
 * Inspired by and partially taken from the Neos.Form package (www.neos.io)
20
 */
21
22
namespace TYPO3\CMS\Form\Domain\Model;
23
24
use Psr\Http\Message\ResponseInterface;
25
use TYPO3\CMS\Core\Utility\ArrayUtility;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Extbase\Mvc\Request;
28
use TYPO3\CMS\Extbase\Object\ObjectManager;
29
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
30
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
31
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
32
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotFoundException;
33
use TYPO3\CMS\Form\Domain\Finishers\FinisherInterface;
34
use TYPO3\CMS\Form\Domain\Model\Exception\DuplicateFormElementException;
35
use TYPO3\CMS\Form\Domain\Model\Exception\FinisherPresetNotFoundException;
36
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
37
use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
38
use TYPO3\CMS\Form\Domain\Model\FormElements\Page;
39
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractCompositeRenderable;
40
use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
41
use TYPO3\CMS\Form\Domain\Model\Renderable\VariableRenderableInterface;
42
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
43
use TYPO3\CMS\Form\Exception as FormException;
44
use TYPO3\CMS\Form\Mvc\ProcessingRule;
45
46
/**
47
 * This class encapsulates a complete *Form Definition*, with all of its pages,
48
 * form elements, validation rules which apply and finishers which should be
49
 * executed when the form is completely filled in.
50
 *
51
 * It is *not modified* when the form executes.
52
 *
53
 * The Anatomy Of A Form
54
 * =====================
55
 *
56
 * A FormDefinition consists of multiple *Page* ({@link Page}) objects. When a
57
 * form is displayed to the user, only one *Page* is visible at any given time,
58
 * and there is a navigation to go back and forth between the pages.
59
 *
60
 * A *Page* consists of multiple *FormElements* ({@link FormElementInterface}, {@link AbstractFormElement}),
61
 * which represent the input fields, textareas, checkboxes shown inside the page.
62
 *
63
 * *FormDefinition*, *Page* and *FormElement* have *identifier* properties, which
64
 * must be unique for each given type (i.e. it is allowed that the FormDefinition and
65
 * a FormElement have the *same* identifier, but two FormElements are not allowed to
66
 * have the same identifier.
67
 *
68
 * Simple Example
69
 * --------------
70
 *
71
 * Generally, you can create a FormDefinition manually by just calling the API
72
 * methods on it, or you use a *Form Definition Factory* to build the form from
73
 * another representation format such as YAML.
74
 *
75
 * /---code php
76
 * $formDefinition = GeneralUtility::makeInstance(FormDefinition::class, 'myForm');
77
 *
78
 * $page1 = GeneralUtility::makeInstance(Page::class, 'page1');
79
 * $formDefinition->addPage($page);
80
 *
81
 * $element1 = GeneralUtility::makeInstance(GenericFormElement::class, 'title', 'Textfield'); # the second argument is the type of the form element
82
 * $page1->addElement($element1);
83
 * \---
84
 *
85
 * Creating a Form, Using Abstract Form Element Types
86
 * =====================================================
87
 *
88
 * While you can use the {@link FormDefinition::addPage} or {@link Page::addElement}
89
 * methods and create the Page and FormElement objects manually, it is often better
90
 * to use the corresponding create* methods ({@link FormDefinition::createPage}
91
 * and {@link Page::createElement}), as you pass them an abstract *Form Element Type*
92
 * such as *Text* or *Page*, and the system **automatically
93
 * resolves the implementation class name and sets default values**.
94
 *
95
 * So the simple example from above should be rewritten as follows:
96
 *
97
 * /---code php
98
 * $prototypeConfiguration = []; // We'll talk about this later
99
 *
100
 * $formDefinition = GeneralUtility::makeInstance(FormDefinition::class, 'myForm', $prototypeConfiguration);
101
 * $page1 = $formDefinition->createPage('page1');
102
 * $element1 = $page1->addElement('title', 'Textfield');
103
 * \---
104
 *
105
 * Now, you might wonder how the system knows that the element *Textfield*
106
 * is implemented using a GenericFormElement: **This is configured in the $prototypeConfiguration**.
107
 *
108
 * To make the example from above actually work, we need to add some sensible
109
 * values to *$prototypeConfiguration*:
110
 *
111
 * <pre>
112
 * $prototypeConfiguration = [
113
 *   'formElementsDefinition' => [
114
 *     'Page' => [
115
 *       'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page'
116
 *     ],
117
 *     'Textfield' => [
118
 *       'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
119
 *     ]
120
 *   ]
121
 * ]
122
 * </pre>
123
 *
124
 * For each abstract *Form Element Type* we add some configuration; in the above
125
 * case only the *implementation class name*. Still, it is possible to set defaults
126
 * for *all* configuration options of such an element, as the following example
127
 * shows:
128
 *
129
 * <pre>
130
 * $prototypeConfiguration = [
131
 *   'formElementsDefinition' => [
132
 *     'Page' => [
133
 *       'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page',
134
 *       'label' => 'this is the label of the page if nothing is specified'
135
 *     ],
136
 *     'Textfield' => [
137
 *       'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement',
138
 *       'label' = >'Default Label',
139
 *       'defaultValue' => 'Default form element value',
140
 *       'properties' => [
141
 *         'placeholder' => 'Text which is shown if element is empty'
142
 *       ]
143
 *     ]
144
 *   ]
145
 * ]
146
 * </pre>
147
 *
148
 * Using Preconfigured $prototypeConfiguration
149
 * ---------------------------------
150
 *
151
 * Often, it is not really useful to manually create the $prototypeConfiguration array.
152
 *
153
 * Most of it comes pre-configured inside the YAML settings of the extensions,
154
 * and the {@link \TYPO3\CMS\Form\Domain\Configuration\ConfigurationService} contains helper methods
155
 * which return the ready-to-use *$prototypeConfiguration*.
156
 *
157
 * Property Mapping and Validation Rules
158
 * =====================================
159
 *
160
 * Besides Pages and FormElements, the FormDefinition can contain information
161
 * about the *format of the data* which is inputted into the form. This generally means:
162
 *
163
 * - expected Data Types
164
 * - Property Mapping Configuration to be used
165
 * - Validation Rules which should apply
166
 *
167
 * Background Info
168
 * ---------------
169
 * You might wonder why Data Types and Validation Rules are *not attached
170
 * to each FormElement itself*.
171
 *
172
 * If the form should create a *hierarchical output structure* such as a multi-
173
 * dimensional array or a PHP object, your expected data structure might look as follows:
174
 * <pre>
175
 * - person
176
 * -- firstName
177
 * -- lastName
178
 * -- address
179
 * --- street
180
 * --- city
181
 * </pre>
182
 *
183
 * Now, let's imagine you want to edit *person.address.street* and *person.address.city*,
184
 * but want to validate that the *combination* of *street* and *city* is valid
185
 * according to some address database.
186
 *
187
 * In this case, the form elements would be configured to fill *street* and *city*,
188
 * but the *validator* needs to be attached to the *compound object* *address*,
189
 * as both parts need to be validated together.
190
 *
191
 * Connecting FormElements to the output data structure
192
 * ====================================================
193
 *
194
 * The *identifier* of the *FormElement* is most important, as it determines
195
 * where in the output structure the value which is entered by the user is placed,
196
 * and thus also determines which validation rules need to apply.
197
 *
198
 * Using the above example, if you want to create a FormElement for the *street*,
199
 * you should use the identifier *person.address.street*.
200
 *
201
 * Rendering a FormDefinition
202
 * ==========================
203
 *
204
 * In order to trigger *rendering* on a FormDefinition,
205
 * the current {@link \TYPO3\CMS\Extbase\Mvc\Request} needs to be bound to the FormDefinition,
206
 * resulting in a {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime} object which contains the *Runtime State* of the form
207
 * (such as the currently inserted values).
208
 *
209
 * /---code php
210
 * # $currentRequest and $currentResponse need to be available, f.e. inside a controller you would
211
 * # use $this->request and $this->response; inside a ViewHelper you would use $this->controllerContext->getRequest()
212
 * # and $this->controllerContext->getResponse()
213
 * $form = $formDefinition->bind($currentRequest, $currentResponse);
214
 *
215
 * # now, you can use the $form object to get information about the currently
216
 * # entered values into the form, etc.
217
 * \---
218
 *
219
 * Refer to the {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime} API doc for further information.
220
 *
221
 * Scope: frontend
222
 * **This class is NOT meant to be sub classed by developers.**
223
 */
224
class FormDefinition extends AbstractCompositeRenderable implements VariableRenderableInterface
225
{
226
    /**
227
     * The finishers for this form
228
     *
229
     * @var \TYPO3\CMS\Form\Domain\Finishers\FinisherInterface[]
230
     */
231
    protected $finishers = [];
232
233
    /**
234
     * Property Mapping Rules, indexed by element identifier
235
     *
236
     * @var \TYPO3\CMS\Form\Mvc\ProcessingRule[]
237
     */
238
    protected $processingRules = [];
239
240
    /**
241
     * Contains all elements of the form, indexed by identifier.
242
     * Is used as internal cache as we need this really often.
243
     *
244
     * @var \TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface[]
245
     */
246
    protected $elementsByIdentifier = [];
247
248
    /**
249
     * Form element default values in the format ['elementIdentifier' => 'default value']
250
     *
251
     * @var array
252
     */
253
    protected $elementDefaultValues = [];
254
255
    /**
256
     * Renderer class name to be used.
257
     *
258
     * @var string
259
     */
260
    protected $rendererClassName = '';
261
262
    /**
263
     * @var array
264
     */
265
    protected $typeDefinitions;
266
267
    /**
268
     * @var array
269
     */
270
    protected $validatorsDefinition;
271
272
    /**
273
     * @var array
274
     */
275
    protected $finishersDefinition;
276
277
    /**
278
     * @var array
279
     */
280
    protected $conditionContextDefinition;
281
282
    /**
283
     * The persistence identifier of the form
284
     *
285
     * @var string
286
     */
287
    protected $persistenceIdentifier;
288
289
    /**
290
     * Constructor. Creates a new FormDefinition with the given identifier.
291
     *
292
     * @param string $identifier The Form Definition's identifier, must be a non-empty string.
293
     * @param array $prototypeConfiguration overrides form defaults of this definition
294
     * @param string $type element type of this form
295
     * @param string $persistenceIdentifier the persistence identifier of the form
296
     * @throws IdentifierNotValidException if the identifier was not valid
297
     */
298
    public function __construct(
299
        string $identifier,
300
        array $prototypeConfiguration = [],
301
        string $type = 'Form',
302
        string $persistenceIdentifier = null
303
    ) {
304
        $this->typeDefinitions = $prototypeConfiguration['formElementsDefinition'] ?? [];
305
        $this->validatorsDefinition = $prototypeConfiguration['validatorsDefinition'] ?? [];
306
        $this->finishersDefinition = $prototypeConfiguration['finishersDefinition'] ?? [];
307
        $this->conditionContextDefinition = $prototypeConfiguration['conditionContextDefinition'] ?? [];
308
309
        if (!is_string($identifier) || strlen($identifier) === 0) {
0 ignored issues
show
introduced by
The condition is_string($identifier) is always true.
Loading history...
310
            throw new IdentifierNotValidException('The given identifier was not a string or the string was empty.', 1477082503);
311
        }
312
313
        $this->identifier = $identifier;
314
        $this->type = $type;
315
        $this->persistenceIdentifier = (string)$persistenceIdentifier;
316
317
        if ($prototypeConfiguration !== []) {
318
            $this->initializeFromFormDefaults();
319
        }
320
    }
321
322
    /**
323
     * Initialize the form defaults of the current type
324
     *
325
     * @throws TypeDefinitionNotFoundException
326
     * @internal
327
     */
328
    protected function initializeFromFormDefaults()
329
    {
330
        if (!isset($this->typeDefinitions[$this->type])) {
331
            throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $this->type), 1474905835);
332
        }
333
        $typeDefinition = $this->typeDefinitions[$this->type];
334
        $this->setOptions($typeDefinition);
335
    }
336
337
    /**
338
     * Set multiple properties of this object at once.
339
     * Every property which has a corresponding set* method can be set using
340
     * the passed $options array.
341
     *
342
     * @param array $options
343
     * @param bool $resetFinishers
344
     * @internal
345
     */
346
    public function setOptions(array $options, bool $resetFinishers = false)
347
    {
348
        if (isset($options['rendererClassName'])) {
349
            $this->setRendererClassName($options['rendererClassName']);
350
        }
351
        if (isset($options['label'])) {
352
            $this->setLabel($options['label']);
353
        }
354
        if (isset($options['renderingOptions'])) {
355
            foreach ($options['renderingOptions'] as $key => $value) {
356
                $this->setRenderingOption($key, $value);
357
            }
358
        }
359
        if (isset($options['finishers'])) {
360
            if ($resetFinishers) {
361
                $this->finishers = [];
362
            }
363
            foreach ($options['finishers'] as $finisherConfiguration) {
364
                $this->createFinisher($finisherConfiguration['identifier'], $finisherConfiguration['options'] ?? []);
365
            }
366
        }
367
368
        if (isset($options['variants'])) {
369
            foreach ($options['variants'] as $variantConfiguration) {
370
                $this->createVariant($variantConfiguration);
371
            }
372
        }
373
374
        ArrayUtility::assertAllArrayKeysAreValid(
375
            $options,
376
            ['rendererClassName', 'renderingOptions', 'finishers', 'formEditor', 'label', 'variants']
377
        );
378
    }
379
380
    /**
381
     * Create a page with the given $identifier and attach this page to the form.
382
     *
383
     * - Create Page object based on the given $typeName
384
     * - set defaults inside the Page object
385
     * - attach Page object to this form
386
     * - return the newly created Page object
387
     *
388
     * @param string $identifier Identifier of the new page
389
     * @param string $typeName Type of the new page
390
     * @return Page the newly created page
391
     * @throws TypeDefinitionNotFoundException
392
     */
393
    public function createPage(string $identifier, string $typeName = 'Page'): Page
394
    {
395
        if (!isset($this->typeDefinitions[$typeName])) {
396
            throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $typeName), 1474905953);
397
        }
398
399
        $typeDefinition = $this->typeDefinitions[$typeName];
400
401
        if (!isset($typeDefinition['implementationClassName'])) {
402
            throw new TypeDefinitionNotFoundException(sprintf('The "implementationClassName" was not set in type definition "%s".', $typeName), 1477083126);
403
        }
404
        $implementationClassName = $typeDefinition['implementationClassName'];
405
406
        $classSchema = GeneralUtility::makeInstance(ReflectionService::class)->getClassSchema($implementationClassName);
407
        if ($classSchema->hasInjectMethods() || $classSchema->hasInjectProperties() || $classSchema->hasMethod('initializeObject')) {
408
            // @deprecated since v11, will be removed in v12 - Fallback for Page implementations that have
409
            // inject* or initializeObject methods, since Page prototype needs manual constructor arguments
410
            // which can't be mixed with injection in symfony DI. Deprecation is logged by ObjectManager->get().
411
            // Drop everything except the makeInstance call below in v12.
412
            $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
413
            /** @var Page $page */
414
            $page = $objectManager->get($implementationClassName, $identifier, $typeName);
415
        } else {
416
            /** @var Page $page */
417
            $page = GeneralUtility::makeInstance($implementationClassName, $identifier, $typeName);
418
        }
419
420
        if (isset($typeDefinition['label'])) {
421
            $page->setLabel($typeDefinition['label']);
422
        }
423
424
        if (isset($typeDefinition['renderingOptions'])) {
425
            foreach ($typeDefinition['renderingOptions'] as $key => $value) {
426
                $page->setRenderingOption($key, $value);
427
            }
428
        }
429
430
        ArrayUtility::assertAllArrayKeysAreValid(
431
            $typeDefinition,
432
            ['implementationClassName', 'label', 'renderingOptions', 'formEditor']
433
        );
434
435
        $this->addPage($page);
436
        return $page;
437
    }
438
439
    /**
440
     * Add a new page at the end of the form.
441
     *
442
     * Instead of this method, you should often use {@link createPage} instead.
443
     *
444
     * @param Page $page
445
     * @throws FormDefinitionConsistencyException if Page is already added to a FormDefinition
446
     * @see createPage
447
     */
448
    public function addPage(Page $page)
449
    {
450
        $this->addRenderable($page);
451
    }
452
453
    /**
454
     * Get the Form's pages
455
     *
456
     * @return array|Page[] The Form's pages in the correct order
457
     */
458
    public function getPages(): array
459
    {
460
        return $this->renderables;
461
    }
462
463
    /**
464
     * Check whether a page with the given $index exists
465
     *
466
     * @param int $index
467
     * @return bool TRUE if a page with the given $index exists, otherwise FALSE
468
     */
469
    public function hasPageWithIndex(int $index): bool
470
    {
471
        return isset($this->renderables[$index]);
472
    }
473
474
    /**
475
     * Get the page with the passed index. The first page has index zero.
476
     *
477
     * If page at $index does not exist, an exception is thrown. @see hasPageWithIndex()
478
     *
479
     * @param int $index
480
     * @return Page the page, or NULL if none found.
481
     * @throws FormException if the specified index does not exist
482
     */
483
    public function getPageByIndex(int $index)
484
    {
485
        if (!$this->hasPageWithIndex($index)) {
486
            throw new FormException(sprintf('There is no page with an index of %d', $index), 1329233627);
487
        }
488
        return $this->renderables[$index];
489
    }
490
491
    /**
492
     * Adds the specified finisher to this form
493
     *
494
     * @param FinisherInterface $finisher
495
     */
496
    public function addFinisher(FinisherInterface $finisher)
497
    {
498
        $this->finishers[] = $finisher;
499
    }
500
501
    /**
502
     * @param string $finisherIdentifier identifier of the finisher as registered in the current form (for example: "Redirect")
503
     * @param array $options options for this finisher in the format ['option1' => 'value1', 'option2' => 'value2', ...]
504
     * @return FinisherInterface
505
     * @throws FinisherPresetNotFoundException
506
     */
507
    public function createFinisher(string $finisherIdentifier, array $options = []): FinisherInterface
508
    {
509
        if (isset($this->finishersDefinition[$finisherIdentifier]) && is_array($this->finishersDefinition[$finisherIdentifier]) && isset($this->finishersDefinition[$finisherIdentifier]['implementationClassName'])) {
510
            $implementationClassName = $this->finishersDefinition[$finisherIdentifier]['implementationClassName'];
511
            $defaultOptions = $this->finishersDefinition[$finisherIdentifier]['options'] ?? [];
512
            ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
513
514
            $classSchema = GeneralUtility::makeInstance(ReflectionService::class)->getClassSchema($implementationClassName);
515
            if (!$classSchema->hasMethod('setFinisherIdentifier')
516
                || ($classSchema->hasMethod('__construct')
517
                    && count($classSchema->getMethod('__construct')->getParameters()) >= 1
518
                    && (string)$classSchema->getMethod('__construct')->getFirstParameter()->getType() === 'string')
519
            ) {
520
                // @deprecated since v11, will be removed in v12 - Fallback for Finishers that do not
521
                // extend AbstractFinisher and have no setFinisherIdentifier() method or still have a
522
                // constructor argument to set the finisher name: Mixing manual constructor arguments
523
                // with injection is not allowed by symfony DI, so we dropped the constructor argument
524
                // for v11 to keep the injection.
525
                // The above if detects "old" finisher and falls back to ObjectManager, which will log
526
                // a deprecation.
527
                // Drop the if with this body in v12, keep else body, clean up
528
                // AbstractFinisher->setFinisherIdentifier() and enable the method in FinisherInterface.
529
                $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
530
                /** @var FinisherInterface $finisher */
531
                $finisher = $objectManager->get($implementationClassName, $finisherIdentifier);
532
                if ($classSchema->hasMethod('setFinisherIdentifier')) {
533
                    $finisher->setFinisherIdentifier($finisherIdentifier);
0 ignored issues
show
Bug introduced by
The method setFinisherIdentifier() does not exist on TYPO3\CMS\Form\Domain\Finishers\FinisherInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Form\Domain\Finishers\FinisherInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

533
                    $finisher->/** @scrutinizer ignore-call */ 
534
                               setFinisherIdentifier($finisherIdentifier);
Loading history...
534
                }
535
            } else {
536
                /** @var FinisherInterface $finisher */
537
                $finisher = GeneralUtility::makeInstance($implementationClassName);
538
                $finisher->setFinisherIdentifier($finisherIdentifier);
539
            }
540
541
            $finisher->setOptions($defaultOptions);
542
            $this->addFinisher($finisher);
543
            return $finisher;
544
        }
545
        throw new FinisherPresetNotFoundException('The finisher preset identified by "' . $finisherIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328709784);
546
    }
547
548
    /**
549
     * Gets all finishers of this form
550
     *
551
     * @return \TYPO3\CMS\Form\Domain\Finishers\FinisherInterface[]
552
     */
553
    public function getFinishers(): array
554
    {
555
        return $this->finishers;
556
    }
557
558
    /**
559
     * Add an element to the ElementsByIdentifier Cache.
560
     *
561
     * @param RenderableInterface $renderable
562
     * @throws DuplicateFormElementException
563
     * @internal
564
     */
565
    public function registerRenderable(RenderableInterface $renderable)
566
    {
567
        if ($renderable instanceof FormElementInterface) {
568
            if (isset($this->elementsByIdentifier[$renderable->getIdentifier()])) {
569
                throw new DuplicateFormElementException(sprintf('A form element with identifier "%s" is already part of the form.', $renderable->getIdentifier()), 1325663761);
570
            }
571
            $this->elementsByIdentifier[$renderable->getIdentifier()] = $renderable;
572
        }
573
    }
574
575
    /**
576
     * Remove an element from the ElementsByIdentifier cache
577
     *
578
     * @param RenderableInterface $renderable
579
     * @internal
580
     */
581
    public function unregisterRenderable(RenderableInterface $renderable)
582
    {
583
        if ($renderable instanceof FormElementInterface) {
584
            unset($this->elementsByIdentifier[$renderable->getIdentifier()]);
585
        }
586
    }
587
588
    /**
589
     * Get all form elements with their identifiers as keys
590
     *
591
     * @return FormElementInterface[]
592
     */
593
    public function getElements(): array
594
    {
595
        return $this->elementsByIdentifier;
596
    }
597
598
    /**
599
     * Get a Form Element by its identifier
600
     *
601
     * If identifier does not exist, returns NULL.
602
     *
603
     * @param string $elementIdentifier
604
     * @return FormElementInterface The element with the given $elementIdentifier or NULL if none found
605
     */
606
    public function getElementByIdentifier(string $elementIdentifier)
607
    {
608
        return $this->elementsByIdentifier[$elementIdentifier] ?? null;
609
    }
610
611
    /**
612
     * Sets the default value of a form element
613
     *
614
     * @param string $elementIdentifier identifier of the form element. This supports property paths!
615
     * @param mixed $defaultValue
616
     * @internal
617
     */
618
    public function addElementDefaultValue(string $elementIdentifier, $defaultValue)
619
    {
620
        $this->elementDefaultValues = ArrayUtility::setValueByPath(
621
            $this->elementDefaultValues,
622
            $elementIdentifier,
623
            $defaultValue,
624
            '.'
625
        );
626
    }
627
628
    /**
629
     * returns the default value of the specified form element
630
     * or NULL if no default value was set
631
     *
632
     * @param string $elementIdentifier identifier of the form element. This supports property paths!
633
     * @return mixed The elements default value
634
     * @internal
635
     */
636
    public function getElementDefaultValueByIdentifier(string $elementIdentifier)
637
    {
638
        return ObjectAccess::getPropertyPath($this->elementDefaultValues, $elementIdentifier);
639
    }
640
641
    /**
642
     * Move $pageToMove before $referencePage
643
     *
644
     * @param Page $pageToMove
645
     * @param Page $referencePage
646
     */
647
    public function movePageBefore(Page $pageToMove, Page $referencePage)
648
    {
649
        $this->moveRenderableBefore($pageToMove, $referencePage);
650
    }
651
652
    /**
653
     * Move $pageToMove after $referencePage
654
     *
655
     * @param Page $pageToMove
656
     * @param Page $referencePage
657
     */
658
    public function movePageAfter(Page $pageToMove, Page $referencePage)
659
    {
660
        $this->moveRenderableAfter($pageToMove, $referencePage);
661
    }
662
663
    /**
664
     * Remove $pageToRemove from form
665
     *
666
     * @param Page $pageToRemove
667
     */
668
    public function removePage(Page $pageToRemove)
669
    {
670
        $this->removeRenderable($pageToRemove);
671
    }
672
673
    /**
674
     * Bind the current request & response to this form instance, effectively creating
675
     * a new "instance" of the Form.
676
     *
677
     * @param Request $request
678
     * @param ResponseInterface $response
679
     * @return FormRuntime
680
     */
681
    public function bind(Request $request, ResponseInterface $response): FormRuntime
682
    {
683
        return GeneralUtility::makeInstance(FormRuntime::class, $this, $request, $response);
684
    }
685
686
    /**
687
     * @param string $propertyPath
688
     * @return ProcessingRule
689
     */
690
    public function getProcessingRule(string $propertyPath): ProcessingRule
691
    {
692
        if (!isset($this->processingRules[$propertyPath])) {
693
            $this->processingRules[$propertyPath] = GeneralUtility::makeInstance(ProcessingRule::class);
694
        }
695
        return $this->processingRules[$propertyPath];
696
    }
697
698
    /**
699
     * Get all mapping rules
700
     *
701
     * @return \TYPO3\CMS\Form\Mvc\ProcessingRule[]
702
     * @internal
703
     */
704
    public function getProcessingRules(): array
705
    {
706
        return $this->processingRules;
707
    }
708
709
    /**
710
     * @return array
711
     * @internal
712
     */
713
    public function getTypeDefinitions(): array
714
    {
715
        return $this->typeDefinitions;
716
    }
717
718
    /**
719
     * @return array
720
     * @internal
721
     */
722
    public function getValidatorsDefinition(): array
723
    {
724
        return $this->validatorsDefinition;
725
    }
726
727
    /**
728
     * @return array
729
     * @internal
730
     */
731
    public function getConditionContextDefinition(): array
732
    {
733
        return $this->conditionContextDefinition;
734
    }
735
736
    /**
737
     * Get the persistence identifier of the form
738
     *
739
     * @return string
740
     * @internal
741
     */
742
    public function getPersistenceIdentifier(): string
743
    {
744
        return $this->persistenceIdentifier;
745
    }
746
747
    /**
748
     * Set the renderer class name
749
     *
750
     * @param string $rendererClassName
751
     */
752
    public function setRendererClassName(string $rendererClassName)
753
    {
754
        $this->rendererClassName = $rendererClassName;
755
    }
756
757
    /**
758
     * Get the classname of the renderer
759
     *
760
     * @return string
761
     */
762
    public function getRendererClassName(): string
763
    {
764
        return $this->rendererClassName;
765
    }
766
}
767