Completed
Push — master ( d47025...042ba2 )
by
unknown
14:11
created

FormEditorController::migrateEmailFormatOption()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 5
nop 1
dl 0
loc 18
rs 9.6111
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
namespace TYPO3\CMS\Form\Controller;
19
20
use TYPO3\CMS\Backend\Routing\UriBuilder;
21
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
22
use TYPO3\CMS\Backend\View\BackendTemplateView;
23
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24
use TYPO3\CMS\Core\Imaging\Icon;
25
use TYPO3\CMS\Core\Localization\LanguageService;
26
use TYPO3\CMS\Core\Page\PageRenderer;
27
use TYPO3\CMS\Core\Site\Entity\Site;
28
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
29
use TYPO3\CMS\Core\Utility\ArrayUtility;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
32
use TYPO3\CMS\Fluid\View\TemplateView;
33
use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
34
use TYPO3\CMS\Form\Domain\Configuration\FormDefinitionConversionService;
35
use TYPO3\CMS\Form\Domain\Exception\RenderingException;
36
use TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory;
37
use TYPO3\CMS\Form\Exception;
38
use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException;
39
use TYPO3\CMS\Form\Service\TranslationService;
40
use TYPO3\CMS\Form\Type\FormDefinitionArray;
41
42
/**
43
 * The form editor controller
44
 *
45
 * Scope: backend
46
 * @internal
47
 */
