ac92795f397ca14e8ce7e761d46aeeafab903787
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Controller / FormEditorController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Form\Controller;
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 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
19 use TYPO3\CMS\Backend\View\BackendTemplateView;
20 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21 use TYPO3\CMS\Core\Configuration\Loader\FalYamlFileLoader;
22 use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration;
23 use TYPO3\CMS\Core\Imaging\Icon;
24 use TYPO3\CMS\Core\Localization\LanguageService;
25 use TYPO3\CMS\Core\Utility\ArrayUtility;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use TYPO3\CMS\Core\Utility\MathUtility;
28 use TYPO3\CMS\Extbase\Mvc\View\JsonView;
29 use TYPO3\CMS\Fluid\View\TemplateView;
30 use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
31 use TYPO3\CMS\Form\Domain\Exception\InvalidFormDefinitionException;
32 use TYPO3\CMS\Form\Domain\Exception\RenderingException;
33 use TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory;
34 use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException;
35 use TYPO3\CMS\Form\Service\TranslationService;
36 use TYPO3\CMS\Form\Type\FormDefinitionArray;
37
38 /**
39 * The form editor controller
40 *
41 * Scope: backend
42 */
43 class FormEditorController extends AbstractBackendController
44 {
45
46 /**
47 * Default View Container
48 *
49 * @var BackendTemplateView
50 */
51 protected $defaultViewObjectName = BackendTemplateView::class;
52
53 /**
54 * @var array
55 */
56 protected $prototypeConfiguration;
57
58 /**
59 * Displays the form editor
60 *
61 * @param string $formPersistenceIdentifier
62 * @param string $prototypeName
63 * @throws PersistenceManagerException
64 * @internal
65 */
66 public function indexAction(string $formPersistenceIdentifier, string $prototypeName = null)
67 {
68 $this->registerDocheaderButtons();
69 $this->view->getModuleTemplate()->setModuleName($this->request->getPluginName() . '_' . $this->request->getControllerName());
70 $this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
71
72 if (
73 strpos($formPersistenceIdentifier, 'EXT:') === 0
74 && !$this->formSettings['persistenceManager']['allowSaveToExtensionPaths']
75 ) {
76 throw new PersistenceManagerException('Edit a extension formDefinition is not allowed.', 1478265661);
77 }
78
79 $prototypeName = $prototypeName ?? $formDefinition['prototypeName'] ?? 'standard';
80 /** @var \TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration */
81 $configuration = GeneralUtility::makeInstance(Configuration::class)
82 ->setRemoveImportsProperty(false)
83 ->setMergeLists(false);
84 $formDefinition = $this->formPersistenceManager->load($formPersistenceIdentifier, $configuration);
85 $formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition);
86 $formDefinition = $this->transformFormDefinitionWithImportsForFormEditor($formDefinition, is_array($formDefinition['imports']));
87 $formDefinition['prototypeName'] = $prototypeName;
88
89 $configurationService = $this->objectManager->get(ConfigurationService::class);
90 $this->prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName);
91
92 $formEditorDefinitions = $this->getFormEditorDefinitions();
93
94 $formEditorAppInitialData = [
95 'formEditorDefinitions' => $formEditorDefinitions,
96 'formDefinition' => $formDefinition,
97 'formPersistenceIdentifier' => $formPersistenceIdentifier,
98 'prototypeName' => $prototypeName,
99 'endpoints' => [
100 'formPageRenderer' => $this->controllerContext->getUriBuilder()->uriFor('renderFormPage'),
101 'saveForm' => $this->controllerContext->getUriBuilder()->uriFor('saveForm')
102 ],
103 'additionalViewModelModules' => $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']['additionalViewModelModules'],
104 'maximumUndoSteps' => $this->prototypeConfiguration['formEditor']['maximumUndoSteps'],
105 ];
106
107 $this->view->assign('formEditorAppInitialData', json_encode($formEditorAppInitialData));
108 $this->view->assign('stylesheets', $this->resolveResourcePaths($this->prototypeConfiguration['formEditor']['stylesheets']));
109 $this->view->assign('formEditorTemplates', $this->renderFormEditorTemplates($formEditorDefinitions));
110 $this->view->assign('dynamicRequireJsModules', $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']);
111
112 $popupWindowWidth = 700;
113 $popupWindowHeight = 750;
114 $popupWindowSize = ($this->getBackendUser()->getTSConfigVal('options.popupWindowSize'))
115 ? trim($this->getBackendUser()->getTSConfigVal('options.popupWindowSize'))
116 : null;
117 if (!empty($popupWindowSize)) {
118 list($popupWindowWidth, $popupWindowHeight) = GeneralUtility::intExplode('x', $popupWindowSize);
119 }
120 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
121 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
122 $addInlineSettings = [
123 'FormEditor' => [
124 'typo3WinBrowserUrl' => (string)$uriBuilder->buildUriFromRoute('wizard_element_browser'),
125 ],
126 'Popup' => [
127 'PopupWindow' => [
128 'width' => $popupWindowWidth,
129 'height' => $popupWindowHeight
130 ],
131 ]
132 ];
133
134 $addInlineSettings = array_replace_recursive(
135 $addInlineSettings,
136 $this->prototypeConfiguration['formEditor']['addInlineSettings']
137 );
138 $this->view->assign('addInlineSettings', $addInlineSettings);
139 }
140
141 /**
142 * Initialize the save action.
143 * This action uses the Fluid JsonView::class as view.
144 *
145 * @internal
146 */
147 public function initializeSaveFormAction()
148 {
149 $this->defaultViewObjectName = JsonView::class;
150 }
151
152 /**
153 * Save a formDefinition which was build by the form editor.
154 *
155 * @param string $formPersistenceIdentifier
156 * @param FormDefinitionArray $formDefinition
157 * @internal
158 */
159 public function saveFormAction(string $formPersistenceIdentifier, FormDefinitionArray $formDefinition)
160 {
161 $formDefinition = $formDefinition->getArrayCopy();
162
163 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormSave'] ?? [] as $className) {
164 $hookObj = GeneralUtility::makeInstance($className);
165 if (method_exists($hookObj, 'beforeFormSave')) {
166 $formDefinition = $hookObj->beforeFormSave(
167 $formPersistenceIdentifier,
168 $formDefinition
169 );
170 }
171 }
172
173 $formDefinition = $this->transformFormDefinitionWithImportsForFormFramework(
174 $formPersistenceIdentifier,
175 $formDefinition,
176 is_array($formDefinition['imports'])
177 );
178
179 $response = [
180 'status' => 'success',
181 ];
182
183 try {
184 $this->formPersistenceManager->save($formPersistenceIdentifier, $formDefinition);
185 } catch (PersistenceManagerException $e) {
186 $response = [
187 'status' => 'error',
188 'message' => $e->getMessage(),
189 'code' => $e->getCode(),
190 ];
191 }
192
193 $this->view->assign('response', $response);
194 // saveFormAction uses the extbase JsonView::class.
195 // That's why we have to set the view variables in this way.
196 $this->view->setVariablesToRender([
197 'response',
198 ]);
199 }
200
201 /**
202 * Render a page from the formDefinition which was build by the form editor.
203 * Use the frontend rendering and set the form framework to preview mode.
204 *
205 * @param FormDefinitionArray $formDefinition
206 * @param int $pageIndex
207 * @param string $prototypeName
208 * @return string
209 * @internal
210 */
211 public function renderFormPageAction(FormDefinitionArray $formDefinition, int $pageIndex, string $prototypeName = null): string
212 {
213 $prototypeName = $prototypeName ?? $formDefinition['prototypeName'] ?? 'standard';
214 $formFactory = $this->objectManager->get(ArrayFormFactory::class);
215 $formDefinition = $formFactory->build($formDefinition->getArrayCopy(), $prototypeName);
216 $formDefinition->setRenderingOption('previewMode', true);
217 $form = $formDefinition->bind($this->request, $this->response);
218 $form->overrideCurrentPage($pageIndex);
219
220 return $form->render();
221 }
222
223 /**
224 * Prepare the formElements.*.formEditor section from the YAML settings.
225 * Sort all formElements into groups and add additional data.
226 *
227 * @param array $formElementsDefinition
228 * @return array
229 */
230 protected function getInsertRenderablesPanelConfiguration(array $formElementsDefinition): array
231 {
232 $formElementsByGroup = [];
233
234 foreach ($formElementsDefinition as $formElementName => $formElementConfiguration) {
235 if (!isset($formElementConfiguration['group'])) {
236 continue;
237 }
238 if (!isset($formElementsByGroup[$formElementConfiguration['group']])) {
239 $formElementsByGroup[$formElementConfiguration['group']] = [];
240 }
241
242 $formElementConfiguration = TranslationService::getInstance()->translateValuesRecursive(
243 $formElementConfiguration,
244 $this->prototypeConfiguration['formEditor']['translationFile']
245 );
246
247 $formElementsByGroup[$formElementConfiguration['group']][] = [
248 'key' => $formElementName,
249 'cssKey' => preg_replace('/[^a-z0-9]/', '-', strtolower($formElementName)),
250 'label' => $formElementConfiguration['label'],
251 'sorting' => $formElementConfiguration['groupSorting'],
252 'iconIdentifier' => $formElementConfiguration['iconIdentifier'],
253 ];
254 }
255
256 $formGroups = [];
257 foreach ($this->prototypeConfiguration['formEditor']['formElementGroups'] ?? [] as $groupName => $groupConfiguration) {
258 if (!isset($formElementsByGroup[$groupName])) {
259 continue;
260 }
261
262 usort($formElementsByGroup[$groupName], function ($a, $b) {
263 return $a['sorting'] - $b['sorting'];
264 });
265 unset($formElementsByGroup[$groupName]['sorting']);
266
267 $groupConfiguration = TranslationService::getInstance()->translateValuesRecursive(
268 $groupConfiguration,
269 $this->prototypeConfiguration['formEditor']['translationFile']
270 );
271
272 $formGroups[] = [
273 'key' => $groupName,
274 'elements' => $formElementsByGroup[$groupName],
275 'label' => $groupConfiguration['label'],
276 ];
277 }
278
279 return $formGroups;
280 }
281
282 /**
283 * Reduce the YAML settings by the 'formEditor' keyword.
284 *
285 * @return array
286 */
287 protected function getFormEditorDefinitions(): array
288 {
289 $formEditorDefinitions = [];
290 foreach ([$this->prototypeConfiguration, $this->prototypeConfiguration['formEditor']] as $configuration) {
291 foreach ($configuration as $firstLevelItemKey => $firstLevelItemValue) {
292 if (substr($firstLevelItemKey, -10) !== 'Definition') {
293 continue;
294 }
295 $reducedKey = substr($firstLevelItemKey, 0, -10);
296 foreach ($configuration[$firstLevelItemKey] as $formEditorDefinitionKey => $formEditorDefinitionValue) {
297 if (isset($formEditorDefinitionValue['formEditor'])) {
298 $formEditorDefinitionValue = array_intersect_key($formEditorDefinitionValue, array_flip(['formEditor']));
299 $formEditorDefinitions[$reducedKey][$formEditorDefinitionKey] = $formEditorDefinitionValue['formEditor'];
300 } else {
301 $formEditorDefinitions[$reducedKey][$formEditorDefinitionKey] = $formEditorDefinitionValue;
302 }
303 }
304 }
305 }
306 $formEditorDefinitions = ArrayUtility::reIndexNumericArrayKeysRecursive($formEditorDefinitions);
307 $formEditorDefinitions = TranslationService::getInstance()->translateValuesRecursive(
308 $formEditorDefinitions,
309 $this->prototypeConfiguration['formEditor']['translationFile']
310 );
311 return $formEditorDefinitions;
312 }
313
314 /**
315 * Registers the Icons into the docheader
316 *
317 * @throws \InvalidArgumentException
318 */
319 protected function registerDocheaderButtons()
320 {
321 /** @var ButtonBar $buttonBar */
322 $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
323 $getVars = $this->request->getArguments();
324
325 if (isset($getVars['action']) && $getVars['action'] === 'index') {
326 $newPageButton = $buttonBar->makeInputButton()
327 ->setDataAttributes(['action' => 'formeditor-new-page', 'identifier' => 'headerNewPage'])
328 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.new_page_button'))
329 ->setName('formeditor-new-page')
330 ->setValue('new-page')
331 ->setClasses('t3-form-element-new-page-button hidden')
332 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL));
333 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
334 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
335
336 $closeButton = $buttonBar->makeLinkButton()
337 ->setDataAttributes(['identifier' => 'closeButton'])
338 ->setHref((string)$uriBuilder->buildUriFromRoute('web_FormFormbuilder'))
339 ->setClasses('t3-form-element-close-form-button hidden')
340 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
341 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
342
343 $saveButton = $buttonBar->makeInputButton()
344 ->setDataAttributes(['identifier' => 'saveButton'])
345 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.save_button'))
346 ->setName('formeditor-save-form')
347 ->setValue('save')
348 ->setClasses('t3-form-element-save-form-button hidden')
349 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
350 ->setShowLabelText(true);
351
352 $formSettingsButton = $buttonBar->makeInputButton()
353 ->setDataAttributes(['identifier' => 'formSettingsButton'])
354 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.form_settings_button'))
355 ->setName('formeditor-form-settings')
356 ->setValue('settings')
357 ->setClasses('t3-form-element-form-settings-button hidden')
358 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-system-extension-configure', Icon::SIZE_SMALL))
359 ->setShowLabelText(true);
360
361 $undoButton = $buttonBar->makeInputButton()
362 ->setDataAttributes(['identifier' => 'undoButton'])
363 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.undo_button'))
364 ->setName('formeditor-undo-form')
365 ->setValue('undo')
366 ->setClasses('t3-form-element-undo-form-button hidden disabled')
367 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
368
369 $redoButton = $buttonBar->makeInputButton()
370 ->setDataAttributes(['identifier' => 'redoButton'])
371 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.redo_button'))
372 ->setName('formeditor-redo-form')
373 ->setValue('redo')
374 ->setClasses('t3-form-element-redo-form-button hidden disabled')
375 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-forward', Icon::SIZE_SMALL));
376
377 $buttonBar->addButton($newPageButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
378 $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
379 $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
380 $buttonBar->addButton($formSettingsButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
381 $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
382 $buttonBar->addButton($redoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
383 }
384 }
385
386 /**
387 * @param array $array
388 * @param bool $hasImports
389 * @return array
390 * @throws PropertyException
391 */
392 protected function transformFormDefinitionWithImportsForFormEditor(array $array, bool $hasImports): array
393 {
394 $result = $array;
395 foreach ($result as $key => $value) {
396 if (is_array($value)) {
397 if (
398 $key === 'renderables'
399 || $key === 'validators'
400 || $key === 'finishers'
401 ) {
402 if ($hasImports) {
403 foreach ($value as $itemKey => $item) {
404 if (is_int($itemKey)) {
405 throw new InvalidFormDefinitionException(
406 'All array keys within "' . $key . '" must be strings.',
407 1505505524
408 );
409 }
410
411 if ($itemKey !== $item['identifier']) {
412 throw new InvalidFormDefinitionException(
413 'All items keys within "' . $key . '" must be equal to the "identifier" property.',
414 1505505525
415 );
416 }
417
418 if (!isset($item['sorting'])) {
419 throw new InvalidFormDefinitionException(
420 'All items within "' . $key . '" must have a "sorting" property.',
421 1505505526
422 );
423 }
424 }
425 }
426 // transform string keys to integer keys
427 $value = array_values($value);
428
429 if ($hasImports) {
430 // sort by "sorting"
431 usort($value, function ($a, $b) {
432 return (float)$a['sorting'] - (float)$b['sorting'];
433 });
434 }
435 }
436 $result[$key] = $this->transformFormDefinitionWithImportsForFormEditor($value, $hasImports);
437 }
438 }
439 return $result;
440 }
441
442 /**
443 * @param string $formPersistenceIdentifier
444 * @param array $formDefinition
445 * @param bool $hasImports
446 * @return array
447 */
448 protected function transformFormDefinitionWithImportsForFormFramework(
449 string $formPersistenceIdentifier,
450 array $formDefinition,
451 bool $hasImports
452 ): array {
453 if ($hasImports) {
454 $fakeYaml = $this->generateFakeYamlFromImports(
455 $formDefinition['imports']
456 );
457 /** @var \TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration */
458 $configuration = GeneralUtility::makeInstance(Configuration::class)
459 ->setMergeLists(false);
460 $importsFormDefinition = $this->objectManager->get(FalYamlFileLoader::class, $configuration)
461 ->loadFromContent($fakeYaml);
462 $importsFormDefinition = $this->castValuesToNumbers($importsFormDefinition);
463 }
464
465 $formDefinition = $this->castValuesToNumbers($formDefinition);
466 $formDefinition = $this->setIdentifiersAsKeys($formDefinition);
467 $formDefinition = $this->setNewSortings($formDefinition);
468
469 if ($hasImports) {
470 $this->makeFormDefinitionWithImportsDiff($formDefinition, $importsFormDefinition);
471 }
472 return $formDefinition;
473 }
474
475 /**
476 * @param array $array
477 * @return array
478 */
479 public static function castValuesToNumbers(array $array): array
480 {
481 $result = $array;
482 foreach ($result as $key => $value) {
483 if (is_array($value)) {
484 $result[$key] = self::castValuesToNumbers($value);
485 } elseif (MathUtility::canBeInterpretedAsInteger($value)) {
486 $result[$key] = (int)$value;
487 } elseif (MathUtility::canBeInterpretedAsFloat($value)) {
488 $result[$key] = (float)$value;
489 }
490 }
491 return $result;
492 }
493
494 /**
495 * @param array $formDefinition
496 * @return array
497 */
498 protected function setNewSortings(array $formDefinition): array
499 {
500 $result = $formDefinition;
501
502 foreach ($result as $key => $value) {
503 if (is_array($value)) {
504 if (
505 $key === 'renderables'
506 || $key === 'validators'
507 || $key === 'finishers'
508 ) {
509 $sorting = 10;
510 foreach ($value as $identifier => $item) {
511 $value[$identifier]['sorting'] = $sorting;
512 $sorting += 10;
513 }
514 }
515 $result[$key] = $this->setNewSortings($value);
516 }
517 }
518 return $result;
519 }
520
521 /**
522 * @param array $array
523 * @return array
524 */
525 protected function setIdentifiersAsKeys(array $array): array
526 {
527 $result = $array;
528 foreach ($result as $key => $value) {
529 if (is_array($value)) {
530 if (
531 $key === 'renderables'
532 || $key === 'validators'
533 || $key === 'finishers'
534 ) {
535 $newValue = [];
536 foreach ($value as $itemKey => $item) {
537 $newValue[$item['identifier']] = $item;
538 }
539 $value = $newValue;
540 }
541 $result[$key] = $this->setIdentifiersAsKeys($value);
542 }
543 }
544 return $result;
545 }
546
547 /**
548 * @param array $imports
549 * @return string
550 */
551 protected function generateFakeYamlFromImports(array $imports): string
552 {
553 $fakeYaml = 'imports:' . LF;
554 foreach ($imports as $import) {
555 foreach ($import as $resource) {
556 $fakeYaml .= ' - { resource: "' . $resource . '" }' . LF;
557 }
558 }
559 return $fakeYaml;
560 }
561
562 /**
563 * @param array &$newFullFormDefinition
564 * @param array $importsFormDefinition
565 * @param array $path
566 */
567 protected function makeFormDefinitionWithImportsDiff(
568 array &$newFullFormDefinition,
569 array $importsFormDefinition,
570 array $path = []
571 ) {
572 foreach ($importsFormDefinition as $key => $valueFromImportsFormDefinition) {
573 $currentPath = $path;
574 $currentPath[] = $key;
575 $currentPathString = implode('/', $currentPath);
576 if (is_array($valueFromImportsFormDefinition)) {
577 if (!ArrayUtility::isValidPath($newFullFormDefinition, $currentPathString)) {
578 // Overwrite the value within the new formDefinition with null
579 // because the value exists within one of the imports
580 // but not within the new formDefinition which means
581 // that the value should be deleted.
582 $newFullFormDefinition = ArrayUtility::setValueByPath($newFullFormDefinition, $currentPathString, null);
583 } else {
584 $this->makeFormDefinitionWithImportsDiff($newFullFormDefinition, $valueFromImportsFormDefinition, $currentPath);
585 $value = ArrayUtility::getValueByPath($newFullFormDefinition, $currentPathString);
586 // If values are deleted within deeper nestings, the array
587 // keys still exists. If empty arrays exists within the new formDefinition
588 // then they should be removed.
589 if (is_array($value) && empty($value)) {
590 $newFullFormDefinition = ArrayUtility::removeByPath($newFullFormDefinition, $currentPathString);
591 }
592 }
593 } else {
594 if (
595 ArrayUtility::isValidPath($newFullFormDefinition, $currentPathString)
596 && ArrayUtility::getValueByPath($newFullFormDefinition, $currentPathString) === $valueFromImportsFormDefinition
597 ) {
598 // Remove the value within the new formDefinition
599 // because the value already exists within one of the imports.
600 $newFullFormDefinition = ArrayUtility::removeByPath($newFullFormDefinition, $currentPathString);
601 } elseif (!ArrayUtility::isValidPath($newFullFormDefinition, $currentPathString)) {
602 // Overwrite the value within the new formDefinition with null
603 // because the value exists within one of the imports
604 // but not within the new formDefinition which means
605 // that the value should be deleted.
606 $newFullFormDefinition = ArrayUtility::setValueByPath($newFullFormDefinition, $currentPathString, null);
607 }
608 }
609 }
610 }
611
612 /**
613 * Render the "text/x-formeditor-template" templates.
614 *
615 * @param array $formEditorDefinitions
616 * @return string
617 */
618 protected function renderFormEditorTemplates(array $formEditorDefinitions): string
619 {
620 $fluidConfiguration = $this->prototypeConfiguration['formEditor']['formEditorFluidConfiguration'];
621 $formEditorPartials = $this->prototypeConfiguration['formEditor']['formEditorPartials'];
622
623 if (!isset($fluidConfiguration['templatePathAndFilename'])) {
624 throw new RenderingException(
625 'The option templatePathAndFilename must be set.',
626 1485636499
627 );
628 }
629 if (
630 !isset($fluidConfiguration['layoutRootPaths'])
631 || !is_array($fluidConfiguration['layoutRootPaths'])
632 ) {
633 throw new RenderingException(
634 'The option layoutRootPaths must be set.',
635 1480294721
636 );
637 }
638 if (
639 !isset($fluidConfiguration['partialRootPaths'])
640 || !is_array($fluidConfiguration['partialRootPaths'])
641 ) {
642 throw new RenderingException(
643 'The option partialRootPaths must be set.',
644 1480294722
645 );
646 }
647
648 $insertRenderablesPanelConfiguration = $this->getInsertRenderablesPanelConfiguration($formEditorDefinitions['formElements']);
649
650 $view = $this->objectManager->get(TemplateView::class);
651 $view->setControllerContext(clone $this->controllerContext);
652 $view->getRenderingContext()->getTemplatePaths()->fillFromConfigurationArray($fluidConfiguration);
653 $view->setTemplatePathAndFilename($fluidConfiguration['templatePathAndFilename']);
654 $view->assignMultiple([
655 'insertRenderablesPanelConfiguration' => $insertRenderablesPanelConfiguration,
656 'formEditorPartials' => $formEditorPartials,
657 ]);
658
659 return $view->render();
660 }
661
662 /**
663 * Returns the current BE user.
664 *
665 * @return BackendUserAuthentication
666 */
667 protected function getBackendUser(): BackendUserAuthentication
668 {
669 return $GLOBALS['BE_USER'];
670 }
671
672 /**
673 * Returns the language service
674 *
675 * @return LanguageService
676 */
677 protected function getLanguageService(): LanguageService
678 {
679 return $GLOBALS['LANG'];
680 }
681 }