[SECURITY] Filter disallowed properties in form editor
[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\Routing\UriBuilder;
19 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
20 use TYPO3\CMS\Backend\View\BackendTemplateView;
21 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22 use TYPO3\CMS\Core\Imaging\Icon;
23 use TYPO3\CMS\Core\Localization\LanguageService;
24 use TYPO3\CMS\Core\Site\Entity\Site;
25 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
26 use TYPO3\CMS\Core\Utility\ArrayUtility;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
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\Configuration\FormDefinitionConversionService;
32 use TYPO3\CMS\Form\Domain\Exception\RenderingException;
33 use TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory;
34 use TYPO3\CMS\Form\Exception;
35 use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException;
36 use TYPO3\CMS\Form\Service\TranslationService;
37 use TYPO3\CMS\Form\Type\FormDefinitionArray;
38
39 /**
40 * The form editor controller
41 *
42 * Scope: backend
43 */
44 class FormEditorController extends AbstractBackendController
45 {
46
47 /**
48 * Default View Container
49 *
50 * @var BackendTemplateView
51 */
52 protected $defaultViewObjectName = BackendTemplateView::class;
53
54 /**
55 * @var array
56 */
57 protected $prototypeConfiguration;
58
59 /**
60 * Displays the form editor
61 *
62 * @param string $formPersistenceIdentifier
63 * @param string $prototypeName
64 * @throws PersistenceManagerException
65 * @internal
66 */
67 public function indexAction(string $formPersistenceIdentifier, string $prototypeName = null)
68 {
69 $this->registerDocheaderButtons();
70 $this->view->getModuleTemplate()->setModuleName($this->request->getPluginName() . '_' . $this->request->getControllerName());
71 $this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
72
73 if (
74 strpos($formPersistenceIdentifier, 'EXT:') === 0
75 && !$this->formSettings['persistenceManager']['allowSaveToExtensionPaths']
76 ) {
77 throw new PersistenceManagerException('Edit a extension formDefinition is not allowed.', 1478265661);
78 }
79
80 $configurationService = $this->objectManager->get(ConfigurationService::class);
81 $formDefinition = $this->formPersistenceManager->load($formPersistenceIdentifier);
82
83 if ($prototypeName === null) {
84 $prototypeName = $formDefinition['prototypeName'] ?? 'standard';
85 } else {
86 // Loading a form definition with another prototype is currently not implemented but is planned in the future.
87 // This safety check is a preventive measure.
88 $selectablePrototypeNames = $configurationService->getSelectablePrototypeNamesDefinedInFormEditorSetup();
89 if (!in_array($prototypeName, $selectablePrototypeNames, true)) {
90 throw new Exception(sprintf('The prototype name "%s" is not configured within "formManager.selectablePrototypesConfiguration" ', $prototypeName), 1528625039);
91 }
92 }
93
94 $formDefinition['prototypeName'] = $prototypeName;
95 $this->prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName);
96
97 $formDefinition = $this->transformFormDefinitionForFormEditor($formDefinition);
98 $formEditorDefinitions = $this->getFormEditorDefinitions();
99
100 $formEditorAppInitialData = [
101 'formEditorDefinitions' => $formEditorDefinitions,
102 'formDefinition' => $formDefinition,
103 'formPersistenceIdentifier' => $formPersistenceIdentifier,
104 'prototypeName' => $prototypeName,
105 'endpoints' => [
106 'formPageRenderer' => $this->controllerContext->getUriBuilder()->uriFor('renderFormPage'),
107 'saveForm' => $this->controllerContext->getUriBuilder()->uriFor('saveForm')
108 ],
109 'additionalViewModelModules' => $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']['additionalViewModelModules'],
110 'maximumUndoSteps' => $this->prototypeConfiguration['formEditor']['maximumUndoSteps'],
111 ];
112
113 $this->view->assign('formEditorAppInitialData', json_encode($formEditorAppInitialData));
114 $this->view->assign('stylesheets', $this->resolveResourcePaths($this->prototypeConfiguration['formEditor']['stylesheets']));
115 $this->view->assign('formEditorTemplates', $this->renderFormEditorTemplates($formEditorDefinitions));
116 $this->view->assign('dynamicRequireJsModules', $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']);
117
118 $popupWindowWidth = 700;
119 $popupWindowHeight = 750;
120 $popupWindowSize = \trim($this->getBackendUser()->getTSConfig()['options.']['popupWindowSize'] ?? '');
121 if (!empty($popupWindowSize)) {
122 list($popupWindowWidth, $popupWindowHeight) = GeneralUtility::intExplode('x', $popupWindowSize);
123 }
124 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
125 $addInlineSettings = [
126 'FormEditor' => [
127 'typo3WinBrowserUrl' => (string)$uriBuilder->buildUriFromRoute('wizard_element_browser'),
128 ],
129 'Popup' => [
130 'PopupWindow' => [
131 'width' => $popupWindowWidth,
132 'height' => $popupWindowHeight
133 ],
134 ]
135 ];
136
137 $addInlineSettings = array_replace_recursive(
138 $addInlineSettings,
139 $this->prototypeConfiguration['formEditor']['addInlineSettings']
140 );
141 $this->view->assign('addInlineSettings', $addInlineSettings);
142 }
143
144 /**
145 * Initialize the save action.
146 * This action uses the Fluid JsonView::class as view.
147 *
148 * @internal
149 */
150 public function initializeSaveFormAction()
151 {
152 $this->defaultViewObjectName = JsonView::class;
153 }
154
155 /**
156 * Save a formDefinition which was build by the form editor.
157 *
158 * @param string $formPersistenceIdentifier
159 * @param FormDefinitionArray $formDefinition
160 * @internal
161 */
162 public function saveFormAction(string $formPersistenceIdentifier, FormDefinitionArray $formDefinition)
163 {
164 $formDefinition = $formDefinition->getArrayCopy();
165
166 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormSave'] ?? [] as $className) {
167 $hookObj = GeneralUtility::makeInstance($className);
168 if (method_exists($hookObj, 'beforeFormSave')) {
169 $formDefinition = $hookObj->beforeFormSave(
170 $formPersistenceIdentifier,
171 $formDefinition
172 );
173 }
174 }
175
176 $response = [
177 'status' => 'success',
178 ];
179
180 try {
181 $this->formPersistenceManager->save($formPersistenceIdentifier, $formDefinition);
182 $configurationService = $this->objectManager->get(ConfigurationService::class);
183 $this->prototypeConfiguration = $configurationService->getPrototypeConfiguration($formDefinition['prototypeName']);
184 $formDefinition = $this->transformFormDefinitionForFormEditor($formDefinition);
185 $response['formDefinition'] = $formDefinition;
186 } catch (PersistenceManagerException $e) {
187 $response = [
188 'status' => 'error',
189 'message' => $e->getMessage(),
190 'code' => $e->getCode(),
191 ];
192 }
193
194 $this->view->assign('response', $response);
195 // saveFormAction uses the extbase JsonView::class.
196 // That's why we have to set the view variables in this way.
197 $this->view->setVariablesToRender([
198 'response',
199 ]);
200 }
201
202 /**
203 * Render a page from the formDefinition which was build by the form editor.
204 * Use the frontend rendering and set the form framework to preview mode.
205 *
206 * @param FormDefinitionArray $formDefinition
207 * @param int $pageIndex
208 * @param string $prototypeName
209 * @return string
210 * @internal
211 */
212 public function renderFormPageAction(FormDefinitionArray $formDefinition, int $pageIndex, string $prototypeName = null): string
213 {
214 $prototypeName = $prototypeName ?: $formDefinition['prototypeName'] ?? 'standard';
215 $formDefinition = $formDefinition->getArrayCopy();
216
217 $formFactory = $this->objectManager->get(ArrayFormFactory::class);
218 $formDefinition = $formFactory->build($formDefinition, $prototypeName);
219 $formDefinition->setRenderingOption('previewMode', true);
220 $form = $formDefinition->bind($this->request, $this->response);
221 $form->setCurrentSiteLanguage($this->buildFakeSiteLanguage(0, 0));
222 $form->overrideCurrentPage($pageIndex);
223
224 return $form->render();
225 }
226
227 /**
228 * Build a SiteLanguage object to render the form preview with a
229 * specific language.
230 *
231 * @param int $pageId
232 * @param int $languageId
233 * @return SiteLanguage
234 */
235 protected function buildFakeSiteLanguage(int $pageId, int $languageId): SiteLanguage
236 {
237 $fakeSiteConfiguration = [
238 'languages' => [
239 [
240 'languageId' => $languageId,
241 'title' => 'Dummy',
242 'navigationTitle' => '',
243 'typo3Language' => '',
244 'flag' => '',
245 'locale' => '',
246 'iso-639-1' => '',
247 'hreflang' => '',
248 'direction' => '',
249 ],
250 ],
251 ];
252
253 /** @var \TYPO3\CMS\Core\Site\Entity\SiteLanguage $currentSiteLanguage */
254 $currentSiteLanguage = GeneralUtility::makeInstance(Site::class, 'form-dummy', $pageId, $fakeSiteConfiguration)
255 ->getLanguageById($languageId);
256 return $currentSiteLanguage;
257 }
258
259 /**
260 * Prepare the formElements.*.formEditor section from the YAML settings.
261 * Sort all formElements into groups and add additional data.
262 *
263 * @param array $formElementsDefinition
264 * @return array
265 */
266 protected function getInsertRenderablesPanelConfiguration(array $formElementsDefinition): array
267 {
268 $formElementsByGroup = [];
269
270 foreach ($formElementsDefinition as $formElementName => $formElementConfiguration) {
271 if (!isset($formElementConfiguration['group'])) {
272 continue;
273 }
274 if (!isset($formElementsByGroup[$formElementConfiguration['group']])) {
275 $formElementsByGroup[$formElementConfiguration['group']] = [];
276 }
277
278 $formElementConfiguration = TranslationService::getInstance()->translateValuesRecursive(
279 $formElementConfiguration,
280 $this->prototypeConfiguration['formEditor']['translationFile'] ?? null
281 );
282
283 $formElementsByGroup[$formElementConfiguration['group']][] = [
284 'key' => $formElementName,
285 'cssKey' => preg_replace('/[^a-z0-9]/', '-', strtolower($formElementName)),
286 'label' => $formElementConfiguration['label'],
287 'sorting' => $formElementConfiguration['groupSorting'],
288 'iconIdentifier' => $formElementConfiguration['iconIdentifier'],
289 ];
290 }
291
292 $formGroups = [];
293 foreach ($this->prototypeConfiguration['formEditor']['formElementGroups'] ?? [] as $groupName => $groupConfiguration) {
294 if (!isset($formElementsByGroup[$groupName])) {
295 continue;
296 }
297
298 usort($formElementsByGroup[$groupName], function ($a, $b) {
299 return $a['sorting'] - $b['sorting'];
300 });
301 unset($formElementsByGroup[$groupName]['sorting']);
302
303 $groupConfiguration = TranslationService::getInstance()->translateValuesRecursive(
304 $groupConfiguration,
305 $this->prototypeConfiguration['formEditor']['translationFile'] ?? null
306 );
307
308 $formGroups[] = [
309 'key' => $groupName,
310 'elements' => $formElementsByGroup[$groupName],
311 'label' => $groupConfiguration['label'],
312 ];
313 }
314
315 return $formGroups;
316 }
317
318 /**
319 * Reduce the YAML settings by the 'formEditor' keyword.
320 *
321 * @return array
322 */
323 protected function getFormEditorDefinitions(): array
324 {
325 $formEditorDefinitions = [];
326 foreach ([$this->prototypeConfiguration, $this->prototypeConfiguration['formEditor']] as $configuration) {
327 foreach ($configuration as $firstLevelItemKey => $firstLevelItemValue) {
328 if (substr($firstLevelItemKey, -10) !== 'Definition') {
329 continue;
330 }
331 $reducedKey = substr($firstLevelItemKey, 0, -10);
332 foreach ($configuration[$firstLevelItemKey] as $formEditorDefinitionKey => $formEditorDefinitionValue) {
333 if (isset($formEditorDefinitionValue['formEditor'])) {
334 $formEditorDefinitionValue = array_intersect_key($formEditorDefinitionValue, array_flip(['formEditor']));
335 $formEditorDefinitions[$reducedKey][$formEditorDefinitionKey] = $formEditorDefinitionValue['formEditor'];
336 } else {
337 $formEditorDefinitions[$reducedKey][$formEditorDefinitionKey] = $formEditorDefinitionValue;
338 }
339 }
340 }
341 }
342 $formEditorDefinitions = ArrayUtility::reIndexNumericArrayKeysRecursive($formEditorDefinitions);
343 $formEditorDefinitions = TranslationService::getInstance()->translateValuesRecursive(
344 $formEditorDefinitions,
345 $this->prototypeConfiguration['formEditor']['translationFile'] ?? null
346 );
347 return $formEditorDefinitions;
348 }
349
350 /**
351 * Registers the Icons into the docheader
352 *
353 * @throws \InvalidArgumentException
354 */
355 protected function registerDocheaderButtons()
356 {
357 /** @var ButtonBar $buttonBar */
358 $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
359 $getVars = $this->request->getArguments();
360
361 if (isset($getVars['action']) && $getVars['action'] === 'index') {
362 $newPageButton = $buttonBar->makeInputButton()
363 ->setDataAttributes(['action' => 'formeditor-new-page', 'identifier' => 'headerNewPage'])
364 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.new_page_button'))
365 ->setName('formeditor-new-page')
366 ->setValue('new-page')
367 ->setClasses('t3-form-element-new-page-button hidden')
368 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL));
369 /** @var UriBuilder $uriBuilder */
370 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
371
372 $closeButton = $buttonBar->makeLinkButton()
373 ->setDataAttributes(['identifier' => 'closeButton'])
374 ->setHref((string)$uriBuilder->buildUriFromRoute('web_FormFormbuilder'))
375 ->setClasses('t3-form-element-close-form-button hidden')
376 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
377 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
378
379 $saveButton = $buttonBar->makeInputButton()
380 ->setDataAttributes(['identifier' => 'saveButton'])
381 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.save_button'))
382 ->setName('formeditor-save-form')
383 ->setValue('save')
384 ->setClasses('t3-form-element-save-form-button hidden')
385 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
386 ->setShowLabelText(true);
387
388 $formSettingsButton = $buttonBar->makeInputButton()
389 ->setDataAttributes(['identifier' => 'formSettingsButton'])
390 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.form_settings_button'))
391 ->setName('formeditor-form-settings')
392 ->setValue('settings')
393 ->setClasses('t3-form-element-form-settings-button hidden')
394 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-system-extension-configure', Icon::SIZE_SMALL))
395 ->setShowLabelText(true);
396
397 $undoButton = $buttonBar->makeInputButton()
398 ->setDataAttributes(['identifier' => 'undoButton'])
399 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.undo_button'))
400 ->setName('formeditor-undo-form')
401 ->setValue('undo')
402 ->setClasses('t3-form-element-undo-form-button hidden disabled')
403 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-edit-undo', Icon::SIZE_SMALL));
404
405 $redoButton = $buttonBar->makeInputButton()
406 ->setDataAttributes(['identifier' => 'redoButton'])
407 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.redo_button'))
408 ->setName('formeditor-redo-form')
409 ->setValue('redo')
410 ->setClasses('t3-form-element-redo-form-button hidden disabled')
411 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-edit-redo', Icon::SIZE_SMALL));
412
413 $buttonBar->addButton($newPageButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
414 $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
415 $buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
416 $buttonBar->addButton($formSettingsButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
417 $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
418 $buttonBar->addButton($redoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
419 }
420 }
421
422 /**
423 * Render the "text/x-formeditor-template" templates.
424 *
425 * @param array $formEditorDefinitions
426 * @return string
427 */
428 protected function renderFormEditorTemplates(array $formEditorDefinitions): string
429 {
430 $fluidConfiguration = $this->prototypeConfiguration['formEditor']['formEditorFluidConfiguration'] ?? null;
431 $formEditorPartials = $this->prototypeConfiguration['formEditor']['formEditorPartials'] ?? null;
432
433 if (!isset($fluidConfiguration['templatePathAndFilename'])) {
434 throw new RenderingException(
435 'The option templatePathAndFilename must be set.',
436 1485636499
437 );
438 }
439 if (
440 !isset($fluidConfiguration['layoutRootPaths'])
441 || !is_array($fluidConfiguration['layoutRootPaths'])
442 ) {
443 throw new RenderingException(
444 'The option layoutRootPaths must be set.',
445 1480294721
446 );
447 }
448 if (
449 !isset($fluidConfiguration['partialRootPaths'])
450 || !is_array($fluidConfiguration['partialRootPaths'])
451 ) {
452 throw new RenderingException(
453 'The option partialRootPaths must be set.',
454 1480294722
455 );
456 }
457
458 $insertRenderablesPanelConfiguration = $this->getInsertRenderablesPanelConfiguration($formEditorDefinitions['formElements']);
459
460 $view = $this->objectManager->get(TemplateView::class);
461 $view->setControllerContext(clone $this->controllerContext);
462 $view->getRenderingContext()->getTemplatePaths()->fillFromConfigurationArray($fluidConfiguration);
463 $view->setTemplatePathAndFilename($fluidConfiguration['templatePathAndFilename']);
464 $view->assignMultiple([
465 'insertRenderablesPanelConfiguration' => $insertRenderablesPanelConfiguration,
466 'formEditorPartials' => $formEditorPartials,
467 ]);
468
469 return $view->render();
470 }
471
472 /**
473 * @todo move this to FormDefinitionConversionService
474 * @param array $formDefinition
475 * @return array
476 */
477 protected function transformFormDefinitionForFormEditor(array $formDefinition): array
478 {
479 $multiValueProperties = [];
480 foreach ($this->prototypeConfiguration['formElementsDefinition'] as $type => $configuration) {
481 if (!isset($configuration['formEditor']['editors'])) {
482 continue;
483 }
484 foreach ($configuration['formEditor']['editors'] as $editorConfiguration) {
485 if ($editorConfiguration['templateName'] === 'Inspector-PropertyGridEditor') {
486 $multiValueProperties[$type][] = $editorConfiguration['propertyPath'];
487 }
488 }
489 }
490
491 $formDefinition = $this->filterEmptyArrays($formDefinition);
492
493 // @todo: replace with rte parsing
494 $formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition);
495 $formDefinition = $this->transformMultiValueElementsForFormEditor($formDefinition, $multiValueProperties);
496
497 $formDefinitionConversionService = $this->getFormDefinitionConversionService();
498 $formDefinition = $formDefinitionConversionService->addHmacData($formDefinition);
499
500 return $formDefinition;
501 }
502
503 /**
504 * Some data needs a transformation before it can be used by the
505 * form editor. This rules for multivalue elements like select
506 * elements. To ensure the right sorting if the data goes into
507 * javascript, we need to do transformations:
508 *
509 * [
510 * '5' => '5',
511 * '4' => '4',
512 * '3' => '3'
513 * ]
514 *
515 *
516 * This method transform this into:
517 *
518 * [
519 * [
520 * _label => '5'
521 * _value => 5
522 * ],
523 * [
524 * _label => '4'
525 * _value => 4
526 * ],
527 * [
528 * _label => '3'
529 * _value => 3
530 * ],
531 * ]
532 *
533 * @param array $formDefinition
534 * @param array $multiValueProperties
535 * @return array
536 */
537 protected function transformMultiValueElementsForFormEditor(
538 array $formDefinition,
539 array $multiValueProperties
540 ): array {
541 $output = $formDefinition;
542 foreach ($formDefinition as $key => $value) {
543 if (isset($value['type']) && array_key_exists($value['type'], $multiValueProperties)) {
544 $multiValuePropertiesForType = $multiValueProperties[$value['type']];
545 foreach ($multiValuePropertiesForType as $multiValueProperty) {
546 if (!ArrayUtility::isValidPath($value, $multiValueProperty, '.')) {
547 continue;
548 }
549 $multiValuePropertyData = ArrayUtility::getValueByPath($value, $multiValueProperty, '.');
550 if (!is_array($multiValuePropertyData)) {
551 continue;
552 }
553 $newMultiValuePropertyData = [];
554 foreach ($multiValuePropertyData as $k => $v) {
555 $newMultiValuePropertyData[] = [
556 '_label' => $v,
557 '_value' => $k
558 ];
559 }
560 $value = ArrayUtility::setValueByPath($value, $multiValueProperty, $newMultiValuePropertyData, '.');
561 }
562 }
563
564 $output[$key] = $value;
565 if (is_array($value)) {
566 $output[$key] = $this->transformMultiValueElementsForFormEditor($value, $multiValueProperties);
567 }
568 }
569
570 return $output;
571 }
572
573 /**
574 * Remove keys from an array if the key value is an empty array
575 *
576 * @param array $array
577 * @return array
578 */
579 protected function filterEmptyArrays(array $array): array
580 {
581 foreach ($array as $key => $value) {
582 if (!is_array($value)) {
583 continue;
584 }
585 if (empty($value)) {
586 unset($array[$key]);
587 continue;
588 }
589 $array[$key] = $this->filterEmptyArrays($value);
590 if (empty($array[$key])) {
591 unset($array[$key]);
592 }
593 }
594
595 return $array;
596 }
597
598 /**
599 * @return FormDefinitionConversionService
600 */
601 protected function getFormDefinitionConversionService(): FormDefinitionConversionService
602 {
603 return GeneralUtility::makeInstance(FormDefinitionConversionService::class);
604 }
605
606 /**
607 * Returns the current BE user.
608 *
609 * @return BackendUserAuthentication
610 */
611 protected function getBackendUser(): BackendUserAuthentication
612 {
613 return $GLOBALS['BE_USER'];
614 }
615
616 /**
617 * Returns the language service
618 *
619 * @return LanguageService
620 */
621 protected function getLanguageService(): LanguageService
622 {
623 return $GLOBALS['LANG'];
624 }
625 }