48
class FormEditorController extends AbstractBackendController
49
{
50
51
    /**
52
     * Default View Container
53
     *
54
     * @var string
55
     */
56
    protected $defaultViewObjectName = BackendTemplateView::class;
57
58
    /**
59
     * @var array
60
     */
61
    protected $prototypeConfiguration;
62
63
    /**
64
     * Displays the form editor
65
     *
66
     * @param string $formPersistenceIdentifier
67
     * @param string $prototypeName
68
     * @throws PersistenceManagerException
69
     * @internal
70
     */
71
    public function indexAction(string $formPersistenceIdentifier, string $prototypeName = null)
72
    {
73
        $this->registerDocheaderButtons();
74
        $this->view->getModuleTemplate()->setModuleName($this->request->getPluginName() . '_' . $this->request->getControllerName());
0 ignored issues
show
Bug introduced by
The method getModuleTemplate() does not exist on TYPO3\CMS\Extbase\Mvc\View\ViewInterface. It seems like you code against a sub-type of TYPO3\CMS\Extbase\Mvc\View\ViewInterface such as TYPO3\CMS\Extbase\Mvc\View\EmptyView or TYPO3\CMS\Backend\View\BackendTemplateView or TYPO3\CMS\Extbase\Mvc\View\NotFoundView. ( Ignorable by Annotation )

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

74
        $this->view->/** @scrutinizer ignore-call */ 
75
                     getModuleTemplate()->setModuleName($this->request->getPluginName() . '_' . $this->request->getControllerName());
Loading history...
75
        $this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
76
77
        if (
78
            strpos($formPersistenceIdentifier, 'EXT:') === 0
79
            && !$this->formSettings['persistenceManager']['allowSaveToExtensionPaths']
80
        ) {
81
            throw new PersistenceManagerException('Edit a extension formDefinition is not allowed.', 1478265661);
82
        }
83
84
        $configurationService = $this->objectManager->get(ConfigurationService::class);
85
        $formDefinition = $this->formPersistenceManager->load($formPersistenceIdentifier);
86
87
        if ($prototypeName === null) {
88
            $prototypeName = $formDefinition['prototypeName'] ?? 'standard';
89
        } else {
90
            // Loading a form definition with another prototype is currently not implemented but is planned in the future.
91
            // This safety check is a preventive measure.
92
            $selectablePrototypeNames = $configurationService->getSelectablePrototypeNamesDefinedInFormEditorSetup();
93
            if (!in_array($prototypeName, $selectablePrototypeNames, true)) {
94
                throw new Exception(sprintf('The prototype name "%s" is not configured within "formManager.selectablePrototypesConfiguration" ', $prototypeName), 1528625039);
95
            }
96
        }
97
98
        $formDefinition['prototypeName'] = $prototypeName;
99
        $this->prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName);
100
101
        $formDefinition = $this->transformFormDefinitionForFormEditor($formDefinition);
102
        $formEditorDefinitions = $this->getFormEditorDefinitions();
103
104
        $formEditorAppInitialData = [
105
            'formEditorDefinitions' => $formEditorDefinitions,
106
            'formDefinition' => $formDefinition,
107
            'formPersistenceIdentifier' => $formPersistenceIdentifier,
108
            'prototypeName' => $prototypeName,
109
            'endpoints' => [
110
                'formPageRenderer' => $this->controllerContext->getUriBuilder()->uriFor('renderFormPage'),
111
                'saveForm' => $this->controllerContext->getUriBuilder()->uriFor('saveForm')
112
            ],
113
            'additionalViewModelModules' => $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']['additionalViewModelModules'],
114
            'maximumUndoSteps' => $this->prototypeConfiguration['formEditor']['maximumUndoSteps'],
115
        ];
116
117
        $this->view->assign('formEditorAppInitialData', json_encode($formEditorAppInitialData));
118
        $this->view->assign('stylesheets', $this->resolveResourcePaths($this->prototypeConfiguration['formEditor']['stylesheets']));
119
        $this->view->assign('formEditorTemplates', $this->renderFormEditorTemplates($formEditorDefinitions));
120
        $this->view->assign('dynamicRequireJsModules', $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']);
121
122
        $this->getPageRenderer()->addInlineLanguageLabelFile('EXT:form/Resources/Private/Language/locallang_formEditor_failSafeErrorHandling_javascript.xlf');
123
124
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
125
        $addInlineSettings = [
126
            'FormEditor' => [
127
                'typo3WinBrowserUrl' => (string)$uriBuilder->buildUriFromRoute('wizard_element_browser'),
128
            ],
129
        ];
130
131
        $addInlineSettings = array_replace_recursive(
132
            $addInlineSettings,
133
            $this->prototypeConfiguration['formEditor']['addInlineSettings']
134
        );
135
        $this->view->assign('addInlineSettings', $addInlineSettings);
136
    }
137
138
    /**
139
     * Initialize the save action.
140
     * This action uses the Fluid JsonView::class as view.
141
     *
142
     * @internal
143
     */
144
    public function initializeSaveFormAction()
145
    {
146
        $this->defaultViewObjectName = JsonView::class;
147
    }
148
149
    /**
150
     * Save a formDefinition which was build by the form editor.
151
     *
152
     * @param string $formPersistenceIdentifier
153
     * @param FormDefinitionArray $formDefinition
154
     * @internal
155
     */
156
    public function saveFormAction(string $formPersistenceIdentifier, FormDefinitionArray $formDefinition)
157
    {
158
        $formDefinition = $formDefinition->getArrayCopy();
159
160
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormSave'] ?? [] as $className) {
161
            $hookObj = GeneralUtility::makeInstance($className);
162
            if (method_exists($hookObj, 'beforeFormSave')) {
163
                $formDefinition = $hookObj->beforeFormSave(
164
                    $formPersistenceIdentifier,
165
                    $formDefinition
166
                );
167
            }
168
        }
169
170
        $response = [
171
            'status' => 'success',
172
        ];
173
174
        try {
175
            $this->formPersistenceManager->save($formPersistenceIdentifier, $formDefinition);
176
            $configurationService = $this->objectManager->get(ConfigurationService::class);
177
            $this->prototypeConfiguration = $configurationService->getPrototypeConfiguration($formDefinition['prototypeName']);
178
            $formDefinition = $this->transformFormDefinitionForFormEditor($formDefinition);
179
            $response['formDefinition'] = $formDefinition;
180
        } catch (PersistenceManagerException $e) {
181
            $response = [
182
                'status' => 'error',
183
                'message' => $e->getMessage(),
184
                'code' => $e->getCode(),
185
            ];
186
        }
187
188
        $this->view->assign('response', $response);
189
        // saveFormAction uses the extbase JsonView::class.
190
        // That's why we have to set the view variables in this way.
191
        $this->view->setVariablesToRender([
0 ignored issues
show
Bug introduced by
The method setVariablesToRender() does not exist on TYPO3\CMS\Extbase\Mvc\View\ViewInterface. It seems like you code against a sub-type of TYPO3\CMS\Extbase\Mvc\View\ViewInterface such as TYPO3\CMS\Extbase\Mvc\View\EmptyView or TYPO3\CMS\Extbase\Mvc\View\NotFoundView or TYPO3\CMS\Extbase\Mvc\View\JsonView. ( Ignorable by Annotation )

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

191
        $this->view->/** @scrutinizer ignore-call */ 
192
                     setVariablesToRender([
Loading history...
192
            'response',
193
        ]);
194
    }
195
196
    /**
197
     * Render a page from the formDefinition which was build by the form editor.
198
     * Use the frontend rendering and set the form framework to preview mode.
199
     *
200
     * @param FormDefinitionArray $formDefinition
201
     * @param int $pageIndex
202
     * @param string $prototypeName
203
     * @return string
204
     * @internal
205
     */
206
    public function renderFormPageAction(FormDefinitionArray $formDefinition, int $pageIndex, string $prototypeName = null): string
207
    {
208
        $prototypeName = $prototypeName ?: $formDefinition['prototypeName'] ?? 'standard';
209
        $formDefinition = $formDefinition->getArrayCopy();
210
211
        $formFactory = $this->objectManager->get(ArrayFormFactory::class);
212
        $formDefinition = $formFactory->build($formDefinition, $prototypeName);
213
        $formDefinition->setRenderingOption('previewMode', true);
214
        $form = $formDefinition->bind($this->request, $this->response);
215
        $form->setCurrentSiteLanguage($this->buildFakeSiteLanguage(0, 0));
216
        $form->overrideCurrentPage($pageIndex);
217
218
        return $form->render();
219
    }
220
221
    /**
222
     * Build a SiteLanguage object to render the form preview with a
223
     * specific language.
224
     *
225
     * @param int $pageId
226
     * @param int $languageId
227
     * @return SiteLanguage
228
     */
229
    protected function buildFakeSiteLanguage(int $pageId, int $languageId): SiteLanguage
230
    {
231
        $fakeSiteConfiguration = [
232
            'languages' => [
233
                [
234
                    'languageId' => $languageId,
235
                    'title' => 'Dummy',
236
                    'navigationTitle' => '',
237
                    'typo3Language' => '',
238
                    'flag' => '',
239
                    'locale' => '',
240
                    'iso-639-1' => '',
241
                    'hreflang' => '',
242
                    'direction' => '',
243
                ],
244
            ],
245
        ];
246
247
        /** @var \TYPO3\CMS\Core\Site\Entity\SiteLanguage $currentSiteLanguage */
248
        $currentSiteLanguage = GeneralUtility::makeInstance(Site::class, 'form-dummy', $pageId, $fakeSiteConfiguration)
249
            ->getLanguageById($languageId);
250
        return $currentSiteLanguage;
251
    }
252
253
    /**
254
     * Prepare the formElements.*.formEditor section from the YAML settings.
255
     * Sort all formElements into groups and add additional data.
256
     *
257
     * @param array $formElementsDefinition
258
     * @return array
259
     */
260
    protected function getInsertRenderablesPanelConfiguration(array $formElementsDefinition): array
261
    {
262
        /** @var array<string, array<string, string>> $formElementsByGroup */
263
        $formElementsByGroup = [];
264
265
        foreach ($formElementsDefinition as $formElementName => $formElementConfiguration) {
266
            if (!isset($formElementConfiguration['group'])) {
267
                continue;
268
            }
269
            if (!isset($formElementsByGroup[$formElementConfiguration['group']])) {
270
                $formElementsByGroup[$formElementConfiguration['group']] = [];
271
            }
272
273
            $formElementConfiguration = TranslationService::getInstance()->translateValuesRecursive(
274
                $formElementConfiguration,
275
                $this->prototypeConfiguration['formEditor']['translationFiles'] ?? []
276
            );
277
278
            $formElementsByGroup[$formElementConfiguration['group']][] = [
279
                'key' => $formElementName,
280
                'cssKey' => preg_replace('/[^a-z0-9]/', '-', strtolower($formElementName)),
281
                'label' => $formElementConfiguration['label'],
282
                'sorting' => $formElementConfiguration['groupSorting'],
283
                'iconIdentifier' => $formElementConfiguration['iconIdentifier'],
284
            ];
285
        }
286
287
        $formGroups = [];
288
        foreach ($this->prototypeConfiguration['formEditor']['formElementGroups'] ?? [] as $groupName => $groupConfiguration) {
289
            if (!isset($formElementsByGroup[$groupName])) {
290
                continue;
291
            }
292
293
            usort($formElementsByGroup[$groupName], function ($a, $b) {
294
                return $a['sorting'] - $b['sorting'];
295
            });
296
            unset($formElementsByGroup[$groupName]['sorting']);
297
298
            $groupConfiguration = TranslationService::getInstance()->translateValuesRecursive(
299
                $groupConfiguration,
300
                $this->prototypeConfiguration['formEditor']['translationFiles'] ?? []
301
            );
302
303
            $formGroups[] = [
304
                'key' => $groupName,
305
                'elements' => $formElementsByGroup[$groupName],
306
                'label' => $groupConfiguration['label'],
307
            ];
308
        }
309
310
        return $formGroups;
311
    }
312
313
    /**
314
     * Reduce the YAML settings by the 'formEditor' keyword.
315
     *
316
     * @return array
317
     */
318
    protected function getFormEditorDefinitions(): array
319
    {
320
        $formEditorDefinitions = [];
321
        foreach ([$this->prototypeConfiguration, $this->prototypeConfiguration['formEditor']] as $configuration) {
322
            foreach ($configuration as $firstLevelItemKey => $firstLevelItemValue) {
323
                if (substr($firstLevelItemKey, -10) !== 'Definition') {
324
                    continue;
325
                }
326
                $reducedKey = substr($firstLevelItemKey, 0, -10);
327
                foreach ($configuration[$firstLevelItemKey] as $formEditorDefinitionKey => $formEditorDefinitionValue) {
328
                    if (isset($formEditorDefinitionValue['formEditor'])) {
329
                        $formEditorDefinitionValue = array_intersect_key($formEditorDefinitionValue, array_flip(['formEditor']));
330
                        $formEditorDefinitions[$reducedKey][$formEditorDefinitionKey] = $formEditorDefinitionValue['formEditor'];
331
                    } else {
332
                        $formEditorDefinitions[$reducedKey][$formEditorDefinitionKey] = $formEditorDefinitionValue;
333
                    }
334
                }
335
            }
336
        }
337
        $formEditorDefinitions = ArrayUtility::reIndexNumericArrayKeysRecursive($formEditorDefinitions);
338
        $formEditorDefinitions = TranslationService::getInstance()->translateValuesRecursive(
339
            $formEditorDefinitions,
340
            $this->prototypeConfiguration['formEditor']['translationFiles'] ?? []
341
        );
342
        return $formEditorDefinitions;
343
    }
344
345
    /**
346
     * Registers the Icons into the docheader
347
     *
348
     * @throws \InvalidArgumentException
349
     */
350
    protected function registerDocheaderButtons()
351
    {
352
        /** @var ButtonBar $buttonBar */
353
        $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
354
        $getVars = $this->request->getArguments();
355
356
        if (isset($getVars['action']) && $getVars['action'] === 'index') {
357
            $newPageButton = $buttonBar->makeInputButton()
358
                ->setDataAttributes(['action' => 'formeditor-new-page', 'identifier' => 'headerNewPage'])
359
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.new_page_button'))
360
                ->setName('formeditor-new-page')
361
                ->setValue('new-page')
362
                ->setClasses('t3-form-element-new-page-button hidden')
363
                ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL));
364
            /** @var UriBuilder $uriBuilder */
365
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
366
367
            $closeButton = $buttonBar->makeLinkButton()
368
                ->setDataAttributes(['identifier' => 'closeButton'])
369
                ->setHref((string)$uriBuilder->buildUriFromRoute('web_FormFormbuilder'))
370
                ->setClasses('t3-form-element-close-form-button hidden')
371
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
372
                ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
373
374
            $saveButton = $buttonBar->makeInputButton()
375
                ->setDataAttributes(['identifier' => 'saveButton'])
376
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.save_button'))
377
                ->setName('formeditor-save-form')
378
                ->setValue('save')
379
                ->setClasses('t3-form-element-save-form-button hidden')
380
                ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
381
                ->setShowLabelText(true);
382
383
            $formSettingsButton = $buttonBar->makeInputButton()
384
                ->setDataAttributes(['identifier' => 'formSettingsButton'])
385
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.form_settings_button'))
386
                ->setName('formeditor-form-settings')
387
                ->setValue('settings')
388
                ->setClasses('t3-form-element-form-settings-button hidden')
389
                ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-system-extension-configure', Icon::SIZE_SMALL))
390
                ->setShowLabelText(true);
391
392
            $undoButton = $buttonBar->makeInputButton()
393
                ->setDataAttributes(['identifier' => 'undoButton'])
394
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.undo_button'))
395
                ->setName('formeditor-undo-form')
396
                ->setValue('undo')
397
                ->setClasses('t3-form-element-undo-form-button hidden disabled')
398
                ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-edit-undo', Icon::SIZE_SMALL));
399
400
            $redoButton = $buttonBar->makeInputButton()
401
                ->setDataAttributes(['identifier' => 'redoButton'])
402
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.redo_button'))
403
                ->setName('formeditor-redo-form')
404
                ->setValue('redo')
405
                ->setClasses('t3-form-element-redo-form-button hidden disabled')
406
                ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-edit-redo', Icon::SIZE_SMALL));
407
408
            $buttonBar->addButton($newPageButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
409
            $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
410
            $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
411
            $buttonBar->addButton($formSettingsButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
412
            $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
413
            $buttonBar->addButton($redoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
414
        }
415
    }
416
417
    /**
418
     * Render the "text/x-formeditor-template" templates.
419
     *
420
     * @param array $formEditorDefinitions
421
     * @return string
422
     */
423
    protected function renderFormEditorTemplates(array $formEditorDefinitions): string
424
    {
425
        $fluidConfiguration = $this->prototypeConfiguration['formEditor']['formEditorFluidConfiguration'] ?? null;
426
        $formEditorPartials = $this->prototypeConfiguration['formEditor']['formEditorPartials'] ?? null;
427
428
        if (!isset($fluidConfiguration['templatePathAndFilename'])) {
429
            throw new RenderingException(
430
                'The option templatePathAndFilename must be set.',
431
                1485636499
432
            );
433
        }
434
        if (
435
            !isset($fluidConfiguration['layoutRootPaths'])
436
            || !is_array($fluidConfiguration['layoutRootPaths'])
437
        ) {
438
            throw new RenderingException(
439
                'The option layoutRootPaths must be set.',
440
                1480294721
441
            );
442
        }
443
        if (
444
            !isset($fluidConfiguration['partialRootPaths'])
445
            || !is_array($fluidConfiguration['partialRootPaths'])
446
        ) {
447
            throw new RenderingException(
448
                'The option partialRootPaths must be set.',
449
                1480294722
450
            );
451
        }
452
453
        $insertRenderablesPanelConfiguration = $this->getInsertRenderablesPanelConfiguration($formEditorDefinitions['formElements']);
454
455
        $view = $this->objectManager->get(TemplateView::class);
456
        $view->setControllerContext(clone $this->controllerContext);
457
        $view->getRenderingContext()->getTemplatePaths()->fillFromConfigurationArray($fluidConfiguration);
458
        $view->setTemplatePathAndFilename($fluidConfiguration['templatePathAndFilename']);
459
        $view->assignMultiple([
460
            'insertRenderablesPanelConfiguration' => $insertRenderablesPanelConfiguration,
461
            'formEditorPartials' => $formEditorPartials,
462
        ]);
463
464
        return $view->render();
465
    }
466
467
    /**
468
     * @todo move this to FormDefinitionConversionService
469
     * @param array $formDefinition
470
     * @return array
471
     */
472
    protected function transformFormDefinitionForFormEditor(array $formDefinition): array
473
    {
474
        $multiValueFormElementProperties = [];
475
        $multiValueFinisherProperties = [];
476
477
        foreach ($this->prototypeConfiguration['formElementsDefinition'] as $type => $configuration) {
478
            if (!isset($configuration['formEditor']['editors'])) {
479
                continue;
480
            }
481
            foreach ($configuration['formEditor']['editors'] as $editorConfiguration) {
482
                if ($editorConfiguration['templateName'] === 'Inspector-PropertyGridEditor') {
483
                    $multiValueFormElementProperties[$type][] = $editorConfiguration['propertyPath'];
484
                }
485
            }
486
        }
487
488
        foreach ($this->prototypeConfiguration['formElementsDefinition']['Form']['formEditor']['propertyCollections']['finishers'] ?? [] as $configuration) {
489
            if (!isset($configuration['editors'])) {
490
                continue;
491
            }
492
493
            foreach ($configuration['editors'] as $editorConfiguration) {
494
                if ($editorConfiguration['templateName'] === 'Inspector-PropertyGridEditor') {
495
                    $multiValueFinisherProperties[$configuration['identifier']][] = $editorConfiguration['propertyPath'];
496
                }
497
            }
498
        }
499
500
        $formDefinition = $this->filterEmptyArrays($formDefinition);
501
        $formDefinition = $this->migrateEmailFinisherRecipients($formDefinition);
502
503
        // @todo: replace with rte parsing
504
        $formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition);
505
        $formDefinition = $this->transformMultiValuePropertiesForFormEditor(
506
            $formDefinition,
507
            'type',
508
            $multiValueFormElementProperties
509
        );
510
        $formDefinition = $this->transformMultiValuePropertiesForFormEditor(
511
            $formDefinition,
512
            'identifier',
513
            $multiValueFinisherProperties
514
        );
515
516
        $formDefinitionConversionService = $this->getFormDefinitionConversionService();
517
        $formDefinition = $formDefinitionConversionService->addHmacData($formDefinition);
518
519
        return $formDefinition;
520
    }
521
522
    /**
523
     * Some data needs a transformation before it can be used by the
524
     * form editor. This rules for multivalue elements like select
525
     * elements. To ensure the right sorting if the data goes into
526
     * javascript, we need to do transformations:
527
     *
528
     * [
529
     *   '5' => '5',
530
     *   '4' => '4',
531
     *   '3' => '3'
532
     * ]
533
     *
534
     *
535
     * This method transform this into:
536
     *
537
     * [
538
     *   [
539
     *     _label => '5'
540
     *     _value => 5
541
     *   ],
542
     *   [
543
     *     _label => '4'
544
     *     _value => 4
545
     *   ],
546
     *   [
547
     *     _label => '3'
548
     *     _value => 3
549
     *   ],
550
     * ]
551
     *
552
     * @param array $formDefinition
553
     * @param string $identifierProperty
554
     * @param array $multiValueProperties
555
     * @return array
556
     */
557
    protected function transformMultiValuePropertiesForFormEditor(
558
        array $formDefinition,
559
        string $identifierProperty,
560
        array $multiValueProperties
561
    ): array {
562
        $output = $formDefinition;
563
        foreach ($formDefinition as $key => $value) {
564
            $identifier = $value[$identifierProperty] ?? null;
565
566
            if (array_key_exists($identifier, $multiValueProperties)) {
567
                $multiValuePropertiesForIdentifier = $multiValueProperties[$identifier];
568
569
                foreach ($multiValuePropertiesForIdentifier as $multiValueProperty) {
570
                    if (!ArrayUtility::isValidPath($value, $multiValueProperty, '.')) {
571
                        continue;
572
                    }
573
574
                    $multiValuePropertyData = ArrayUtility::getValueByPath($value, $multiValueProperty, '.');
575
576
                    if (!is_array($multiValuePropertyData)) {
577
                        continue;
578
                    }
579
580
                    $newMultiValuePropertyData = [];
581
582
                    foreach ($multiValuePropertyData as $k => $v) {
583
                        $newMultiValuePropertyData[] = [
584
                            '_label' => $v,
585
                            '_value' => $k,
586
                        ];
587
                    }
588
589
                    $value = ArrayUtility::setValueByPath($value, $multiValueProperty, $newMultiValuePropertyData, '.');
590
                }
591
            }
592
593
            $output[$key] = $value;
594
595
            if (is_array($value)) {
596
                $output[$key] = $this->transformMultiValuePropertiesForFormEditor(
597
                    $value,
598
                    $identifierProperty,
599
                    $multiValueProperties
600
                );
601
            }
602
        }
603
604
        return $output;
605
    }
606
607
    /**
608
     * Remove keys from an array if the key value is an empty array
609
     *
610
     * @param array $array
611
     * @return array
612
     */
613
    protected function filterEmptyArrays(array $array): array
614
    {
615
        foreach ($array as $key => $value) {
616
            if (!is_array($value)) {
617
                continue;
618
            }
619
            if (empty($value)) {
620
                unset($array[$key]);
621
                continue;
622
            }
623
            $array[$key] = $this->filterEmptyArrays($value);
624
            if (empty($array[$key])) {
625
                unset($array[$key]);
626
            }
627
        }
628
629
        return $array;
630
    }
631
632
    /**
633
     * Migrate single recipient options to their list successors
634
     *
635
     * @param array $formDefinition
636
     * @return array
637
     */
638
    protected function migrateEmailFinisherRecipients(array $formDefinition): array
639
    {
640
        foreach ($formDefinition['finishers'] ?? [] as $i => $finisherConfiguration) {
641
            if (!in_array($finisherConfiguration['identifier'], ['EmailToSender', 'EmailToReceiver'], true)) {
642
                continue;
643
            }
644
645
            $recipientAddress = $finisherConfiguration['options']['recipientAddress'] ?? '';
646
            $recipientName = $finisherConfiguration['options']['recipientName'] ?? '';
647
            $carbonCopyAddress = $finisherConfiguration['options']['carbonCopyAddress'] ?? '';
648
            $blindCarbonCopyAddress = $finisherConfiguration['options']['blindCarbonCopyAddress'] ?? '';
649
            $replyToAddress = $finisherConfiguration['options']['replyToAddress'] ?? '';
650
651
            if (!empty($recipientAddress)) {
652
                $finisherConfiguration['options']['recipients'][$recipientAddress] = $recipientName;
653
            }
654
655
            if (!empty($carbonCopyAddress)) {
656
                $finisherConfiguration['options']['carbonCopyRecipients'][$carbonCopyAddress] = '';
657
            }
658
659
            if (!empty($blindCarbonCopyAddress)) {
660
                $finisherConfiguration['options']['blindCarbonCopyRecipients'][$blindCarbonCopyAddress] = '';
661
            }
662
663
            if (!empty($replyToAddress)) {
664
                $finisherConfiguration['options']['replyToRecipients'][$replyToAddress] = '';
665
            }
666
667
            unset(
668
                $finisherConfiguration['options']['recipientAddress'],
669
                $finisherConfiguration['options']['recipientName'],
670
                $finisherConfiguration['options']['carbonCopyAddress'],
671
                $finisherConfiguration['options']['blindCarbonCopyAddress'],
672
                $finisherConfiguration['options']['replyToAddress']
673
            );
674
            $formDefinition['finishers'][$i] = $finisherConfiguration;
675
        }
676
677
        return $formDefinition;
678
    }
679
680
    /**
681
     * @return FormDefinitionConversionService
682
     */
683
    protected function getFormDefinitionConversionService(): FormDefinitionConversionService
684
    {
685
        return GeneralUtility::makeInstance(FormDefinitionConversionService::class);
686
    }
687
688
    /**
689
     * Returns the current BE user.
690
     *
691
     * @return BackendUserAuthentication
692
     */
693
    protected function getBackendUser(): BackendUserAuthentication
694
    {
695
        return $GLOBALS['BE_USER'];
696
    }
697
698
    /**
699
     * Returns the language service
700
     *
701
     * @return LanguageService
702
     */
703
    protected function getLanguageService(): LanguageService
704
    {
705
        return $GLOBALS['LANG'];
706
    }
707
708
    /**
709
     * Returns the page renderer
710
     *
711
     * @return PageRenderer
712
     */
713
    protected function getPageRenderer(): PageRenderer
714
    {
715
        return GeneralUtility::makeInstance(PageRenderer::class);
716
    }
717
}
718