[FEATURE] EXT:form - introduce YAML "imports" 10/54210/32
authorRalf Zimmermann <ralf.zimmermann@tritum.de>
Fri, 22 Sep 2017 13:44:44 +0000 (15:44 +0200)
committerJigal van Hemert <jigal.van.hemert@typo3.org>
Tue, 9 Jan 2018 18:11:32 +0000 (19:11 +0100)
The form extension now features imports in YAML configuration files via
the special toplevel "imports" option. With the help of this feature,
form setup and especially form definitions can be reused without copying.

Furthermore, the YAML loading has been centralized and slightly extended
to meet the requirements of the form framework.

The configuration of the form framework has been merged into one file. A
follow up patch will rearrange the configuration to improve clarity and
understandability. This task heavily depends on the "imports"
functionality.

Resolves: #82089
Releases: master
Change-Id: I44f52572ab2d516949dd017ef1face351b448d65
Reviewed-on: https://review.typo3.org/54210
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Mathias Brodala <mbrodala@pagemachine.de>
Tested-by: Mathias Brodala <mbrodala@pagemachine.de>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Jigal van Hemert <jigal.van.hemert@typo3.org>
Tested-by: Jigal van Hemert <jigal.van.hemert@typo3.org>
41 files changed:
typo3/sysext/core/Classes/Configuration/Loader/FalYamlFileLoader.php [new file with mode: 0644]
typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoader.php
typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoader/Configuration.php [new file with mode: 0644]
typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoaderInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Configuration/Writer/Exception/FileWriteException.php [new file with mode: 0644]
typo3/sysext/core/Classes/Configuration/Writer/YamlFileWriter.php [new file with mode: 0644]
typo3/sysext/core/Classes/Resource/ResourceFactory.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-82089-ExtFormYamlConfigurationsTyposcriptOption.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-82089-ExtFormSupportsYamlImports.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Configuration/Loader/YamlFileLoaderTest.php
typo3/sysext/form/Classes/Controller/FormEditorController.php
typo3/sysext/form/Classes/Controller/FormFrontendController.php
typo3/sysext/form/Classes/Controller/FormManagerController.php
typo3/sysext/form/Classes/Domain/Exception/FormDefinitionNotValidException.php [new file with mode: 0644]
typo3/sysext/form/Classes/Domain/Factory/ArrayFormFactory.php
typo3/sysext/form/Classes/Mvc/Configuration/ConfigurationManager.php
typo3/sysext/form/Classes/Mvc/Configuration/Exception/NoConfigurationFoundException.php [new file with mode: 0644]
typo3/sysext/form/Classes/Mvc/Configuration/YamlSource.php [deleted file]
typo3/sysext/form/Classes/Mvc/Persistence/FormPersistenceManager.php
typo3/sysext/form/Classes/Property/TypeConverter/FormDefinitionArrayConverter.php
typo3/sysext/form/Classes/ViewHelpers/RenderViewHelper.php
typo3/sysext/form/Configuration/TypoScript/setup.txt
typo3/sysext/form/Configuration/Yaml/BaseSetup.yaml [deleted file]
typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml [deleted file]
typo3/sysext/form/Configuration/Yaml/FormEngineSetup.yaml [deleted file]
typo3/sysext/form/Configuration/Yaml/FormSetup.yaml [new file with mode: 0644]
typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/EmailSelectEditor.html [new file with mode: 0644]
typo3/sysext/form/Tests/Unit/Mvc/Configuration/ConfigurationManagerTest.php [new file with mode: 0644]
typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File1.yaml [new file with mode: 0644]
typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File2.yaml [new file with mode: 0644]
typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File3.yaml [new file with mode: 0644]
typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File4.yaml [new file with mode: 0644]
typo3/sysext/form/Tests/Unit/Mvc/Configuration/YamlSourceTest.php [deleted file]
typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/ConfigurationManagerTest.php [new file with mode: 0644]
typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File1.yaml [new file with mode: 0644]
typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File2.yaml [new file with mode: 0644]
typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File3.yaml [new file with mode: 0644]
typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File4.yaml [new file with mode: 0644]
typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/Header.yaml [new file with mode: 0644]
typo3/sysext/form/ext_localconf.php
typo3/sysext/form/ext_typoscript_setup.txt [deleted file]

diff --git a/typo3/sysext/core/Classes/Configuration/Loader/FalYamlFileLoader.php b/typo3/sysext/core/Classes/Configuration/Loader/FalYamlFileLoader.php
new file mode 100644 (file)
index 0000000..9aea9f2
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+namespace TYPO3\CMS\Core\Configuration\Loader;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * YAML loader for FAL files
+ */
+class FalYamlFileLoader extends YamlFileLoader
+{
+    /**
+     * @var \TYPO3\CMS\Core\Resource\ResourceFactory
+     */
+    protected $resourceFactory;
+
+    /**
+     * @param Configuration|null $configuration
+     */
+    public function __construct(Configuration $configuration = null, ResourceFactory $resourceFactory = null)
+    {
+        parent::__construct($configuration);
+        $this->resourceFactory = $resourceFactory ?: GeneralUtility::makeInstance(ResourceFactory::class);
+    }
+
+    /**
+     * Loads and parses a YAML file, returns an array with the data found
+     *
+     * @param File|string $fileName either relative to PATH_site or prefixed with EXT:... or File object
+     * @return array the configuration as array
+     */
+    public function load($fileName): array
+    {
+        return $this->loadFromContent($this->getFileContents($fileName));
+    }
+
+    /**
+     * @param File|string $fileName either relative to PATH_site or prefixed with EXT:... or File object
+     * @return string the contents of the file
+     * @throws \RuntimeException when the file was not accessible
+     */
+    protected function getFileContents($fileName): string
+    {
+        $file = null;
+
+        if (is_string($fileName)) {
+            $file = $this->resourceFactory->retrieveFileOrFolderObject($fileName);
+        } elseif (is_object($fileName)) {
+            $file = $fileName;
+        }
+
+        if ($file instanceof File) {
+            $content = $file->getContents();
+
+            if (!$content) {
+                throw new \RuntimeException('YAML file "' . $file->getIdentifier() . '" could not be loaded', 1512561127);
+            }
+
+            return $content;
+        }
+
+        return parent::getFileContents($fileName);
+    }
+}
index eb05f5f..4651ea4 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Core\Configuration\Loader;
  */
 
 use Symfony\Component\Yaml\Yaml;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -25,35 +26,64 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  * - A special "imports" key in the YAML file allows to include other YAML files recursively
  *   where the actual YAML file gets loaded after the import statements, which are interpreted at the very beginning
  *
- * - Merging configuration options of import files when having simple "lists" will add items to the list instead
- *   of overwriting them.
+ * - Merging configuration options of import files when having simple "lists" will add items to the list by default
+ *   instead of overwriting them.
  *
  * - Special placeholder values set via %optionA.suboptionB% replace the value with the named path of the configuration
  *   The placeholders will act as a full replacement of this value.
  */
-class YamlFileLoader
+class YamlFileLoader implements YamlFileLoaderInterface
 {
+    /**
+     * @var Configuration
+     */
+    protected $configuration;
+
+    /**
+     * @param Configuration|null $configuration
+     */
+    public function __construct(Configuration $configuration = null)
+    {
+        $this->configuration = $configuration ?: GeneralUtility::makeInstance(Configuration::class);
+    }
 
     /**
-     * Loads and parses a YAML file, and returns an array with the found data
+     * Loads and parses a YAML file, returns an array with the data found
      *
      * @param string $fileName either relative to PATH_site or prefixed with EXT:...
      * @return array the configuration as array
+     */
+    public function load($fileName): array
+    {
+        if (!is_string($fileName)) {
+            throw new \InvalidArgumentException('The argument "$fileName" must be a string ("' . gettype($fileName) . '" given)', 1512558206);
+        }
+        return $this->loadFromContent($this->getFileContents($fileName));
+    }
+
+    /**
+     * Parses a string as YAML, returns an array with the data found
+     *
+     * @param string $content
+     * @return array the configuration as array
      * @throws \RuntimeException when the file is empty or is of invalid format
      */
-    public function load(string $fileName): array
+    public function loadFromContent(string $content): array
     {
-        $content = $this->getFileContents($fileName);
         $content = Yaml::parse($content);
 
         if (!is_array($content)) {
-            throw new \RuntimeException('YAML file "' . $fileName . '" could not be parsed into valid syntax, probably empty?', 1497332874);
+            throw new \RuntimeException('YAML content could not be parsed into valid syntax, probably empty?', 1497332874);
         }
 
-        $content = $this->processImports($content);
+        if ($this->configuration->getProcessImports()) {
+            $content = $this->processImports($content);
+        }
 
         // Check for "%" placeholders
-        $content = $this->processPlaceholders($content, $content);
+        if ($this->configuration->getProcessPlaceholders()) {
+            $content = $this->processPlaceholders($content, $content);
+        }
 
         return $content;
     }
@@ -66,11 +96,14 @@ class YamlFileLoader
      * @return string the contents of the file
      * @throws \RuntimeException when the file was not accessible
      */
-    protected function getFileContents(string $fileName): string
+    protected function getFileContents($fileName): string
     {
+        if (!is_string($fileName)) {
+            throw new \InvalidArgumentException('The argument "$fileName" must be a string ("' . gettype($fileName) . '" given)', 1512558207);
+        }
         $streamlinedFileName = GeneralUtility::getFileAbsFileName($fileName);
         if (!$streamlinedFileName) {
-            throw new \RuntimeException('YAML File "' . $fileName . '" could not be loaded', 1485784246);
+            throw new \RuntimeException('YAML file "' . $fileName . '" could not be loaded', 1485784246);
         }
         return file_get_contents($streamlinedFileName);
     }
@@ -90,7 +123,9 @@ class YamlFileLoader
                 // override the imported content with the one from the current file
                 $content = $this->merge($importedContent, $content);
             }
-            unset($content['imports']);
+            if ($this->configuration->getRemoveImportsProperty()) {
+                unset($content['imports']);
+            }
         }
         return $content;
     }
@@ -155,8 +190,8 @@ class YamlFileLoader
     }
 
     /**
-     * Same as array_replace_recursive except that when in simple arrays (= YAML lists), the entries are
-     * appended (array_merge)
+     * Same as array_replace_recursive except that when in simple arrays (= YAML lists),
+     * the entries are appended (array_merge) configured accordingly
      *
      * @param array $val1
      * @param array $val2
@@ -166,8 +201,10 @@ class YamlFileLoader
     protected function merge(array $val1, array $val2): array
     {
         // Simple lists get merged / added up
-        if (count(array_filter(array_keys($val1), 'is_int')) === count($val1)) {
-            return array_merge($val1, $val2);
+        if ($this->configuration->getMergeLists()) {
+            if (count(array_filter(array_keys($val1), 'is_int')) === count($val1)) {
+                return array_merge($val1, $val2);
+            }
         }
         foreach ($val1 as $k => $v) {
             // The key also exists in second array, if it is a simple value
diff --git a/typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoader/Configuration.php b/typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoader/Configuration.php
new file mode 100644 (file)
index 0000000..108a25e
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Configuration for YAML file loading
+ */
+class Configuration
+{
+    /**
+     * @var bool
+     */
+    protected $processImports = true;
+
+    /**
+     * @var bool
+     */
+    protected $removeImportsProperty = true;
+
+    /**
+     * @var bool
+     */
+    protected $mergeLists = true;
+
+    /**
+     * @var bool
+     */
+    protected $processPlaceholders = true;
+
+    /**
+     * @return bool
+     */
+    public function getProcessImports(): bool
+    {
+        return $this->processImports;
+    }
+
+    /**
+     * @param bool $processImports
+     * @return Configuration
+     */
+    public function setProcessImports(bool $processImports): self
+    {
+        $this->processImports = $processImports;
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    public function getRemoveImportsProperty(): bool
+    {
+        return $this->removeImportsProperty;
+    }
+
+    /**
+     * @param bool $removeImportsProperty
+     * @return Configuration
+     */
+    public function setRemoveImportsProperty(bool $removeImportsProperty): self
+    {
+        $this->removeImportsProperty = $removeImportsProperty;
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    public function getProcessPlaceholders(): bool
+    {
+        return $this->processPlaceholders;
+    }
+
+    /**
+     * @param bool $processPlaceholders
+     * @return Configuration
+     */
+    public function setProcessPlaceholders(bool $processPlaceholders): self
+    {
+        $this->processPlaceholders = $processPlaceholders;
+        return $this;
+    }
+
+    /**
+     * @return bool
+     */
+    public function getMergeLists(): bool
+    {
+        return $this->mergeLists;
+    }
+
+    /**
+     * @param bool $mergeLists
+     * @return Configuration
+     */
+    public function setMergeLists(bool $mergeLists): self
+    {
+        $this->mergeLists = $mergeLists;
+        return $this;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoaderInterface.php b/typo3/sysext/core/Classes/Configuration/Loader/YamlFileLoaderInterface.php
new file mode 100644 (file)
index 0000000..3748a4d
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+namespace TYPO3\CMS\Core\Configuration\Loader;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Interface for YAML file loaders
+ */
+interface YamlFileLoaderInterface
+{
+    /**
+     * Loads and parses a YAML file, returns an array with the data found
+     *
+     * @param mixed $file
+     * @return array the configuration as array
+     */
+    public function load($file): array;
+
+    /**
+     * Parses a string as YAML, returns an array with the data found
+     *
+     * @param string $content
+     * @return array the configuration as array
+     */
+    public function loadFromContent(string $content): array;
+}
diff --git a/typo3/sysext/core/Classes/Configuration/Writer/Exception/FileWriteException.php b/typo3/sysext/core/Classes/Configuration/Writer/Exception/FileWriteException.php
new file mode 100644 (file)
index 0000000..245a1c5
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace TYPO3\CMS\Core\Configuration\Writer\Exception;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Exception for file write errors
+ */
+class FileWriteException extends \TYPO3\CMS\Core\Exception
+{
+}
diff --git a/typo3/sysext/core/Classes/Configuration/Writer/YamlFileWriter.php b/typo3/sysext/core/Classes/Configuration/Writer/YamlFileWriter.php
new file mode 100644 (file)
index 0000000..8c3eb78
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+namespace TYPO3\CMS\Core\Configuration\Writer;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Symfony\Component\Yaml\Yaml;
+use TYPO3\CMS\Core\Configuration\Writer\Exception\FileWriteException;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * A YAML file writer that allows to write YAML files, based on the Symfony/Yaml component
+ */
+class YamlFileWriter
+{
+
+    /**
+     * Write a YAML file
+     *
+     * @param FILE|string $fileName either relative to PATH_site or prefixed with EXT:... or FILE object
+     * @param array $content The content
+     * @param int $inlineLevel The level where you switch to inline YAML
+     * @param int $indent The amount of spaces to use for indentation of nested nodes
+     * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
+     * @throws FileWriteException if the file could not be written
+     */
+    public function save(
+        $fileName,
+        array $content,
+        int $inlineLevel = 99,
+        int $indent = 2,
+        int $flags = 0
+    ) {
+        $content = Yaml::dump($content, $inlineLevel, $indent, $flags);
+
+        if ($fileName instanceof File) {
+            try {
+                $fileName->setContents($content);
+            } catch (\RuntimeException $e) {
+                throw new FileWriteException($e->getMessage(), 1512582753, $e);
+            }
+        } else {
+            $streamlinedFileName = GeneralUtility::getFileAbsFileName($fileName);
+            if (!$streamlinedFileName) {
+                throw new \FileWriteException('YAML File "' . $fileName . '" could not be loaded', 1485784248);
+            }
+            if (!GeneralUtility::writeFile($streamlinedFileName, $content)) {
+                $error = error_get_last();
+                throw new FileWriteException($error['message'], 1512582929);
+            }
+        }
+    }
+}
index d1fd31b..eab6f69 100644 (file)
@@ -459,7 +459,7 @@ class ResourceFactory implements ResourceFactoryInterface, \TYPO3\CMS\Core\Singl
      * - "file:23"
      *
      * @param string $input
-     * @return File|Folder
+     * @return File|Folder|null
      */
     public function retrieveFileOrFolderObject($input)
     {
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82089-ExtFormYamlConfigurationsTyposcriptOption.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82089-ExtFormYamlConfigurationsTyposcriptOption.rst
new file mode 100644 (file)
index 0000000..9341ee6
--- /dev/null
@@ -0,0 +1,112 @@
+.. include:: ../../Includes.txt
+
+===================================================================
+Deprecation: #82089 - EXT:form yamlConfigurations TypoScript option
+===================================================================
+
+See :issue:`82089`
+See Feature-82089-ExtFormSupportsYamlImports.rst
+
+Description
+===========
+
+The registration of YAML configuration paths for the `form` extension
+via :typoscript:`<module|plugin>.tx_form.settings.yamlConfigurations`
+is deprecated and will be removed in TYPO3 v10.
+
+Instead, a single configuration file must be registered via
+:typoscript:`<module|plugin>.tx_form.settings.configurationFile`.
+
+
+Impact
+======
+
+If paths are added to the TypoScript option
+:typoscript:`<module|plugin>.tx_form.settings.yamlConfigurations`
+a deprecation log entry will be triggered.
+
+
+Affected Installations
+======================
+
+All installations that add paths to the TypoScript option
+:typoscript:`<module|plugin>.tx_form.settings.yamlConfigurations`.
+
+
+Migration
+=========
+
+All occurrences of :typoscript:`<module|plugin>.tx_form.settings.yamlConfigurations`
+must be migrated to :typoscript:`plugin.tx_form.settings.configurationFile`.
+
+
+Form frontend setup
+-------------------
+
+The registration of custom frontend `form` configuration was previously done like this:
+
+.. code-block:: typoscript
+
+   plugin.tx_form {
+       settings {
+           yamlConfigurations {
+               100 = EXT:my_site_package/Configuration/Yaml/CustomFormSetup.yaml
+           }
+       }
+   }
+
+This must be changed to use the new :typoscript:`plugin.tx_form.settings.configurationFile` option:
+
+.. code-block:: typoscript
+
+   plugin.tx_form {
+       settings {
+           configurationFile = EXT:my_site_package/Configuration/Yaml/CustomFormSetup.yaml
+       }
+   }
+
+:file:`EXT:my_site_package/Configuration/Yaml/CustomFormSetup.yaml` should look like this:
+
+.. code-block:: yaml
+
+   imports:
+     - { resource: "EXT:form/Configuration/Yaml/FormSetup.yaml" }
+
+   # Custom form setup configuration
+
+
+Form backend setup (form editor)
+--------------------------------
+
+The registration for custom backend `form` editor configuration was previously done like this:
+
+.. code-block:: typoscript
+
+   module.tx_form {
+       settings {
+           yamlConfigurations {
+               100 = EXT:my_site_package/Configuration/Yaml/CustomFormEditorSetup.yaml
+           }
+       }
+   }
+
+This must be changed to use the new :typoscript:`module.tx_form.settings.configurationFile` option:
+
+.. code-block:: typoscript
+
+   module.tx_form {
+       settings {
+           configurationFile = EXT:my_site_package/Configuration/Yaml/CustomFormEditorSetup.yaml
+       }
+   }
+
+:file:`EXT:my_site_package/Configuration/Yaml/CustomFormEditorSetup.yaml` should look like this:
+
+.. code-block:: yaml
+
+   imports:
+     - { resource: "EXT:form/Configuration/Yaml/FormSetup.yaml" }
+
+   # Custom form editor setup configuration
+
+.. index:: ext:form, Frontend, Backend, NotScanned
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-82089-ExtFormSupportsYamlImports.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-82089-ExtFormSupportsYamlImports.rst
new file mode 100644 (file)
index 0000000..87067d0
--- /dev/null
@@ -0,0 +1,165 @@
+.. include:: ../../Includes.txt
+
+================================================
+Feature: #82089 - EXT:form supports YAML imports
+================================================
+
+See :issue:`82089`
+
+Description
+===========
+
+The `form` extension now features imports in YAML configuration files via the special toplevel
+:yaml:`imports` option. With the help of this feature, form setup and especially form definitions
+can be reused without copying.
+
+
+Form setup configuration
+------------------------
+
+The :yaml:`imports` option can now be used to load the form setup of the `form` extension followed
+by a custom configuration.
+
+For example a :file:`EXT:my_site_package/Configuration/Yaml/FormSetup.yaml` could look like this:
+
+.. code-block:: yaml
+
+    imports:
+      - { resource: "EXT:form/Configuration/Yaml/FormSetup.yaml" }
+      - { resource: "EXT:my_site_package/Configuration/Yaml/FormSetup/Prototypes.yaml" }
+
+You can also combine imports with configuration:
+
+.. code-block:: yaml
+
+    imports:
+      - { resource: "EXT:form/Configuration/Yaml/FormSetup.yaml" }
+
+    TYPO3:
+      CMS:
+        Form:
+          prototypes:
+            # custom configuration
+            # ...
+
+
+Form definitions
+----------------
+
+Imports are also possible within form definitions but must be added manually to the YAML files.
+Currently, the form editor does not have a graphical interface for imports.
+
+The following example shows a basic contact form definition (e.g. in
+:file:`fileadmin/form_definitions/contact.yaml`):
+
+.. code-block:: yaml
+
+    identifier: contact
+    label: 'Contact us'
+    type: Form
+    prototypeName: standard
+    finishers:
+      EmailToReceiver:
+        identifier: EmailToReceiver
+        sorting: 10
+        options:
+          # ...
+    renderables:
+      page-1:
+        identifier: page-1
+        type: Page
+        sorting: 10
+        label: 'Contact Form'
+        renderables:
+          name:
+            identifier: name
+            type: Text
+            label: Name
+            sorting: 10
+            validators:
+              NotEmpty:
+                identifier: NotEmpty
+                sorting: 10
+          subject:
+            identifier: subject
+            type: Text
+            sorting: 20
+            label: Subject
+            validators:
+              NotEmpty:
+                identifier: NotEmpty
+                sorting: 10
+
+Other form definitions can import :file:`fileadmin/form_definitions/contact.yaml` to inherit the
+definitions. Additional form definitions can then be added to extend or change existing definitions.
+
+.. important::
+
+   The form :yaml:`identifier` **must** be changed when importing other form definitions.
+
+You have to do the following in oder to change the form label and to move the :yaml:`subject` field
+before the :yaml:`name` field (see aforementioned
+:file:`fileadmin/form_definitions/another-contact.yaml`):
+
+.. code-block:: yaml
+
+    imports:
+      - { resource: fileadmin/form_definitions/contact.yaml }
+
+    # The identifier MUST be changed
+    identifier: inquiry
+
+    label: Inquiry
+    renderables:
+      page-1:
+        renderables:
+          name:
+            sorting: 20
+          subject:
+            sorting: 10
+
+The key of every section with an :yaml:`identifier` property must be named exactly like the
+:yaml:`identifier` property. This way it is ensured that form definitions importing other form
+definitions and form definitions which are imported are properly merged.
+
+For example, before this feature was introduced a list of :yaml:`finishers` was defined like this:
+
+.. code-block:: yaml
+
+    finishers:
+      -
+        identifier: EmailToReceiver
+        options:
+          # ...
+      -
+        identifier: EmailToSender
+        options:
+          # ...
+      # ...
+
+To guarantee imports work properly this must be rewritten slightly. Please use the :yaml:`identifier`
+value as section key:
+
+.. code-block:: yaml
+
+    finishers:
+      EmailToReceiver:
+        identifier: EmailToReceiver
+        options:
+          # ...
+      EmailToSender:
+        identifier: EmailToSender
+        options:
+          # ...
+      # ...
+
+Aside from this, every section with an :yaml:`identifier` must have a :yaml:`sorting` property. This
+property is essential to detect differences in sortings between the form definition you import and
+the imported form definition.
+
+.. tip::
+
+   Form definitions managed with the form editor are migrated automatically once opened and saved.
+
+
+.. index:: Frontend, Backend, ext:form
\ No newline at end of file
index 3a003d2..c59a4bb 100644 (file)
@@ -190,4 +190,17 @@ betterthanbefore: %firstset.myinitialversion%
         $output = $subject->_call('isPlaceholder', $placeholderValue);
         $this->assertSame($expected, $output);
     }
+
+    /**
+     * @test
+     */
+    public function loadFromContentThrowsExceptionIfContentIsInvalid()
+    {
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1497332874);
+
+        $subject = $this->getAccessibleMock(YamlFileLoader::class, ['dummy']);
+        $input = 'foo bar';
+        $subject->_call('loadFromContent', $input);
+    }
 }
index 227b874..3d9d104 100644 (file)
@@ -18,13 +18,17 @@ namespace TYPO3\CMS\Form\Controller;
 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
 use TYPO3\CMS\Backend\View\BackendTemplateView;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Configuration\Loader\FalYamlFileLoader;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Extbase\Mvc\View\JsonView;
 use TYPO3\CMS\Fluid\View\TemplateView;
 use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
+use TYPO3\CMS\Form\Domain\Exception\InvalidFormDefinitionException;
 use TYPO3\CMS\Form\Domain\Exception\RenderingException;
 use TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory;
 use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException;
@@ -72,11 +76,14 @@ class FormEditorController extends AbstractBackendController
             throw new PersistenceManagerException('Edit a extension formDefinition is not allowed.', 1478265661);
         }
 
-        $formDefinition = $this->formPersistenceManager->load($formPersistenceIdentifier);
+        $prototypeName = $prototypeName ?? $formDefinition['prototypeName'] ?? 'standard';
+        /** @var \TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration */
+        $configuration = GeneralUtility::makeInstance(Configuration::class)
+            ->setRemoveImportsProperty(false)
+            ->setMergeLists(false);
+        $formDefinition = $this->formPersistenceManager->load($formPersistenceIdentifier, $configuration);
         $formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition);
-        if (empty($prototypeName)) {
-            $prototypeName = $formDefinition['prototypeName'] ?? 'standard';
-        }
+        $formDefinition = $this->transformFormDefinitionWithImportsForFormEditor($formDefinition, is_array($formDefinition['imports']));
         $formDefinition['prototypeName'] = $prototypeName;
 
         $configurationService = $this->objectManager->get(ConfigurationService::class);
@@ -152,6 +159,7 @@ class FormEditorController extends AbstractBackendController
     public function saveFormAction(string $formPersistenceIdentifier, FormDefinitionArray $formDefinition)
     {
         $formDefinition = $formDefinition->getArrayCopy();
+
         foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormSave'] ?? [] as $className) {
             $hookObj = GeneralUtility::makeInstance($className);
             if (method_exists($hookObj, 'beforeFormSave')) {
@@ -162,6 +170,12 @@ class FormEditorController extends AbstractBackendController
             }
         }
 
+        $formDefinition = $this->transformFormDefinitionWithImportsForFormFramework(
+            $formPersistenceIdentifier,
+            $formDefinition,
+            is_array($formDefinition['imports'])
+        );
+
         $response = [
             'status' => 'success',
         ];
@@ -196,15 +210,13 @@ class FormEditorController extends AbstractBackendController
      */
     public function renderFormPageAction(FormDefinitionArray $formDefinition, int $pageIndex, string $prototypeName = null): string
     {
-        if (empty($prototypeName)) {
-            $prototypeName = $formDefinition['prototypeName'] ?? 'standard';
-        }
-
+        $prototypeName = $prototypeName ?? $formDefinition['prototypeName'] ?? 'standard';
         $formFactory = $this->objectManager->get(ArrayFormFactory::class);
         $formDefinition = $formFactory->build($formDefinition->getArrayCopy(), $prototypeName);
         $formDefinition->setRenderingOption('previewMode', true);
         $form = $formDefinition->bind($this->request, $this->response);
         $form->overrideCurrentPage($pageIndex);
+
         return $form->render();
     }
 
@@ -217,7 +229,6 @@ class FormEditorController extends AbstractBackendController
      */
     protected function getInsertRenderablesPanelConfiguration(array $formElementsDefinition): array
     {
-        $formElementGroups = $this->prototypeConfiguration['formEditor']['formElementGroups'] ?? [];
         $formElementsByGroup = [];
 
         foreach ($formElementsDefinition as $formElementName => $formElementConfiguration) {
@@ -243,7 +254,7 @@ class FormEditorController extends AbstractBackendController
         }
 
         $formGroups = [];
-        foreach ($formElementGroups as $groupName => $groupConfiguration) {
+        foreach ($this->prototypeConfiguration['formEditor']['formElementGroups'] ?? [] as $groupName => $groupConfiguration) {
             if (!isset($formElementsByGroup[$groupName])) {
                 continue;
             }
@@ -373,6 +384,232 @@ class FormEditorController extends AbstractBackendController
     }
 
     /**
+     * @param array $array
+     * @param bool $hasImports
+     * @return array
+     * @throws PropertyException
+     */
+    protected function transformFormDefinitionWithImportsForFormEditor(array $array, bool $hasImports): array
+    {
+        $result = $array;
+        foreach ($result as $key => $value) {
+            if (is_array($value)) {
+                if (
+                    $key === 'renderables'
+                    || $key === 'validators'
+                    || $key === 'finishers'
+                ) {
+                    if ($hasImports) {
+                        foreach ($value as $itemKey => $item) {
+                            if (is_int($itemKey)) {
+                                throw new InvalidFormDefinitionException(
+                                    'All array keys within "' . $key . '" must be strings.',
+                                    1505505524
+                                );
+                            }
+
+                            if ($itemKey !== $item['identifier']) {
+                                throw new InvalidFormDefinitionException(
+                                    'All items keys within "' . $key . '" must be equal to the "identifier" property.',
+                                    1505505525
+                                );
+                            }
+
+                            if (!isset($item['sorting'])) {
+                                throw new InvalidFormDefinitionException(
+                                    'All items within "' . $key . '" must have a "sorting" property.',
+                                    1505505526
+                                );
+                            }
+                        }
+                    }
+                    // transform string keys to integer keys
+                    $value = array_values($value);
+
+                    if ($hasImports) {
+                        // sort by "sorting"
+                        usort($value, function ($a, $b) {
+                            return (float)$a['sorting'] - (float)$b['sorting'];
+                        });
+                    }
+                }
+                $result[$key] = $this->transformFormDefinitionWithImportsForFormEditor($value, $hasImports);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * @param string $formPersistenceIdentifier
+     * @param array $formDefinition
+     * @param bool $hasImports
+     * @return array
+     */
+    protected function transformFormDefinitionWithImportsForFormFramework(
+        string $formPersistenceIdentifier,
+        array $formDefinition,
+        bool $hasImports
+    ): array {
+        if ($hasImports) {
+            $fakeYaml = $this->generateFakeYamlFromImports(
+                $formDefinition['imports']
+            );
+            /** @var \TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration */
+            $configuration = GeneralUtility::makeInstance(Configuration::class)
+                ->setMergeLists(false);
+            $importsFormDefinition = $this->objectManager->get(FalYamlFileLoader::class, $configuration)
+                ->loadFromContent($fakeYaml);
+            $importsFormDefinition = $this->castValuesToNumbers($importsFormDefinition);
+        }
+
+        $formDefinition = $this->castValuesToNumbers($formDefinition);
+        $formDefinition = $this->setIdentifiersAsKeys($formDefinition);
+        $formDefinition = $this->setNewSortings($formDefinition);
+
+        if ($hasImports) {
+            $this->makeFormDefinitionWithImportsDiff($formDefinition, $importsFormDefinition);
+        }
+        return $formDefinition;
+    }
+
+    /**
+     * @param array $array
+     * @return array
+     */
+    public static function castValuesToNumbers(array $array): array
+    {
+        $result = $array;
+        foreach ($result as $key => $value) {
+            if (is_array($value)) {
+                $result[$key] = self::castValuesToNumbers($value);
+            } elseif (MathUtility::canBeInterpretedAsInteger($value)) {
+                $result[$key] = (int)$value;
+            } elseif (MathUtility::canBeInterpretedAsFloat($value)) {
+                $result[$key] = (float)$value;
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * @param array $formDefinition
+     * @return array
+     */
+    protected function setNewSortings(array $formDefinition): array
+    {
+        $result = $formDefinition;
+
+        foreach ($result as $key => $value) {
+            if (is_array($value)) {
+                if (
+                    $key === 'renderables'
+                    || $key === 'validators'
+                    || $key === 'finishers'
+                ) {
+                    $sorting = 10;
+                    foreach ($value as $identifier => $item) {
+                        $value[$identifier]['sorting'] = $sorting;
+                        $sorting += 10;
+                    }
+                }
+                $result[$key] = $this->setNewSortings($value);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * @param array $array
+     * @return array
+     */
+    protected function setIdentifiersAsKeys(array $array): array
+    {
+        $result = $array;
+        foreach ($result as $key => $value) {
+            if (is_array($value)) {
+                if (
+                    $key === 'renderables'
+                    || $key === 'validators'
+                    || $key === 'finishers'
+                ) {
+                    $newValue = [];
+                    foreach ($value as $itemKey => $item) {
+                        $newValue[$item['identifier']] = $item;
+                    }
+                    $value = $newValue;
+                }
+                $result[$key] = $this->setIdentifiersAsKeys($value);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * @param array $imports
+     * @return string
+     */
+    protected function generateFakeYamlFromImports(array $imports): string
+    {
+        $fakeYaml = 'imports:' . LF;
+        foreach ($imports as $import) {
+            foreach ($import as $resource) {
+                $fakeYaml .= '  - { resource: "' . $resource . '" }' . LF;
+            }
+        }
+        return $fakeYaml;
+    }
+
+    /**
+     * @param array &$newFullFormDefinition
+     * @param array $importsFormDefinition
+     * @param array $path
+     */
+    protected function makeFormDefinitionWithImportsDiff(
+        array &$newFullFormDefinition,
+        array $importsFormDefinition,
+        array $path = []
+    ) {
+        foreach ($importsFormDefinition as $key => $valueFromImportsFormDefinition) {
+            $currentPath = $path;
+            $currentPath[] = $key;
+            $currentPathString = implode('/', $currentPath);
+            if (is_array($valueFromImportsFormDefinition)) {
+                if (!ArrayUtility::isValidPath($newFullFormDefinition, $currentPathString)) {
+                    // Overwrite the value within the new formDefinition with null
+                    // because the value exists within one of the imports
+                    // but not within the new formDefinition which means
+                    // that the value should be deleted.
+                    $newFullFormDefinition = ArrayUtility::setValueByPath($newFullFormDefinition, $currentPathString, null);
+                } else {
+                    $this->makeFormDefinitionWithImportsDiff($newFullFormDefinition, $valueFromImportsFormDefinition, $currentPath);
+                    $value = ArrayUtility::getValueByPath($newFullFormDefinition, $currentPathString);
+                    // If values are deleted within deeper nestings, the array
+                    // keys still exists. If empty arrays exists within the new formDefinition
+                    // then they should be removed.
+                    if (is_array($value) && empty($value)) {
+                        $newFullFormDefinition = ArrayUtility::removeByPath($newFullFormDefinition, $currentPathString);
+                    }
+                }
+            } else {
+                if (
+                    ArrayUtility::isValidPath($newFullFormDefinition, $currentPathString)
+                    && ArrayUtility::getValueByPath($newFullFormDefinition, $currentPathString) === $valueFromImportsFormDefinition
+                ) {
+                    // Remove the value within the new formDefinition
+                    // because the value already exists within one of the imports.
+                    $newFullFormDefinition = ArrayUtility::removeByPath($newFullFormDefinition, $currentPathString);
+                } elseif (!ArrayUtility::isValidPath($newFullFormDefinition, $currentPathString)) {
+                    // Overwrite the value within the new formDefinition with null
+                    // because the value exists within one of the imports
+                    // but not within the new formDefinition which means
+                    // that the value should be deleted.
+                    $newFullFormDefinition = ArrayUtility::setValueByPath($newFullFormDefinition, $currentPathString, null);
+                }
+            }
+        }
+    }
+
+    /**
      * Render the "text/x-formeditor-template" templates.
      *
      * @param array $formEditorDefinitions
index c9e304e..265f911 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Form\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
@@ -55,7 +56,10 @@ class FormFrontendController extends ActionController
     {
         $formDefinition = [];
         if (!empty($this->settings['persistenceIdentifier'])) {
-            $formDefinition = $this->formPersistenceManager->load($this->settings['persistenceIdentifier']);
+            /** @var \TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration */
+            $configuration = $this->objectManager->get(Configuration::class)
+                ->setRemoveImportsProperty(false);
+            $formDefinition = $this->formPersistenceManager->load($this->settings['persistenceIdentifier'], $configuration);
             $formDefinition['persistenceIdentifier'] = $this->settings['persistenceIdentifier'];
             $formDefinition = $this->overrideByTypoScriptSettings($formDefinition);
             $formDefinition = $this->overrideByFlexFormSettings($formDefinition);
index 71f5639..35d7d27 100644 (file)
@@ -15,11 +15,12 @@ namespace TYPO3\CMS\Form\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
-use Symfony\Component\Yaml\Yaml;
 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Backend\View\BackendTemplateView;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Configuration\Loader\FalYamlFileLoader;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
@@ -98,8 +99,10 @@ class FormManagerController extends AbstractBackendController
             throw new FormException(sprintf('No form name', $templatePath), 1472312204);
         }
 
-        $templatePath = GeneralUtility::getFileAbsFileName($templatePath);
-        $form = Yaml::parse(file_get_contents($templatePath));
+        /** @var \TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration */
+        $configuration = GeneralUtility::makeInstance(Configuration::class)
+            ->setMergeLists(false);
+        $form = $this->objectManager->get(FalYamlFileLoader::class, $configuration)->load($templatePath);
         $form['label'] = $formName;
         $form['identifier'] = $this->formPersistenceManager->getUniqueIdentifier($this->convertFormNameToIdentifier($formName));
         $form['prototypeName'] = $prototypeName;
@@ -160,7 +163,12 @@ class FormManagerController extends AbstractBackendController
      */
     public function duplicateAction(string $formName, string $formPersistenceIdentifier, string $savePath)
     {
-        $formToDuplicate = $this->formPersistenceManager->load($formPersistenceIdentifier);
+        /** @var \TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration */
+        $configuration = GeneralUtility::makeInstance(Configuration::class)
+            ->setProcessImports(false)
+            ->setRemoveImportsProperty(false)
+            ->setMergeLists(false);
+        $formToDuplicate = $this->formPersistenceManager->load($formPersistenceIdentifier, $configuration);
         $formToDuplicate['label'] = $formName;
         $formToDuplicate['identifier'] = $this->formPersistenceManager->getUniqueIdentifier($this->convertFormNameToIdentifier($formName));
 
diff --git a/typo3/sysext/form/Classes/Domain/Exception/FormDefinitionNotValidException.php b/typo3/sysext/form/Classes/Domain/Exception/FormDefinitionNotValidException.php
new file mode 100644 (file)
index 0000000..ed76305
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Form\Domain\Exception;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Form\Domain\Exception;
+
+/**
+ * @api
+ */
+class FormDefinitionNotValidException extends Exception
+{
+}
index ea42b2a..f408503 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Form\Domain\Factory;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Object\ObjectManager;
 use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
@@ -49,6 +50,11 @@ class ArrayFormFactory extends AbstractFormFactory
         }
         $persistenceIdentifier = $configuration['persistenceIdentifier'] ?? null;
 
+        $configuration = $this->transformFormDefinitionWithImportsForFormFramework($configuration);
+        unset($configuration['imports']);
+        $configuration = ArrayUtility::convertBooleanStringsToBooleanRecursive($configuration);
+        $configuration = ArrayUtility::removeNullValuesRecursive($configuration);
+
         $prototypeConfiguration = GeneralUtility::makeInstance(ObjectManager::class)
             ->get(ConfigurationService::class)
             ->getPrototypeConfiguration($prototypeName);
@@ -118,4 +124,34 @@ class ArrayFormFactory extends AbstractFormFactory
 
         return $renderable;
     }
+
+    /**
+     * @param array $array
+     * @return array
+     * @internal
+     */
+    protected function transformFormDefinitionWithImportsForFormFramework(array $array): array
+    {
+        $result = $array;
+        foreach ($result as $key => $value) {
+            if (is_array($value)) {
+                if (
+                    $key === 'renderables'
+                    || $key === 'validators'
+                    || $key === 'finishers'
+                ) {
+                    // sort by "sorting"
+                    usort($value, function ($a, $b) {
+                        return (float)$a['sorting'] - (float)$b['sorting'];
+                    });
+
+                    foreach ($value as $itemKey => $item) {
+                        unset($value[$itemKey]['sorting']);
+                    }
+                }
+                $result[$key] = $this->transformFormDefinitionWithImportsForFormFramework($value);
+            }
+        }
+        return $result;
+    }
 }
index a002c65..d523697 100644 (file)
@@ -17,10 +17,14 @@ namespace TYPO3\CMS\Form\Mvc\Configuration;
 
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Configuration\Loader\FalYamlFileLoader;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Configuration\ConfigurationManager as ExtbaseConfigurationManager;
 use TYPO3\CMS\Form\Mvc\Configuration\Exception\ExtensionNameRequiredException;
+use TYPO3\CMS\Form\Mvc\Configuration\Exception\NoConfigurationFoundException;
 
 /**
  * Extend the ExtbaseConfigurationManager to read YAML configurations.
@@ -37,20 +41,6 @@ class ConfigurationManager extends ExtbaseConfigurationManager implements Config
     protected $cache;
 
     /**
-     * @var \TYPO3\CMS\Form\Mvc\Configuration\YamlSource
-     */
-    protected $yamlSource;
-
-    /**
-     * @param \TYPO3\CMS\Form\Mvc\Configuration\YamlSource $yamlSource
-     * @internal
-     */
-    public function injectYamlSource(\TYPO3\CMS\Form\Mvc\Configuration\YamlSource $yamlSource)
-    {
-        $this->yamlSource = $yamlSource;
-    }
-
-    /**
      * @param string $configurationType The kind of configuration to fetch - must be one of the CONFIGURATION_TYPE_* constants
      * @param string $extensionName if specified, the configuration for the given extension will be returned.
      * @param string $pluginName if specified, the configuration for the given plugin will be returned.
@@ -68,14 +58,14 @@ class ConfigurationManager extends ExtbaseConfigurationManager implements Config
     }
 
     /**
-     * Load and parse YAML files which are configured within the TypoScript
-     * path plugin.tx_extensionkey.settings.yamlConfigurations
+     * Load and parse a YAML configuration which is configured within
+     * plugin.tx_form.settings.configurationFile
      *
      * The following steps will be done:
      *
-     * * Convert each singe YAML file into an array
-     * * merge this arrays together
+     * * load a YAML file into an array
      * * resolve all declared inheritances
+     * * convert all boolean strings ('true' / 'false') into boolean values
      * * remove all keys if their values are NULL
      * * return all configuration paths within TYPO3.CMS
      * * sort by array keys, if all keys within the current nesting level are numerical keys
@@ -84,6 +74,7 @@ class ConfigurationManager extends ExtbaseConfigurationManager implements Config
      * @param string $extensionName
      * @return array
      * @throws ExtensionNameRequiredException
+     * @throws NoConfigurationFoundException
      */
     protected function getConfigurationFromYamlFile(string $extensionName): array
     {
@@ -97,25 +88,47 @@ class ConfigurationManager extends ExtbaseConfigurationManager implements Config
 
         $typoscriptSettings = $this->getTypoScriptSettings($extensionName);
 
-        $yamlSettingsFilePaths = isset($typoscriptSettings['yamlConfigurations'])
-            ? ArrayUtility::sortArrayWithIntegerKeys($typoscriptSettings['yamlConfigurations'])
-            : [];
-
-        $cacheKeySuffix = $extensionName . md5(json_encode($yamlSettingsFilePaths));
+        $cacheKeySuffix = $extensionName;
+        if (isset($typoscriptSettings['configurationFile'])) {
+            $cacheKeySuffix .= md5($typoscriptSettings['configurationFile']);
+        } elseif (isset($typoscriptSettings['yamlConfigurations'])) {
+            $cacheKeySuffix .= md5(json_encode($typoscriptSettings['yamlConfigurations']));
+        }
 
         $yamlSettings = $this->getYamlSettingsFromCache($cacheKeySuffix);
         if (!empty($yamlSettings)) {
             return $this->overrideConfigurationByTypoScript($yamlSettings, $extensionName);
         }
 
-        $yamlSettings = InheritancesResolverService::create($this->yamlSource->load($yamlSettingsFilePaths))
-            ->getResolvedConfiguration();
+        if (isset($typoscriptSettings['configurationFile'])) {
+            $configuration = $this->objectManager->get(Configuration::class)
+                ->setMergeLists(false);
+            $yamlSettings = $this->objectManager->get(FalYamlFileLoader::class, $configuration)
+                ->load($typoscriptSettings['configurationFile']);
+        } elseif (isset($typoscriptSettings['yamlConfigurations'])) {
+            trigger_error('EXT:form configuration registration via "<module|plugin>.tx_form.settings.yamlConfigurations" has been deprecated in v9 and will be removed in v10. Use "<module|plugin>.tx_form.settings.configurationFile" instead.', E_USER_DEPRECATED);
+            $yamlContent = $this->generateYamlFromLegacyYamlConfigurations(
+                $typoscriptSettings['yamlConfigurations']
+            );
+            $yamlSettings = $this->objectManager->get(YamlFileLoader::class)
+                ->loadFromContent($yamlContent);
+        } else {
+            throw new NoConfigurationFoundException(
+                'No YAML configurations could be found for extension ' . $extensionName,
+                1471473378
+            );
+        }
 
+        $yamlSettings = ArrayUtility::convertBooleanStringsToBooleanRecursive($yamlSettings);
         $yamlSettings = ArrayUtility::removeNullValuesRecursive($yamlSettings);
+        $yamlSettings = InheritancesResolverService::create($yamlSettings)
+            ->getResolvedConfiguration();
+
         $yamlSettings = is_array($yamlSettings['TYPO3']['CMS'][$ucFirstExtensioName])
             ? $yamlSettings['TYPO3']['CMS'][$ucFirstExtensioName]
             : [];
         $yamlSettings = ArrayUtility::sortArrayWithIntegerKeysRecursive($yamlSettings);
+
         $this->setYamlSettingsIntoCache($cacheKeySuffix, $yamlSettings);
 
         return $this->overrideConfigurationByTypoScript($yamlSettings, $extensionName);
@@ -199,4 +212,48 @@ class ConfigurationManager extends ExtbaseConfigurationManager implements Config
             $extensionName
         );
     }
+
+    /**
+     * Compatibility layer for the deprecated TypoScript option
+     * "plugin.tx_form.settings.yamlConfigurations"
+     *
+     * @param array $yamlConfigurations
+     * @return string
+     * @internal
+     */
+    protected function generateYamlFromLegacyYamlConfigurations(array $yamlConfigurations): string
+    {
+        $yamlConfigurations = ArrayUtility::sortArrayWithIntegerKeys($yamlConfigurations);
+        $yamlContent = 'imports:' . LF;
+
+        $baseExtFormConfigurations = [
+            'EXT:form/Configuration/Yaml/BaseSetup.yaml',
+            'EXT:form/Configuration/Yaml/FormEditorSetup.yaml',
+            'EXT:form/Configuration/Yaml/FormEngineSetup.yaml',
+            'EXT:form/Configuration/Yaml/FormSetup.yaml',
+        ];
+
+        $imports = '';
+        $baseExtFormConfigurationsExists = false;
+        foreach ($yamlConfigurations as $yamlConfiguration) {
+            if (in_array($yamlConfiguration, $baseExtFormConfigurations)) {
+                $baseExtFormConfigurationsExists = true;
+            } else {
+                $imports .= '  - { resource: "' . $yamlConfiguration . '" }' . LF;
+            }
+        }
+
+        // We assume that if one of the files defined within $baseExtFormConfigurations exists
+        // within plugin.tx_form.settings.yamlConfigurations, someone wants to load
+        // the base EXT:form setup files (old or new) and afterwards extend it with his own configuration.
+        // In this case, we define the new EXT:form/Configuration/Yaml/FormSetup.yaml file as the
+        // first file to import from.
+        if ($baseExtFormConfigurationsExists) {
+            $yamlContent .= '  - { resource: "EXT:form/Configuration/Yaml/FormSetup.yaml" }' . LF . $imports;
+        } else {
+            $yamlContent .= $imports;
+        }
+
+        return $yamlContent;
+    }
 }
diff --git a/typo3/sysext/form/Classes/Mvc/Configuration/Exception/NoConfigurationFoundException.php b/typo3/sysext/form/Classes/Mvc/Configuration/Exception/NoConfigurationFoundException.php
new file mode 100644 (file)
index 0000000..e46394d
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace TYPO3\CMS\Form\Mvc\Configuration\Exception;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Form\Mvc\Configuration\Exception;
+
+/**
+ * A No Such File exception
+ */
+class NoConfigurationFoundException extends Exception
+{
+}
diff --git a/typo3/sysext/form/Classes/Mvc/Configuration/YamlSource.php b/typo3/sysext/form/Classes/Mvc/Configuration/YamlSource.php
deleted file mode 100644 (file)
index abce0d4..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-<?php
-declare(strict_types=1);
-namespace TYPO3\CMS\Form\Mvc\Configuration;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It originated from the Neos.Form package (www.neos.io)
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use Symfony\Component\Yaml\Exception\ParseException;
-use Symfony\Component\Yaml\Yaml;
-use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
-use TYPO3\CMS\Core\Resource\File;
-use TYPO3\CMS\Core\Utility\ArrayUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Form\Mvc\Configuration\Exception\FileWriteException;
-use TYPO3\CMS\Form\Mvc\Configuration\Exception\NoSuchFileException;
-use TYPO3\CMS\Form\Mvc\Configuration\Exception\ParseErrorException;
-
-/**
- * Configuration source based on YAML files
- *
- * Scope: frontend / backend
- * @internal
- */
-class YamlSource
-{
-    /**
-     * Will be set if the PHP YAML Extension is installed.
-     * Having this installed massively improves YAML parsing performance.
-     *
-     * @var bool
-     * @see http://pecl.php.net/package/yaml
-     */
-    protected $usePhpYamlExtension = false;
-
-    /**
-     * Use PHP YAML Extension if installed.
-     * @internal
-     */
-    public function __construct()
-    {
-        if (extension_loaded('yaml')) {
-            $this->usePhpYamlExtension = true;
-        }
-    }
-
-    /**
-     * Loads the specified configuration files and returns its merged content
-     * as an array.
-     *
-     * @param array $filesToLoad
-     * @return array
-     * @throws ParseErrorException
-     * @throws NoSuchFileException
-     * @internal
-     */
-    public function load(array $filesToLoad): array
-    {
-        $configuration = [];
-        foreach ($filesToLoad as $fileToLoad) {
-            if ($fileToLoad instanceof File) {
-                $fileIdentifier = $fileToLoad->getIdentifier();
-                $rawYamlContent = $fileToLoad->getContents();
-                if ($rawYamlContent === false) {
-                    throw new NoSuchFileException(
-                        'The file "' . $fileToLoad . '" does not exist.',
-                        1498802253
-                    );
-                }
-            } else {
-                $fileIdentifier = $fileToLoad;
-                $fileToLoad = GeneralUtility::getFileAbsFileName($fileToLoad);
-                if (is_file($fileToLoad)) {
-                    $rawYamlContent = file_get_contents($fileToLoad);
-                } else {
-                    throw new NoSuchFileException(
-                        'The file "' . $fileToLoad . '" does not exist.',
-                        1471473378
-                    );
-                }
-            }
-
-            try {
-                if ($this->usePhpYamlExtension) {
-                    $loadedConfiguration = @yaml_parse($rawYamlContent);
-                    if ($loadedConfiguration === false) {
-                        throw new ParseErrorException(
-                            'A parse error occurred while parsing file "' . $fileIdentifier . '".',
-                            1391894094
-                        );
-                    }
-                } else {
-                    $loadedConfiguration = Yaml::parse($rawYamlContent);
-                }
-
-                if (is_array($loadedConfiguration)) {
-                    ArrayUtility::mergeRecursiveWithOverrule($configuration, $loadedConfiguration);
-                }
-            } catch (ParseException $exception) {
-                throw new ParseErrorException(
-                    'An error occurred while parsing file "' . $fileIdentifier . '": ' . $exception->getMessage(),
-                    1480195405
-                );
-            }
-        }
-
-        $configuration = ArrayUtility::convertBooleanStringsToBooleanRecursive($configuration);
-        return $configuration;
-    }
-
-    /**
-     * Save the specified configuration array to the given file in YAML format.
-     *
-     * @param File|string $fileToSave The file to write to.
-     * @param array $configuration The configuration to save
-     * @throws FileWriteException if the file could not be written
-     * @internal
-     */
-    public function save($fileToSave, array $configuration)
-    {
-        try {
-            $header = $this->getHeaderFromFile($fileToSave);
-        } catch (InsufficientFileAccessPermissionsException  $e) {
-            throw new FileWriteException($e->getMessage(), 1512584488, $e);
-        }
-
-        $yaml = Yaml::dump($configuration, 99, 2);
-
-        if ($fileToSave instanceof File) {
-            try {
-                $fileToSave->setContents($header . LF . $yaml);
-            } catch (InsufficientFileAccessPermissionsException $e) {
-                throw new FileWriteException($e->getMessage(), 1512582753, $e);
-            }
-        } else {
-            $byteCount = @file_put_contents($fileToSave, $header . LF . $yaml);
-
-            if ($byteCount === false) {
-                $error = error_get_last();
-                throw new FileWriteException($error['message'], 1512582929);
-            }
-        }
-
-        return $return;
-    }
-
-    /**
-     * Read the header part from the given file. That means, every line
-     * until the first non comment line is found.
-     *
-     * @param File|string $file
-     * @return string The header of the given YAML file
-     */
-    protected function getHeaderFromFile($file): string
-    {
-        $header = '';
-        if ($file instanceof File) {
-            $fileLines = explode(LF, $file->getContents());
-        } elseif (is_file($file)) {
-            $fileLines = file($file);
-        } else {
-            return '';
-        }
-
-        foreach ($fileLines as $line) {
-            if (preg_match('/^#/', $line)) {
-                $header .= $line;
-            } else {
-                break;
-            }
-        }
-        return $header;
-    }
-}
index 3b5acdf..bfd4f3f 100644 (file)
@@ -17,6 +17,10 @@ namespace TYPO3\CMS\Form\Mvc\Persistence;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Configuration\Loader\FalYamlFileLoader;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration as LoadConfiguration;
+use TYPO3\CMS\Core\Configuration\Writer\Exception\FileWriteException;
+use TYPO3\CMS\Core\Configuration\Writer\YamlFileWriter;
 use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
 use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
 use TYPO3\CMS\Core\Resource\File;
@@ -27,7 +31,6 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Extbase\Object\ObjectManager;
 use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface;
-use TYPO3\CMS\Form\Mvc\Configuration\Exception\FileWriteException;
 use TYPO3\CMS\Form\Mvc\Persistence\Exception\NoUniqueIdentifierException;
 use TYPO3\CMS\Form\Mvc\Persistence\Exception\NoUniquePersistenceIdentifierException;
 use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException;
@@ -41,11 +44,6 @@ class FormPersistenceManager implements FormPersistenceManagerInterface
 {
 
     /**
-     * @var \TYPO3\CMS\Form\Mvc\Configuration\YamlSource
-     */
-    protected $yamlSource;
-
-    /**
      * @var \TYPO3\CMS\Core\Resource\StorageRepository
      */
     protected $storageRepository;
@@ -56,15 +54,6 @@ class FormPersistenceManager implements FormPersistenceManagerInterface
     protected $formSettings;
 
     /**
-     * @param \TYPO3\CMS\Form\Mvc\Configuration\YamlSource $yamlSource
-     * @internal
-     */
-    public function injectYamlSource(\TYPO3\CMS\Form\Mvc\Configuration\YamlSource $yamlSource)
-    {
-        $this->yamlSource = $yamlSource;
-    }
-
-    /**
      * @param \TYPO3\CMS\Core\Resource\StorageRepository $storageRepository
      * @internal
      */
@@ -88,11 +77,12 @@ class FormPersistenceManager implements FormPersistenceManagerInterface
      * Only files with the extension .yaml are loaded.
      *
      * @param string $persistenceIdentifier
+     * @param LoadConfiguration $loadConfiguration
      * @return array
      * @throws PersistenceManagerException
      * @internal
      */
-    public function load(string $persistenceIdentifier): array
+    public function load(string $persistenceIdentifier, LoadConfiguration $loadConfiguration = null): array
     {
         if (pathinfo($persistenceIdentifier, PATHINFO_EXTENSION) !== 'yaml') {
             throw new PersistenceManagerException(sprintf('The file "%s" could not be loaded.', $persistenceIdentifier), 1477679819);
@@ -108,7 +98,9 @@ class FormPersistenceManager implements FormPersistenceManagerInterface
         }
 
         try {
-            $yaml = $this->yamlSource->load([$file]);
+            $yaml = GeneralUtility::makeInstance(ObjectManager::class)
+                ->get(FalYamlFileLoader::class, $loadConfiguration)
+                ->load($file);
         } catch (\Exception $e) {
             $yaml = [
                 'identifier' => $file->getCombinedIdentifier(),
@@ -150,13 +142,15 @@ class FormPersistenceManager implements FormPersistenceManagerInterface
             if (!array_key_exists(pathinfo($persistenceIdentifier, PATHINFO_DIRNAME) . '/', $this->getAccessibleExtensionFolders())) {
                 throw new PersistenceManagerException(sprintf('The file "%s" could not be saved.', $persistenceIdentifier), 1484073571);
             }
-            $fileToSave = GeneralUtility::getFileAbsFileName($persistenceIdentifier);
+            $fileToSave = $persistenceIdentifier;
         } else {
             $fileToSave = $this->getOrCreateFile($persistenceIdentifier);
         }
 
         try {
-            $this->yamlSource->save($fileToSave, $formDefinition);
+            GeneralUtility::makeInstance(ObjectManager::class)
+                ->get(YamlFileWriter::class)
+                ->save($fileToSave, $formDefinition);
         } catch (FileWriteException $e) {
             throw new PersistenceManagerException(sprintf(
                 'The file "%s" could not be saved: %s',
index 4a8089e..0416731 100644 (file)
@@ -63,7 +63,7 @@ class FormDefinitionArrayConverter extends AbstractTypeConverter
         }
 
         $rawFormDefinitionArray = ArrayUtility::stripTagsFromValuesRecursive($rawFormDefinitionArray);
-        $rawFormDefinitionArray = $this->convertJsonArrayToAssociativeArray($rawFormDefinitionArray);
+        $rawFormDefinitionArray = $this->transformMultivalueElementsForFormFramework($rawFormDefinitionArray);
         $formDefinitionArray = new FormDefinitionArray($rawFormDefinitionArray);
 
         return $formDefinitionArray;
@@ -88,23 +88,20 @@ class FormDefinitionArrayConverter extends AbstractTypeConverter
      * @param array $input
      * @return array
      */
-    protected function convertJsonArrayToAssociativeArray(array $input): array
+    protected function transformMultivalueElementsForFormFramework(array $input): array
     {
         $output = [];
-
         foreach ($input as $key => $value) {
             if (is_int($key) && is_array($value) && isset($value['_label']) && isset($value['_value'])) {
                 $key = $value['_value'];
                 $value = $value['_label'];
             }
-
             if (is_array($value)) {
-                $output[$key] = $this->convertJsonArrayToAssociativeArray($value);
+                $output[$key] = $this->transformMultivalueElementsForFormFramework($value);
             } else {
                 $output[$key] = $value;
             }
         }
-
         return $output;
     }
 }
index a80a681..9f4443e 100644 (file)
@@ -81,7 +81,7 @@ class RenderViewHelper extends AbstractViewHelper
         $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
         if (!empty($persistenceIdentifier)) {
             $formPersistenceManager = $objectManager->get(FormPersistenceManagerInterface::class);
-            $formConfiguration = $formPersistenceManager->load($persistenceIdentifier);
+            $formConfiguration = $formPersistenceManager->load($persistenceIdentifier, true, false);
             ArrayUtility::mergeRecursiveWithOverrule(
                 $formConfiguration,
                 $overrideConfiguration
index eb81df0..73de4a9 100644 (file)
@@ -17,10 +17,7 @@ plugin.tx_form {
     }
 
     settings {
-        yamlConfigurations {
-            10 = EXT:form/Configuration/Yaml/BaseSetup.yaml
-            20 = EXT:form/Configuration/Yaml/FormEngineSetup.yaml
-        }
+        configurationFile = EXT:form/Configuration/Yaml/FormSetup.yaml
     }
 }
 
@@ -30,4 +27,4 @@ lib.tx_form.contentElementRendering {
     tables = tt_content
     source.current = 1
     dontCheckPid = 1
-}
\ No newline at end of file
+}
diff --git a/typo3/sysext/form/Configuration/Yaml/BaseSetup.yaml b/typo3/sysext/form/Configuration/Yaml/BaseSetup.yaml
deleted file mode 100644 (file)
index bb445df..0000000
+++ /dev/null
@@ -1,455 +0,0 @@
-TYPO3:
-  CMS:
-    Form:
-      persistenceManager:
-        allowedFileMounts:
-          10: 1:/form_definitions/
-          20: 1:/user_upload/
-        allowSaveToExtensionPaths: false
-        allowDeleteFromExtensionPaths: false
-        #allowedExtensionPaths:
-          #10: EXT:example/Resources/Private/Forms/
-
-      prototypes:
-        standard:
-
-          ########### DEFAULT FORM ELEMENT DEFINITIONS ###########
-          formElementsDefinition:
-
-            ### BASE ELEMENTS ###
-            Form:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseFormElementMixin'
-              rendererClassName: 'TYPO3\CMS\Form\Domain\Renderer\FluidFormRenderer'
-              renderingOptions:
-                __inheritances:
-                  10: 'TYPO3.CMS.Form.mixins.translationSettingsMixin'
-                templateRootPaths:
-                  10: 'EXT:form/Resources/Private/Frontend/Templates/'
-                partialRootPaths:
-                  10: 'EXT:form/Resources/Private/Frontend/Partials/'
-                layoutRootPaths:
-                  10: 'EXT:form/Resources/Private/Frontend/Layouts/'
-                addQueryString: false
-                argumentsToBeExcludedFromQueryString: []
-                additionalParams: []
-                controllerAction: perform
-                httpMethod: post
-                httpEnctype: 'multipart/form-data'
-                _isCompositeFormElement: false
-                _isTopLevelFormElement: true
-
-                honeypot:
-                  enable: true
-                  formElementToUse: 'Honeypot'
-
-                submitButtonLabel: 'Submit'
-
-                # set this to TRUE if you want to avoid exceptions for FormElements without definitions
-                skipUnknownElements: true
-
-            ### FORM ELEMENTS: CONTAINER ###
-            Page:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseFormElementMixin'
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\Page'
-              renderingOptions:
-                _isTopLevelFormElement: true
-                _isCompositeFormElement: true
-                nextButtonLabel: 'next Page'
-                previousButtonLabel: 'previous Page'
-
-            SummaryPage:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.prototypes.standard.formElementsDefinition.Page'
-              renderingOptions:
-                _isTopLevelFormElement: true
-                _isCompositeFormElement: false
-                nextButtonLabel: 'next Page'
-                previousButtonLabel: 'previous Page'
-
-            Fieldset:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\Section'
-              renderingOptions:
-                _isCompositeFormElement: true
-
-            GridContainer:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\GridContainer'
-              renderingOptions:
-                _isCompositeFormElement: true
-                _isGridContainerFormElement: true
-              properties:
-                elementClassAttribute: 'container'
-                # overrules 'GridRow.properties.gridColumnClassAutoConfiguration'
-                gridColumnClassAutoConfiguration:
-                  gridSize: 12
-                  viewPorts:
-                    xs:
-                      classPattern: 'col-xs-{@numbersOfColumnsToUse}'
-                    sm:
-                      classPattern: 'col-sm-{@numbersOfColumnsToUse}'
-                    md:
-                      classPattern: 'col-md-{@numbersOfColumnsToUse}'
-                    lg:
-                      classPattern: 'col-lg-{@numbersOfColumnsToUse}'
-
-            GridRow:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\GridRow'
-              properties:
-                elementClassAttribute: 'row'
-                gridColumnClassAutoConfiguration:
-                  gridSize: 12
-                  viewPorts:
-                    xs:
-                      classPattern: 'col-xs-{@numbersOfColumnsToUse}'
-                    sm:
-                      classPattern: 'col-sm-{@numbersOfColumnsToUse}'
-                    md:
-                      classPattern: 'col-md-{@numbersOfColumnsToUse}'
-                    lg:
-                      classPattern: 'col-lg-{@numbersOfColumnsToUse}'
-              renderingOptions:
-                _isCompositeFormElement: true
-                _isGridRowFormElement: true
-
-            ### FORM ELEMENTS: INPUT ###
-            Text:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.TextMixin'
-
-            Password:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.TextMixin'
-
-            AdvancedPassword:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.prototypes.standard.formElementsDefinition.Password'
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
-              properties:
-                elementClassAttribute: 'input-medium'
-                confirmationLabel: ''
-                # Optional description (hint) for the first password input element
-                #passwordDescription: ''
-                confirmationClassAttribute: 'input-medium'
-
-            Textarea:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.TextMixin'
-              properties:
-                elementClassAttribute: 'xxlarge'
-
-            Honeypot:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.TextMixin'
-              properties:
-                renderAsHiddenField: false
-                styleAttribute: 'position:absolute; margin:0 0 0 -999em;'
-              renderingOptions:
-                _isHiddenFormElement: true
-
-            Hidden:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-              renderingOptions:
-                _isHiddenFormElement: true
-
-            ### FORM ELEMENTS: HTML5 ###
-            Email:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.TextMixin'
-              validators:
-                -
-                  identifier: EmailAddress
-
-            Telephone:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.TextMixin'
-              validators:
-                -
-                  identifier: RegularExpression
-                  options:
-                    regularExpression: '/^.*$/'
-
-            Url:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.TextMixin'
-              validators:
-                -
-                  identifier: RegularExpression
-                  options:
-                    regularExpression: '/^.*$/'
-
-            Number:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.TextMixin'
-              validators:
-                -
-                  identifier: Number
-
-            ### FORM ELEMENTS: SELECT ###
-            Checkbox:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-              properties:
-                elementClassAttribute: 'add-on'
-                containerClassAttribute: 'input checkbox'
-                value: 1
-
-            MultiCheckbox:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.MultiSelectionMixin'
-              properties:
-                containerClassAttribute: 'input checkbox'
-
-            MultiSelect:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.MultiSelectionMixin'
-              properties:
-                elementClassAttribute: 'xlarge'
-
-            RadioButton:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.SingleSelectionMixin'
-              properties:
-                elementClassAttribute: 'xlarge'
-
-            SingleSelect:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.SingleSelectionMixin'
-
-            ### FORM ELEMENTS: CUSTOM ###
-            DatePicker:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\DatePicker'
-              properties:
-                elementClassAttribute: 'small form-control'
-                timeSelectorClassAttribute: 'mini'
-                timeSelectorHourLabel: ''
-                timeSelectorMinuteLabel: ''
-                dateFormat: 'Y-m-d'
-                enableDatePicker: true
-                displayTimeSelector: false
-
-            StaticText:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.ReadOnlyFormElementMixin'
-              properties:
-                text: ''
-
-            ContentElement:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.ReadOnlyFormElementMixin'
-              properties:
-                contentElementUid: ''
-
-            ### FORM ELEMENTS: UPLOADS ###
-            FileUpload:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FileUploadMixin'
-              properties:
-                allowedMimeTypes: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.oasis.opendocument.text', 'application/pdf']
-
-            ImageUpload:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.FileUploadMixin'
-              properties:
-                allowedMimeTypes: ['image/jpeg', 'image/png', 'image/bmp']
-                elementClassAttribute: 'lightbox'
-                imageLinkMaxWidth: 500
-                imageMaxWidth: 500
-                imageMaxHeight: 500
-
-          ### FINISHERS ###
-
-          finishersDefinition:
-            Closure:
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Finishers\ClosureFinisher'
-              options:
-                #closure:
-
-            Confirmation:
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Finishers\ConfirmationFinisher'
-              #options:
-                #message: ''
-                #contentElementUid: 0
-                #typoscriptObjectPath: 'lib.tx_form.contentElementRendering'
-
-            EmailToSender:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.finishersEmailMixin'
-
-            EmailToReceiver:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.finishersEmailMixin'
-
-            DeleteUploads:
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Finishers\DeleteUploadsFinisher'
-
-            FlashMessage:
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Finishers\FlashMessageFinisher'
-              #options:
-                #messageBody: ''
-                #messageTitle: ''
-                #messageArguments: {}
-                #messageCode: 0
-                #severity: 0
-
-            Redirect:
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Finishers\RedirectFinisher'
-              #options:
-                #pageUid: 1
-                #additionalParameters: ''
-                #delay: 0
-                #statusCode: 303
-
-            SaveToDatabase:
-              implementationClassName: 'TYPO3\CMS\Form\Domain\Finishers\SaveToDatabaseFinisher'
-              #options:
-                #table: ''
-                #mode: 'insert'
-                #whereClause: []
-                #elements:
-                #  <elementIdentifier>:
-                #    mapOnDatabaseColumn: <databaseColumnName>
-                #    saveFileIdentifierInsteadOfUid: false
-                #    skipIfValueIsEmpty: false
-                #databaseColumnMappings:
-                #  <databaseColumnName>:
-                #    value: 'someValue'
-                #    skipIfValueIsEmpty: false
-
-          ### VALIDATORS ###
-          validatorsDefinition:
-            NotEmpty:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator'
-            DateTime:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\DateTimeValidator'
-            Alphanumeric:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\AlphanumericValidator'
-            Text:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\TextValidator'
-            StringLength:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\StringLengthValidator'
-              #options:
-                #minimum: 0
-                #maximum: 0
-            EmailAddress:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator'
-            Integer:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\IntegerValidator'
-            Float:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\FloatValidator'
-            Number:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\NumberValidator'
-            NumberRange:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\NumberRangeValidator'
-              #options:
-                #minimum: 0
-                #maximum: 0
-            RegularExpression:
-              implementationClassName: 'TYPO3\CMS\Extbase\Validation\Validator\RegularExpressionValidator'
-              #options:
-                #regularExpression: '/^.*$/'
-            Count:
-              implementationClassName: 'TYPO3\CMS\Form\Mvc\Validation\CountValidator'
-              #options:
-                #minimum: 0
-                #maximum: 0
-            FileSize:
-              implementationClassName: 'TYPO3\CMS\Form\Mvc\Validation\FileSizeValidator'
-              #options:
-                #minimum: '0B'
-                #maximum: '10M'
-
-      ########### MIXINS ###########
-      mixins:
-        translationSettingsMixin:
-          translation:
-            translationFile: 'EXT:form/Resources/Private/Language/locallang.xlf'
-            #translatePropertyValueIfEmpty: true
-
-        ########### FORM ELEMENT MIXINS ###########
-        formElementMixins:
-          BaseFormElementMixin: []
-              # The form element type is chosen as the template name by default.
-              # If you want another name you can set it with 'templateName'
-              #templateName: 'CustomTemplateName'
-
-          ReadOnlyFormElementMixin:
-            __inheritances:
-              10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseFormElementMixin'
-            implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
-            renderingOptions:
-              _isReadOnlyFormElement: true
-
-          FormElementMixin:
-            __inheritances:
-              10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseFormElementMixin'
-            implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
-            properties:
-              containerClassAttribute: 'input'
-              elementClassAttribute: ''
-              elementErrorClassAttribute: 'error'
-              #gridColumnClassAutoConfiguration:
-              #  viewPorts:
-              #    xs:
-              #      numbersOfColumnsToUse: ''
-              #    sm:
-              #      numbersOfColumnsToUse: ''
-              #    md:
-              #      numbersOfColumnsToUse: ''
-              #    lg:
-              #      numbersOfColumnsToUse: ''
-
-          TextMixin:
-            __inheritances:
-              10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-
-          SelectionMixin:
-            __inheritances:
-              10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-
-          SingleSelectionMixin:
-            __inheritances:
-              10: 'TYPO3.CMS.Form.mixins.formElementMixins.SelectionMixin'
-
-          MultiSelectionMixin:
-            __inheritances:
-              10: 'TYPO3.CMS.Form.mixins.formElementMixins.SelectionMixin'
-
-          FileUploadMixin:
-            __inheritances:
-              10: 'TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin'
-            implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload'
-            properties:
-              saveToFileMount: '1:/user_upload/'
-
-        finishersEmailMixin:
-          implementationClassName: 'TYPO3\CMS\Form\Domain\Finishers\EmailFinisher'
-          options:
-            #subject: ''
-            #recipientAddress: ''
-            #recipientName: ''
-            #senderAddress:
-            #senderName: ''
-            #replyToAddress: ''
-            #carbonCopyAddress: ''
-            #blindCarbonCopyAddress: ''
-            #format: 'html'
-            #attachUploads: true
-            #translation:
-            #  language: 'default'
-            # {@format} depends the 'format' value
-            templateName: '{@format}.html'
-            templateRootPaths:
-              10: 'EXT:form/Resources/Private/Frontend/Templates/Finishers/Email/'
-            #partialRootPaths: []
-            #layoutRootPaths: []
-            #variables: {}
diff --git a/typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml b/typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml
deleted file mode 100644 (file)
index f9ce552..0000000
+++ /dev/null
@@ -1,1410 +0,0 @@
-TYPO3:
-  CMS:
-    Form:
-      ########### FORM MANAGER CONFIGURATION ###########
-      formManager:
-        dynamicRequireJsModules:
-          app: 'TYPO3/CMS/Form/Backend/FormManager'
-          viewModel: 'TYPO3/CMS/Form/Backend/FormManager/ViewModel'
-        stylesheets:
-          100: 'EXT:form/Resources/Public/Css/form.css'
-        translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
-        javaScriptTranslationFile: 'EXT:form/Resources/Private/Language/locallang_formManager_javascript.xlf'
-        selectablePrototypesConfiguration:
-          100:
-            identifier: 'standard'
-            label: 'formManager.selectablePrototypesConfiguration.standard.label'
-            newFormTemplates:
-              100:
-                templatePath: 'EXT:form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/BlankForm.yaml'
-                label: 'formManager.selectablePrototypesConfiguration.standard.newFormTemplates.blankForm.label'
-              200:
-                templatePath: 'EXT:form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/SimpleContactForm.yaml'
-                label: 'formManager.selectablePrototypesConfiguration.standard.newFormTemplates.simpleContactForm.label'
-        controller:
-          deleteAction:
-            errorTitle: 'formManagerController.deleteAction.error.title'
-            errorMessage: 'formManagerController.deleteAction.error.body'
-
-      ########### FORMEDITOR CONFIGURATION ###########
-      prototypes:
-        standard:
-          formEditor:
-            translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
-            dynamicRequireJsModules:
-              app: 'TYPO3/CMS/Form/Backend/FormEditor'
-              mediator: 'TYPO3/CMS/Form/Backend/FormEditor/Mediator'
-              viewModel: 'TYPO3/CMS/Form/Backend/FormEditor/ViewModel'
-              additionalViewModelModules:
-
-            addInlineSettings: []
-            maximumUndoSteps: 10
-
-            stylesheets:
-              200: 'EXT:form/Resources/Public/Css/form.css'
-
-            formEditorFluidConfiguration:
-              templatePathAndFilename: 'EXT:form/Resources/Private/Backend/Templates/FormEditor/InlineTemplates.html'
-              partialRootPaths:
-                10: 'EXT:form/Resources/Private/Backend/Partials/FormEditor/'
-              layoutRootPaths:
-                10: 'EXT:form/Resources/Private/Backend/Layouts/FormEditor/'
-
-            formEditorPartials:
-              # abstract form element partials
-              FormElement-_ElementToolbar: 'Stage/_ElementToolbar'
-              FormElement-_UnknownElement: 'Stage/_UnknownElement'
-              FormElement-Page: 'Stage/Page'
-              FormElement-SummaryPage: 'Stage/SummaryPage'
-              FormElement-Fieldset: 'Stage/Fieldset'
-              FormElement-GridContainer: 'Stage/Fieldset'
-              FormElement-GridRow: 'Stage/Fieldset'
-              FormElement-Text: 'Stage/SimpleTemplate'
-              FormElement-Password: 'Stage/SimpleTemplate'
-              FormElement-AdvancedPassword: 'Stage/SimpleTemplate'
-              FormElement-Textarea: 'Stage/SimpleTemplate'
-              FormElement-Checkbox: 'Stage/SimpleTemplate'
-              FormElement-MultiCheckbox: 'Stage/SelectTemplate'
-              FormElement-MultiSelect: 'Stage/SelectTemplate'
-              FormElement-RadioButton: 'Stage/SelectTemplate'
-              FormElement-SingleSelect: 'Stage/SelectTemplate'
-              FormElement-DatePicker: 'Stage/SimpleTemplate'
-              FormElement-StaticText: 'Stage/StaticText'
-              FormElement-Hidden: 'Stage/SimpleTemplate'
-              FormElement-ContentElement: 'Stage/ContentElement'
-              FormElement-FileUpload: 'Stage/FileUploadTemplate'
-              FormElement-ImageUpload: 'Stage/FileUploadTemplate'
-              FormElement-Email: 'Stage/SimpleTemplate'
-              FormElement-Telephone: 'Stage/SimpleTemplate'
-              FormElement-Url: 'Stage/SimpleTemplate'
-              FormElement-Number: 'Stage/SimpleTemplate'
-
-              # modals
-              Modal-InsertElements: 'Modals/InsertElements'
-              Modal-InsertPages: 'Modals/InsertPages'
-              Modal-ValidationErrors: 'Modals/ValidationErrors'
-
-              # inspector editors
-              Inspector-FormElementHeaderEditor: 'Inspector/FormElementHeaderEditor'
-              Inspector-CollectionElementHeaderEditor: 'Inspector/CollectionElementHeaderEditor'
-              Inspector-TextEditor: 'Inspector/TextEditor'
-              Inspector-PropertyGridEditor: 'Inspector/PropertyGridEditor'
-              Inspector-SingleSelectEditor: 'Inspector/SingleSelectEditor'
-              Inspector-MultiSelectEditor: 'Inspector/MultiSelectEditor'
-              Inspector-GridColumnViewPortConfigurationEditor: 'Inspector/GridColumnViewPortConfigurationEditor'
-              Inspector-TextareaEditor: 'Inspector/TextareaEditor'
-              Inspector-RemoveElementEditor: 'Inspector/RemoveElementEditor'
-              Inspector-FinishersEditor: 'Inspector/FinishersEditor'
-              Inspector-ValidatorsEditor: 'Inspector/ValidatorsEditor'
-              Inspector-RequiredValidatorEditor: 'Inspector/RequiredValidatorEditor'
-              Inspector-CheckboxEditor: 'Inspector/CheckboxEditor'
-              Inspector-Typo3WinBrowserEditor: 'Inspector/Typo3WinBrowserEditor'
-
-            formElementPropertyValidatorsDefinition:
-              NotEmpty:
-                errorMessage: 'formEditor.formElementPropertyValidatorsDefinition.NotEmpty.label'
-              Integer:
-                errorMessage: 'formEditor.formElementPropertyValidatorsDefinition.Integer.label'
-              NaiveEmail:
-                errorMessage: 'formEditor.formElementPropertyValidatorsDefinition.NaiveEmail.label'
-              NaiveEmailOrEmpty:
-                errorMessage: 'formEditor.formElementPropertyValidatorsDefinition.NaiveEmail.label'
-              FormElementIdentifierWithinCurlyBracesInclusive:
-                errorMessage: 'formEditor.formElementPropertyValidatorsDefinition.FormElementIdentifierWithinCurlyBraces.label'
-              FormElementIdentifierWithinCurlyBracesExclusive:
-                errorMessage: 'formEditor.formElementPropertyValidatorsDefinition.FormElementIdentifierWithinCurlyBraces.label'
-              FileSize:
-                errorMessage: 'formEditor.formElementPropertyValidatorsDefinition.FileSize.label'
-
-            formElementGroups:
-              input:
-                label: 'formEditor.formElementGroups.input.label'
-              html5:
-                label: 'formEditor.formElementGroups.html5.label'
-              select:
-                label: 'formEditor.formElementGroups.select.label'
-              custom:
-                label: 'formEditor.formElementGroups.custom.label'
-              container:
-                label: 'formEditor.formElementGroups.container.label'
-              page:
-                label: 'formEditor.formElementGroups.page.label'
-
-          ########### DEFAULT FORM ELEMENT DEFINITIONS ###########
-          formElementsDefinition:
-            Form:
-              formEditor:
-                _isCompositeFormElement: false
-                _isTopLevelFormElement: true
-
-                saveSuccessFlashMessageTitle: 'formEditor.elements.Form.saveSuccessFlashMessageTitle'
-                saveSuccessFlashMessageMessage: 'formEditor.elements.Form.saveSuccessFlashMessageMessage'
-                saveErrorFlashMessageTitle: 'formEditor.elements.Form.saveErrorFlashMessageTitle'
-                saveErrorFlashMessageMessage: 'formEditor.elements.Form.saveErrorFlashMessageMessage'
-
-                modalValidationErrorsDialogTitle: 'formEditor.modals.validationErrors.dialogTitle'
-                modalValidationErrorsConfirmButton: 'formEditor.modals.validationErrors.confirmButton'
-
-                modalInsertElementsDialogTitle: 'formEditor.modals.insertElements.dialogTitle'
-                modalInsertPagesDialogTitle: 'formEditor.modals.newPages.dialogTitle'
-
-                modalCloseDialogMessage: 'formEditor.modals.close.dialogMessage'
-                modalCloseDialogTitle: 'formEditor.modals.close.dialogTitle'
-                modalCloseConfirmButton: 'formEditor.modals.close.confirmButton'
-                modalCloseCancleButton: 'formEditor.modals.close.cancleButton'
-
-                modalRemoveElementDialogTitle: 'formEditor.modals.removeElement.dialogTitle'
-                modalRemoveElementDialogMessage: 'formEditor.modals.removeElement.dialogMessage'
-                modalRemoveElementConfirmButton: 'formEditor.modals.removeElement.confirmButton'
-                modalRemoveElementCancleButton: 'formEditor.modals.removeElement.cancleButton'
-                modalRemoveElementLastAvailablePageFlashMessageTitle: 'formEditor.modals.removeElement.lastAvailablePageFlashMessageTitle'
-                modalRemoveElementLastAvailablePageFlashMessageMessage: 'formEditor.modals.removeElement.lastAvailablePageFlashMessageMessage'
-
-                inspectorEditorFormElementSelectorNoElements: 'formEditor.inspector.editor.formelement_selector.no_elements'
-
-                paginationTitle: 'formEditor.pagination.title'
-
-                iconIdentifier: 'content-elements-mailform'
-                predefinedDefaults:
-                  renderingOptions:
-                    submitButtonLabel: 'formEditor.elements.Form.editor.submitButtonLabel.value'
-                editors:
-                  300:
-                    identifier: 'submitButtonLabel'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.Form.editor.submitButtonLabel.label'
-                    propertyPath: 'renderingOptions.submitButtonLabel'
-                  900:
-                    identifier: 'finishers'
-                    templateName: 'Inspector-FinishersEditor'
-                    label: 'formEditor.elements.Form.editor.finishers.label'
-                    selectOptions:
-                      10:
-                        value: ''
-                        label: 'formEditor.elements.Form.editor.finishers.EmptyValue.label'
-                      20:
-                        value: 'EmailToSender'
-                        label: 'formEditor.elements.Form.editor.finishers.EmailToSender.label'
-                      30:
-                        value: 'EmailToReceiver'
-                        label: 'formEditor.elements.Form.editor.finishers.EmailToReceiver.label'
-                      40:
-                        value: 'Redirect'
-                        label: 'formEditor.elements.Form.editor.finishers.Redirect.label'
-                      50:
-                        value: 'DeleteUploads'
-                        label: 'formEditor.elements.Form.editor.finishers.DeleteUploads.label'
-                      60:
-                        value: 'Confirmation'
-                        label: 'formEditor.elements.Form.editor.finishers.Confirmation.label'
-
-                propertyCollections:
-                  finishers:
-                    10:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.formEmailFinisherMixin'
-                      identifier: 'EmailToSender'
-
-                    20:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.formEmailFinisherMixin'
-                      identifier: 'EmailToReceiver'
-                      editors:
-                        100:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.header.label'
-                        200:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.subject.label'
-                        300:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientAddress.label'
-                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientAddress.fieldExplanationText'
-                        400:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientName.label'
-                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientName.fieldExplanationText'
-                        500:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.senderAddress.label'
-                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.senderAddress.fieldExplanationText'
-                        600:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.senderName.label'
-                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.senderName.fieldExplanationText'
-                        700:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.replyToAddress.label'
-                        800:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.carbonCopyAddress.label'
-                        900:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.blindCarbonCopyAddress.label'
-                        1000:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.format.label'
-                        1100:
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.attachUploads.label'
-                        1200:
-                          identifier: 'language'
-                          templateName: 'Inspector-SingleSelectEditor'
-                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.language.label'
-                          propertyPath: 'options.translation.language'
-                          selectOptions:
-                            10:
-                              value: 'default'
-                              label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.language.1'
-
-                    30:
-                      identifier: 'Redirect'
-                      editors:
-                        __inheritances:
-                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        100:
-                          label: 'formEditor.elements.Form.finisher.Redirect.editor.header.label'
-                        200:
-                          identifier: 'pageUid'
-                          templateName: 'Inspector-Typo3WinBrowserEditor'
-                          label: 'formEditor.elements.Form.finisher.Redirect.editor.pageUid.label'
-                          buttonLabel: 'formEditor.elements.Form.finisher.Redirect.editor.pageUid.buttonLabel'
-                          browsableType: pages
-                          propertyPath: 'options.pageUid'
-                          propertyValidatorsMode: 'OR'
-                          propertyValidators:
-                            10: 'Integer'
-                            20: 'FormElementIdentifierWithinCurlyBracesExclusive'
-
-                        300:
-                          identifier: 'additionalParameters'
-                          templateName: 'Inspector-TextEditor'
-                          label: 'formEditor.elements.Form.finisher.Redirect.editor.additionalParameters.label'
-                          propertyPath: 'options.additionalParameters'
-
-                    40:
-                      identifier: 'DeleteUploads'
-                      editors:
-                        __inheritances:
-                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        100:
-                          label: 'formEditor.elements.Form.finisher.DeleteUploads.editor.header.label'
-
-                    50:
-                      identifier: 'Confirmation'
-                      editors:
-                        __inheritances:
-                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        100:
-                          label: 'formEditor.elements.Form.finisher.Confirmation.editor.header.label'
-                        200:
-                          identifier: 'contentElement'
-                          templateName: 'Inspector-Typo3WinBrowserEditor'
-                          label: 'formEditor.elements.Form.finisher.Confirmation.editor.contentElement.label'
-                          buttonLabel: 'formEditor.elements.Form.finisher.Confirmation.editor.contentElement.buttonLabel'
-                          browsableType: tt_content
-                          propertyPath: 'options.contentElementUid'
-                          propertyValidatorsMode: 'OR'
-                          propertyValidators:
-                            10: 'IntegerOrEmpty'
-                            20: 'FormElementIdentifierWithinCurlyBracesExclusive'
-                        300:
-                          identifier: 'message'
-                          templateName: 'Inspector-TextareaEditor'
-                          label: 'formEditor.elements.Form.finisher.Confirmation.editor.message.label'
-                          propertyPath: 'options.message'
-                          fieldExplanationText: 'formEditor.elements.Form.finisher.Confirmation.editor.message.fieldExplanationText'
-
-                    60:
-                      identifier: 'Closure'
-                      editors:
-                        __inheritances:
-                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        100:
-                          label: 'formEditor.elements.Form.finisher.Closure.editor.header.label'
-
-                    70:
-                      identifier: 'FlashMessage'
-                      editors:
-                        __inheritances:
-                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        100:
-                          label: 'formEditor.elements.Form.finisher.FlashMessage.editor.header.label'
-
-                    80:
-                      identifier: 'SaveToDatabase'
-                      editors:
-                        __inheritances:
-                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        100:
-                          label: 'formEditor.elements.Form.finisher.SaveToDatabase.editor.header.label'
-
-            ### FORM ELEMENTS: CONTAINER ###
-            Fieldset:
-              formEditor:
-                label: 'formEditor.elements.Fieldset.label'
-                group: container
-                groupSorting: 100
-                _isCompositeFormElement: true
-                iconIdentifier: 't3-form-icon-fieldset'
-                editors:
-                  200:
-                    label: 'formEditor.elements.Fieldset.editor.label.label'
-                  800: null
-
-            GridContainer:
-              formEditor:
-                label: 'formEditor.elements.GridContainer.label'
-                _isCompositeFormElement: true
-                _isGridContainerFormElement: true
-                iconIdentifier: 't3-form-icon-gridcontainer'
-                editors:
-                  200:
-                    label: 'formEditor.elements.GridContainer.editor.label.label'
-                  800: null
-
-            GridRow:
-              formEditor:
-                label: 'formEditor.elements.GridRow.label'
-                group: container
-                groupSorting: 300
-                _isCompositeFormElement: true
-                _isGridRowFormElement: true
-                iconIdentifier: 't3-form-icon-gridrow'
-                editors:
-                  200:
-                    label: 'formEditor.elements.GridRow.editor.label.label'
-                  800: null
-
-            ### FORM ELEMENTS: PAGE TYPES ###
-            Page:
-              formEditor:
-                __inheritances:
-                  10: 'TYPO3.CMS.Form.mixins.formElementMixins.RemovableFormElementMixin'
-                predefinedDefaults:
-                  renderingOptions:
-                    previousButtonLabel: 'formEditor.elements.Page.editor.previousButtonLabel.value'
-                    nextButtonLabel: 'formEditor.elements.Page.editor.nextButtonLabel.value'
-                label: 'formEditor.elements.Page.label'
-                group: page
-                groupSorting: 100
-                _isTopLevelFormElement: true
-                _isCompositeFormElement: true
-                iconIdentifier: 't3-form-icon-page'
-                editors:
-                  200:
-                    label: 'formEditor.elements.Page.editor.label.label'
-                  300:
-                    identifier: 'previousButtonLabel'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.Page.editor.previousButtonLabel.label'
-                    propertyPath: 'renderingOptions.previousButtonLabel'
-                  400:
-                    identifier: 'nextButtonLabel'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.Page.editor.nextButtonLabel.label'
-                    propertyPath: 'renderingOptions.nextButtonLabel'
-
-            SummaryPage:
-              formEditor:
-                predefinedDefaults:
-                  renderingOptions:
-                    previousButtonLabel: 'formEditor.elements.SummaryPage.editor.previousButtonLabel.value'
-                    nextButtonLabel: 'formEditor.elements.SummaryPage.editor.nextButtonLabel.value'
-                label: 'formEditor.elements.SummaryPage.label'
-                group: page
-                groupSorting: 200
-                _isTopLevelFormElement: true
-                _isCompositeFormElement: false
-                iconIdentifier: 't3-form-icon-summary-page'
-                editors:
-                  200:
-                    label: 'formEditor.elements.SummaryPage.editor.label.label'
-                  300:
-                    identifier: 'previousButtonLabel'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.SummaryPage.editor.previousButtonLabel.label'
-                    propertyPath: 'renderingOptions.previousButtonLabel'
-                  400:
-                    identifier: 'nextButtonLabel'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.SummaryPage.editor.nextButtonLabel.label'
-                    propertyPath: 'renderingOptions.nextButtonLabel'
-
-            ### FORM ELEMENTS: INPUT ###
-
-            Text:
-              formEditor:
-                label: 'formEditor.elements.Text.label'
-                group: input
-                groupSorting: 100
-                iconIdentifier: 't3-form-icon-text'
-
-            Password:
-              formEditor:
-                label: 'formEditor.elements.Password.label'
-                group: input
-                groupSorting: 300
-                iconIdentifier: 't3-form-icon-password'
-
-            AdvancedPassword:
-              formEditor:
-                label: 'formEditor.elements.AdvancedPassword.label'
-                group: custom
-                groupSorting: 500
-                predefinedDefaults:
-                  properties:
-                    confirmationLabel: 'formEditor.element.AdvancedPassword.editor.confirmationLabel.predefinedDefaults'
-                  defaultValue: null
-                iconIdentifier: 't3-form-icon-advanced-password'
-                editors:
-                  300:
-                    identifier: 'confirmationLabel'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.AdvancedPassword.editor.confirmationLabel.label'
-                    propertyPath: 'properties.confirmationLabel'
-                  500: null
-
-            Hidden:
-              formEditor:
-                label: 'formEditor.elements.Hidden.label'
-                group: custom
-                groupSorting: 300
-                iconIdentifier: 't3-form-icon-hidden'
-                predefinedDefaults:
-                  defaultValue: ''
-                editors:
-                  300:
-                    identifier: 'defaultValue'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.Hidden.editor.defaultValue.label'
-                    propertyPath: 'defaultValue'
-                  800: null
-
-            Textarea:
-              formEditor:
-                label: 'formEditor.elements.Textarea.label'
-                group: input
-                groupSorting: 200
-                iconIdentifier: 't3-form-icon-textarea'
-                editors:
-                  900:
-                    selectOptions:
-                      # remove email validator
-                      50: null
-
-            ### FORM ELEMENTS: HTML5 ###
-            Email:
-              formEditor:
-                label: 'formEditor.elements.Email.label'
-                group: html5
-                groupSorting: 100
-                iconIdentifier: 't3-form-icon-email'
-                predefinedDefaults:
-                  validators:
-                    -
-                      identifier: EmailAddress
-                editors:
-                  500:
-                    propertyValidators:
-                      10: 'NaiveEmailOrEmpty'
-                  900:
-                    selectOptions:
-                      20: null
-                      30: null
-                      40: null
-                      60: null
-                      70: null
-                      80: null
-                      90: null
-
-                propertyCollections:
-                  validators:
-                    40:
-                      editors:
-                        9999: null
-
-            Telephone:
-              formEditor:
-                label: 'formEditor.elements.Telephone.label'
-                group: html5
-                groupSorting: 200
-                iconIdentifier: 't3-form-icon-telephone'
-
-                editors:
-                  900:
-                    selectOptions:
-                      20: null
-                      30: null
-                      40: null
-                      50: null
-                      60: null
-                      70: null
-                      80: null
-
-                propertyCollections:
-                  validators:
-                    80:
-                      editors:
-                        9999: null
-
-            Url:
-              formEditor:
-                label: 'formEditor.elements.Url.label'
-                group: html5
-                groupSorting: 300
-                iconIdentifier: 't3-form-icon-url'
-
-                editors:
-                  900:
-                    selectOptions:
-                      20: null
-                      30: null
-                      40: null
-                      50: null
-                      60: null
-                      70: null
-                      80: null
-
-                propertyCollections:
-                  validators:
-                    80:
-                      editors:
-                        9999: null
-
-            Number:
-              formEditor:
-                label: 'formEditor.elements.Number.label'
-                group: html5
-                groupSorting: 400
-                iconIdentifier: 't3-form-icon-number'
-                predefinedDefaults:
-                  properties:
-                    fluidAdditionalAttributes:
-                      step: 1
-                  validators:
-                    -
-                      identifier: Number
-                editors:
-                  500:
-                   propertyValidators:
-                      10: 'IntegerOrEmpty'
-                  700:
-                    identifier: 'step'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.TextMixin.editor.step.label'
-                    propertyPath: 'properties.fluidAdditionalAttributes.step'
-                    propertyValidators:
-                      10: 'Integer'
-
-                  900:
-                    selectOptions:
-                      20: null
-                      30: null
-                      40: null
-                      50: null
-                      60:
-                        value: 'Number'
-                        label: 'formEditor.elements.Number.editor.validators.Number.label'
-                      70: null
-                      90: null
-
-                propertyCollections:
-                  validators:
-                    60:
-                      identifier: 'Number'
-                      editors:
-                        __inheritances:
-                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        100:
-                          label: 'formEditor.elements.TextMixin.validators.Number.editor.header.label'
-                        9999: null
-
-            ### FORM ELEMENTS: SELECT ###
-            Checkbox:
-              formEditor:
-                label: 'formEditor.elements.Checkbox.label'
-                group: select
-                groupSorting: 100
-                iconIdentifier: 't3-form-icon-checkbox'
-
-            MultiCheckbox:
-              formEditor:
-                label: 'formEditor.elements.MultiCheckbox.label'
-                group: select
-                groupSorting: 400
-                iconIdentifier: 't3-form-icon-multi-checkbox'
-                editors:
-                  800:
-                    propertyPath: null
-                    propertyValue: null
-
-            MultiSelect:
-              formEditor:
-                label: 'formEditor.elements.MultiSelect.label'
-                group: select
-                groupSorting: 500
-                iconIdentifier: 't3-form-icon-multi-select'
-                editors:
-                  250:
-                    identifier: 'inactiveOption'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.SelectionMixin.editor.inactiveOption.label'
-                    propertyPath: 'properties.prependOptionLabel'
-                    fieldExplanationText: 'formEditor.elements.SelectionMixin.editor.inactiveOption.fieldExplanationText'
-                    doNotSetIfPropertyValueIsEmpty: true
-
-            RadioButton:
-              formEditor:
-                label: 'formEditor.elements.RadioButton.label'
-                group: select
-                groupSorting: 300
-                iconIdentifier: 't3-form-icon-radio-button'
-
-            SingleSelect:
-              formEditor:
-                label: 'formEditor.elements.SingleSelect.label'
-                group: select
-                groupSorting: 200
-                iconIdentifier: 't3-form-icon-single-select'
-                editors:
-                  250:
-                    identifier: 'inactiveOption'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.SelectionMixin.editor.inactiveOption.label'
-                    propertyPath: 'properties.prependOptionLabel'
-                    fieldExplanationText: 'formEditor.elements.SelectionMixin.editor.inactiveOption.fieldExplanationText'
-                    doNotSetIfPropertyValueIsEmpty: true
-
-            ### FORM ELEMENTS: CUSTOM ###
-            DatePicker:
-              formEditor:
-                label: 'formEditor.elements.DatePicker.label'
-                group: custom
-                groupSorting: 200
-                predefinedDefaults:
-                  properties:
-                    dateFormat: 'Y-m-d'
-                    enableDatePicker: true
-                    displayTimeSelector: false
-                iconIdentifier: 't3-form-icon-date-picker'
-                editors:
-                  300:
-                    identifier: 'dateFormat'
-                    templateName: 'Inspector-TextEditor'
-                    label: 'formEditor.elements.DatePicker.editor.dateFormat.label'
-                    propertyPath: 'properties.dateFormat'
-                  400:
-                    identifier: 'enableDatePicker'
-                    templateName: 'Inspector-CheckboxEditor'
-                    label: 'formEditor.elements.DatePicker.editor.enableDatePicker.label'
-                    propertyPath: 'properties.enableDatePicker'
-                  500:
-                    identifier: 'displayTimeSelector'
-                    templateName: 'Inspector-CheckboxEditor'
-                    label: 'formEditor.elements.DatePicker.editor.displayTimeSelector.label'
-                    propertyPath: 'properties.displayTimeSelector'
-                  900:
-                    identifier: 'validators'
-                    templateName: 'Inspector-ValidatorsEditor'
-                    label: 'formEditor.elements.DatePicker.editor.validators.label'
-                    selectOptions:
-                      10:
-                        value: ''
-                        label: 'formEditor.elements.DatePicker.editor.validators.EmptyValue.label'
-                      20:
-                        value: 'DateTime'
-                        label: 'formEditor.elements.DatePicker.editor.validators.DateTime.label'
-
-                propertyCollections:
-                  validators:
-                    10:
-                      identifier: 'DateTime'
-                      editors:
-                        __inheritances:
-                          10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        100:
-                          label: 'formEditor.elements.DatePicker.validators.DateTime.editor.header.label'
-
-            StaticText:
-              formEditor:
-                label: 'formEditor.elements.StaticText.label'
-                group: custom
-                groupSorting: 600
-                predefinedDefaults:
-                  properties:
-                    text: ''
-                iconIdentifier: 't3-form-icon-static-text'
-                editors:
-                  300:
-                    identifier: 'staticText'
-                    templateName: 'Inspector-TextareaEditor'
-                    label: 'formEditor.elements.StaticText.editor.staticText.label'
-                    propertyPath: 'properties.text'
-
-            ContentElement:
-              formEditor:
-                label: 'formEditor.elements.ContentElement.label'
-                group: custom
-                groupSorting: 700
-                predefinedDefaults:
-                  properties:
-                    contentElementUid: ''
-                iconIdentifier: 't3-form-icon-content-element'
-                editors:
-                  200: null
-                  300:
-                    identifier: 'contentElement'
-                    templateName: 'Inspector-Typo3WinBrowserEditor'
-                    label: 'formEditor.elements.ContentElement.editor.contentElement.label'
-                    buttonLabel: 'formEditor.elements.ContentElement.editor.contentElement.buttonLabel'
-                    browsableType: tt_content
-                    propertyPath: 'properties.contentElementUid'
-                    propertyValidatorsMode: 'OR'
-                    propertyValidators:
-                      10: 'Integer'
-                      20: 'FormElementIdentifierWithinCurlyBracesExclusive'
-
-            ### FORM ELEMENTS: UPLOADS ###
-            FileUpload:
-              formEditor:
-                label: 'formEditor.elements.FileUpload.label'
-                group: custom
-                groupSorting: 100
-                predefinedDefaults:
-                  properties:
-                    allowedMimeTypes: ['application/pdf']
-                iconIdentifier: 't3-form-icon-file-upload'
-                editors:
-                  300:
-                    identifier: 'allowedMimeTypes'
-                    templateName: 'Inspector-MultiSelectEditor'
-                    label: 'formEditor.elements.FileUpload.editor.allowedMimeTypes.label'
-                    propertyPath: 'properties.allowedMimeTypes'
-                    selectOptions:
-                      10:
-                        value: 'application/msword'
-                        label: 'formEditor.elements.FileUpload.editor.allowedMimeTypes.doc'
-                      20:
-                        value: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
-                        label: 'formEditor.elements.FileUpload.editor.allowedMimeTypes.docx'
-                      30:
-                        value: 'application/msexcel'
-                        label: 'formEditor.elements.FileUpload.editor.allowedMimeTypes.xls'
-                      40:
-                        value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
-                        label: 'formEditor.elements.FileUpload.editor.allowedMimeTypes.xlsx'
-                      50:
-                        value: 'application/pdf'
-                        label: 'formEditor.elements.FileUpload.editor.allowedMimeTypes.pdf'
-                      60:
-                        value: 'application/vnd.oasis.opendocument.text'
-                        label: 'formEditor.elements.FileUpload.editor.allowedMimeTypes.odt'
-                      70:
-                        value: 'application/vnd.oasis.opendocument.spreadsheet-template'
-                        label: 'formEditor.elements.FileUpload.editor.allowedMimeTypes.ods'
-
-            ImageUpload:
-              formEditor:
-                label: 'formEditor.elements.ImageUpload.label'
-                group: custom
-                groupSorting: 400
-                predefinedDefaults:
-                  properties:
-                    allowedMimeTypes: ['image/jpeg']
-                iconIdentifier: 't3-form-icon-image-upload'
-                editors:
-                  300:
-                    identifier: 'allowedMimeTypes'
-                    templateName: 'Inspector-MultiSelectEditor'
-                    label: 'formEditor.elements.ImageUpload.editor.allowedMimeTypes.label'
-                    propertyPath: 'properties.allowedMimeTypes'
-                    selectOptions:
-                      10:
-                        value: 'image/jpeg'
-                        label: 'formEditor.elements.ImageUpload.editor.allowedMimeTypes.jpg'
-                      20:
-                        value: 'image/png'
-                        label: 'formEditor.elements.ImageUpload.editor.allowedMimeTypes.png'
-                      30:
-                        value: 'image/bmp'
-                        label: 'formEditor.elements.ImageUpload.editor.allowedMimeTypes.bmp'
-
-          ### FINISHERS ###
-          finishersDefinition:
-            EmailToSender:
-              formEditor:
-                iconIdentifier: 't3-form-icon-finisher'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.header.label'
-                predefinedDefaults:
-                  options:
-                    subject: ''
-                    recipientAddress: ''
-                    recipientName: ''
-                    senderAddress: ''
-                    senderName: ''
-                    replyToAddress: ''
-                    carbonCopyAddress: ''
-                    blindCarbonCopyAddress: ''
-                    format: 'html'
-                    attachUploads: true
-
-            EmailToReceiver:
-              formEditor:
-                iconIdentifier: 't3-form-icon-finisher'
-                label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.header.label'
-                predefinedDefaults:
-                  options:
-                    subject: ''
-                    recipientAddress: ''
-                    recipientName: ''
-                    senderAddress: ''
-                    senderName: ''
-                    replyToAddress: ''
-                    carbonCopyAddress: ''
-                    blindCarbonCopyAddress: ''
-                    format: 'html'
-                    attachUploads: true
-                    translation:
-                      language: ''
-
-            Redirect:
-              formEditor:
-                iconIdentifier: 't3-form-icon-finisher'
-                label: 'formEditor.elements.Form.finisher.Redirect.editor.header.label'
-                predefinedDefaults:
-                  options:
-                    pageUid: ''
-                    additionalParameters: ''
-
-            Closure:
-              formEditor:
-                iconIdentifier: 't3-form-icon-finisher'
-                label: 'formEditor.elements.Form.finisher.Closure.editor.header.label'
-                predefinedDefaults:
-                  options:
-                    closure: ''
-
-            Confirmation:
-              formEditor:
-                iconIdentifier: 't3-form-icon-finisher'
-                label: 'formEditor.elements.Form.finisher.Confirmation.editor.header.label'
-                predefinedDefaults:
-                  options:
-                    message: ''
-                    contentElementUid: ''
-
-            FlashMessage:
-              formEditor:
-                iconIdentifier: 't3-form-icon-finisher'
-                label: 'formEditor.elements.Form.finisher.FlashMessage.editor.header.label'
-                predefinedDefaults:
-                  options:
-                    messageBody: ''
-                    messageTitle: ''
-                    messageArguments: ''
-                    messageCode: 0
-                    severity: 0
-
-            SaveToDatabase:
-              formEditor:
-                iconIdentifier: 't3-form-icon-finisher'
-                label: 'formEditor.elements.Form.finisher.SaveToDatabase.editor.header.label'
-                predefinedDefaults:
-                  options: []
-
-            DeleteUploads:
-              formEditor:
-                iconIdentifier: 't3-form-icon-finisher'
-                label: 'formEditor.elements.Form.finisher.DeleteUploads.editor.header.label'
-
-          ### VALIDATORS ###
-          validatorsDefinition:
-            NotEmpty:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label : 'formEditor.elements.FormElement.editor.requiredValidator.label'
-            DateTime:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.DatePicker.validators.DateTime.editor.header.label'
-            Alphanumeric:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.Alphanumeric.label'
-            Text:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.Text.label'
-            StringLength:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.StringLength.label'
-                predefinedDefaults:
-                  options:
-                    minimum: ''
-                    maximum: ''
-            EmailAddress:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.EmailAddress.label'
-            Integer:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.Integer.label'
-            Float:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.Float.label'
-            Number:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.Number.label'
-            NumberRange:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.NumberRange.label'
-                predefinedDefaults:
-                  options:
-                    minimum: ''
-                    maximum: ''
-            RegularExpression:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.TextMixin.editor.validators.RegularExpression.label'
-                predefinedDefaults:
-                  options:
-                    regularExpression: ''
-            Count:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.MultiSelectionMixin.validators.Count.editor.header.label'
-                predefinedDefaults:
-                  options:
-                    minimum: ''
-                    maximum: ''
-            FileSize:
-              formEditor:
-                iconIdentifier: 't3-form-icon-validator'
-                label: 'formEditor.elements.FileUploadMixin.validators.FileSize.editor.header.label'
-                predefinedDefaults:
-                  options:
-                    minimum: '0B'
-                    maximum: '10M'
-
-      ########### MIXINS ###########
-      mixins:
-        ########### FORM ELEMENT MIXINS ###########
-        formElementMixins:
-          BaseFormElementMixin:
-            formEditor:
-              predefinedDefaults: []
-              editors:
-                100:
-                  identifier: 'header'
-                  templateName: 'Inspector-FormElementHeaderEditor'
-                200:
-                  identifier: 'label'
-                  templateName: 'Inspector-TextEditor'
-                  label: 'formEditor.elements.BaseFormElementMixin.editor.label.label'
-                  propertyPath: 'label'
-
-          RemoveButtonMixin:
-            9999:
-              identifier: 'removeButton'
-              templateName: 'Inspector-RemoveElementEditor'
-
-          RemovableFormElementMixin:
-            editors:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.RemoveButtonMixin'
-
-          BaseCollectionEditorsMixin:
-            __inheritances:
-              10: 'TYPO3.CMS.Form.mixins.formElementMixins.RemoveButtonMixin'
-            100:
-              identifier: 'header'
-              templateName: 'Inspector-CollectionElementHeaderEditor'
-              label: ''
-
-          MinimumMaximumEditorsMixin:
-            200:
-              identifier: 'minimum'
-              templateName: 'Inspector-TextEditor'
-              label: 'formEditor.elements.MinimumMaximumEditorsMixin.editor.minimum.label'
-              propertyPath: 'options.minimum'
-              propertyValidators:
-                10: 'Integer'
-            300:
-              identifier: 'maximum'
-              templateName: 'Inspector-TextEditor'
-              label: 'formEditor.elements.MinimumMaximumEditorsMixin.editor.maximum.label'
-              propertyPath: 'options.maximum'
-              propertyValidators:
-                10: 'Integer'
-
-          ReadOnlyFormElementMixin:
-            formEditor:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.RemovableFormElementMixin'
-              editors:
-                200:
-                  label: 'formEditor.elements.ReadOnlyFormElement.editor.label.label'
-
-          FormElementMixin:
-            formEditor:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.RemovableFormElementMixin'
-              editors:
-                200:
-                  label: 'formEditor.elements.FormElement.editor.label.label'
-
-                700:
-                  identifier: 'gridColumnViewPortConfiguration'
-                  templateName: 'Inspector-GridColumnViewPortConfigurationEditor'
-                  label: 'formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.label'
-                  configurationOptions:
-                    viewPorts:
-                      10:
-                        viewPortIdentifier: 'xs'
-                        label: 'formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xs.label'
-                      20:
-                        viewPortIdentifier: 'sm'
-                        label: 'formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.sm.label'
-                      30:
-                        viewPortIdentifier: 'md'
-                        label: 'formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.md.label'
-                      40:
-                        viewPortIdentifier: 'lg'
-                        label: 'formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.lg.label'
-                    numbersOfColumnsToUse:
-                      label: 'formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.label'
-                      propertyPath: 'properties.gridColumnClassAutoConfiguration.viewPorts.{@viewPortIdentifier}.numbersOfColumnsToUse'
-                      fieldExplanationText: 'formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.fieldExplanationText'
-
-                800:
-                  identifier: 'requiredValidator'
-                  templateName: 'Inspector-RequiredValidatorEditor'
-                  label: 'formEditor.elements.FormElement.editor.requiredValidator.label'
-                  validatorIdentifier: 'NotEmpty'
-                  propertyPath: 'properties.fluidAdditionalAttributes.required'
-                  propertyValue: 'required'
-
-          TextMixin:
-            formEditor:
-              predefinedDefaults:
-                defaultValue: ''
-              editors:
-                400:
-                  identifier: 'placeholder'
-                  templateName: 'Inspector-TextEditor'
-                  label: 'formEditor.elements.TextMixin.editor.placeholder.label'
-                  propertyPath: 'properties.fluidAdditionalAttributes.placeholder'
-                  doNotSetIfPropertyValueIsEmpty: true
-                500:
-                  identifier: 'defaultValue'
-                  templateName: 'Inspector-TextEditor'
-                  label: 'formEditor.elements.TextMixin.editor.defaultValue.label'
-                  propertyPath: 'defaultValue'
-                900:
-                  identifier: 'validators'
-                  templateName: 'Inspector-ValidatorsEditor'
-                  label: 'formEditor.elements.TextMixin.editor.validators.label'
-                  selectOptions:
-                    10:
-                      value: ''
-                      label: 'formEditor.elements.TextMixin.editor.validators.EmptyValue.label'
-                    20:
-                      value: 'Alphanumeric'
-                      label: 'formEditor.elements.TextMixin.editor.validators.Alphanumeric.label'
-                    30:
-                      value: 'Text'
-                      label: 'formEditor.elements.TextMixin.editor.validators.Text.label'
-                    40:
-                      value: 'StringLength'
-                      label: 'formEditor.elements.TextMixin.editor.validators.StringLength.label'
-                    50:
-                      value: 'EmailAddress'
-                      label: 'formEditor.elements.TextMixin.editor.validators.EmailAddress.label'
-                    60:
-                      value: 'Integer'
-                      label: 'formEditor.elements.TextMixin.editor.validators.Integer.label'
-                    70:
-                      value: 'Float'
-                      label: 'formEditor.elements.TextMixin.editor.validators.Float.label'
-                    80:
-                      value: 'NumberRange'
-                      label: 'formEditor.elements.TextMixin.editor.validators.NumberRange.label'
-                    90:
-                      value: 'RegularExpression'
-                      label: 'formEditor.elements.TextMixin.editor.validators.RegularExpression.label'
-
-              propertyCollections:
-                validators:
-                  10:
-                    identifier: 'Alphanumeric'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.TextMixin.validators.Alphanumeric.editor.header.label'
-                  20:
-                    identifier: 'Text'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.TextMixin.validators.Text.editor.header.label'
-                  30:
-                    identifier: 'StringLength'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        20: 'TYPO3.CMS.Form.mixins.formElementMixins.MinimumMaximumEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.TextMixin.validators.StringLength.editor.header.label'
-                      200:
-                        additionalElementPropertyPaths:
-                          10: 'properties.fluidAdditionalAttributes.minlength'
-                      300:
-                        additionalElementPropertyPaths:
-                          10: 'properties.fluidAdditionalAttributes.maxlength'
-                  40:
-                    identifier: 'EmailAddress'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.TextMixin.validators.EmailAddress.editor.header.label'
-                  50:
-                    identifier: 'Integer'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.TextMixin.validators.Integer.editor.header.label'
-                  60:
-                    identifier: 'Float'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.TextMixin.validators.Float.editor.header.label'
-                  70:
-                    identifier: 'NumberRange'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        20: 'TYPO3.CMS.Form.mixins.formElementMixins.MinimumMaximumEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.TextMixin.validators.NumberRange.editor.header.label'
-                      200:
-                        additionalElementPropertyPaths:
-                          10: 'properties.fluidAdditionalAttributes.min'
-                      300:
-                        additionalElementPropertyPaths:
-                          10: 'properties.fluidAdditionalAttributes.max'
-                  80:
-                    identifier: 'RegularExpression'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.TextMixin.validators.RegularExpression.editor.header.label'
-                      200:
-                        identifier: 'regex'
-                        templateName: 'Inspector-TextEditor'
-                        label: 'formEditor.elements.TextMixin.validators.RegularExpression.editor.regex.label'
-                        fieldExplanationText: 'formEditor.elements.TextMixin.validators.RegularExpression.editor.regex.fieldExplanationText'
-                        propertyPath: 'options.regularExpression'
-                        propertyValidators:
-                          10: 'NotEmpty'
-
-          SelectionMixin:
-            formEditor:
-              predefinedDefaults:
-                properties:
-                  options: []
-              editors:
-                300:
-                  identifier: 'options'
-                  templateName: 'Inspector-PropertyGridEditor'
-                  label: 'formEditor.elements.SelectionMixin.editor.options.label'
-                  propertyPath: 'properties.options'
-                  isSortable: true
-                  enableAddRow: true
-                  enableDeleteRow: true
-                  removeLastAvailableRowFlashMessageTitle: 'formEditor.elements.SelectionMixin.editor.options.removeLastAvailableRowFlashMessageTitle'
-                  removeLastAvailableRowFlashMessageMessage: 'formEditor.elements.SelectionMixin.editor.options.removeLastAvailableRowFlashMessageMessage'
-
-          SingleSelectionMixin:
-            formEditor:
-              editors:
-                300:
-                  shouldShowPreselectedValueColumn: 'single'
-                  multiSelection: false
-
-          MultiSelectionMixin:
-            formEditor:
-              editors:
-                300:
-                  shouldShowPreselectedValueColumn: 'multiple'
-                  multiSelection: true
-                900:
-                  identifier: 'validators'
-                  templateName: 'Inspector-ValidatorsEditor'
-                  label: 'formEditor.elements.MultiSelectionMixin.editor.validators.label'
-                  selectOptions:
-                    10:
-                      value: ''
-                      label: 'formEditor.elements.MultiSelectionMixin.editor.validators.EmptyValue.label'
-                    20:
-                      value: 'Count'
-                      label: 'formEditor.elements.MultiSelectionMixin.editor.validators.Count.label'
-
-              propertyCollections:
-                validators:
-                  10:
-                    identifier: 'Count'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        20: 'TYPO3.CMS.Form.mixins.formElementMixins.MinimumMaximumEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.MultiSelectionMixin.validators.Count.editor.header.label'
-
-          FileUploadMixin:
-            formEditor:
-              predefinedDefaults:
-                properties:
-                  saveToFileMount: '1:/user_upload/'
-              editors:
-                400:
-                  identifier: 'saveToFileMount'
-                  templateName: 'Inspector-SingleSelectEditor'
-                  label: 'formEditor.elements.FileUploadMixin.editor.saveToFileMount.label'
-                  propertyPath: 'properties.saveToFileMount'
-                  selectOptions:
-                    10:
-                      value: '1:/user_upload/'
-                      label: '1:/user_upload/'
-                900:
-                  identifier: 'validators'
-                  templateName: 'Inspector-ValidatorsEditor'
-                  label: 'formEditor.elements.FileUploadMixin.editor.validators.label'
-                  selectOptions:
-                    10:
-                      value: ''
-                      label: 'formEditor.elements.FileUploadMixin.editor.validators.EmptyValue.label'
-                    20:
-                      value: 'FileSize'
-                      label: 'formEditor.elements.FileUploadMixin.editor.validators.FileSize.label'
-
-              propertyCollections:
-                validators:
-                  10:
-                    identifier: 'FileSize'
-                    editors:
-                      __inheritances:
-                        10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-                        20: 'TYPO3.CMS.Form.mixins.formElementMixins.MinimumMaximumEditorsMixin'
-                      100:
-                        label: 'formEditor.elements.FileUploadMixin.validators.FileSize.editor.header.label'
-                      200:
-                        propertyValidators:
-                          10: 'FileSize'
-                      300:
-                        propertyValidators:
-                          10: 'FileSize'
-
-          formEmailFinisherMixin:
-            editors:
-              __inheritances:
-                10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
-              100:
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.header.label'
-              200:
-                identifier: 'subject'
-                templateName: 'Inspector-TextEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.subject.label'
-                propertyPath: 'options.subject'
-                enableFormelementSelectionButton: true
-                propertyValidators:
-                  10: 'NotEmpty'
-                  20: 'FormElementIdentifierWithinCurlyBracesInclusive'
-              300:
-                identifier: 'recipientAddress'
-                templateName: 'Inspector-TextEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.recipientAddress.label'
-                propertyPath: 'options.recipientAddress'
-                enableFormelementSelectionButton: true
-                propertyValidatorsMode: 'OR'
-                propertyValidators:
-                  10: 'NaiveEmail'
-                  20: 'FormElementIdentifierWithinCurlyBracesExclusive'
-                fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.recipientAddress.fieldExplanationText'
-              400:
-                identifier: 'recipientName'
-                templateName: 'Inspector-TextEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.recipientName.label'
-                propertyPath: 'options.recipientName'
-                enableFormelementSelectionButton: true
-                propertyValidators:
-                  10: 'FormElementIdentifierWithinCurlyBracesInclusive'
-                fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.recipientName.fieldExplanationText'
-              500:
-                identifier: 'senderAddress'
-                templateName: 'Inspector-TextEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.label'
-                propertyPath: 'options.senderAddress'
-                enableFormelementSelectionButton: true
-                propertyValidatorsMode: 'OR'
-                propertyValidators:
-                  10: 'NaiveEmail'
-                  20: 'FormElementIdentifierWithinCurlyBracesExclusive'
-                fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.fieldExplanationText'
-              600:
-                identifier: 'senderName'
-                templateName: 'Inspector-TextEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderName.label'
-                propertyPath: 'options.senderName'
-                enableFormelementSelectionButton: true
-                propertyValidators:
-                  10: 'FormElementIdentifierWithinCurlyBracesInclusive'
-                fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderName.fieldExplanationText'
-              700:
-                identifier: 'replyToAddress'
-                templateName: 'Inspector-TextEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.replyToAddress.label'
-                propertyPath: 'options.replyToAddress'
-                enableFormelementSelectionButton: true
-                propertyValidatorsMode: 'OR'
-                propertyValidators:
-                  10: 'NaiveEmailOrEmpty'
-                  20: 'FormElementIdentifierWithinCurlyBracesExclusive'
-              800:
-                identifier: 'carbonCopyAddress'
-                templateName: 'Inspector-TextEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.carbonCopyAddress.label'
-                propertyPath: 'options.carbonCopyAddress'
-                enableFormelementSelectionButton: true
-                propertyValidatorsMode: 'OR'
-                propertyValidators:
-                  10: 'NaiveEmailOrEmpty'
-                  20: 'FormElementIdentifierWithinCurlyBracesExclusive'
-              900:
-                identifier: 'blindCarbonCopyAddress'
-                templateName: 'Inspector-TextEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.blindCarbonCopyAddress.label'
-                propertyPath: 'options.blindCarbonCopyAddress'
-                enableFormelementSelectionButton: true
-                propertyValidatorsMode: 'OR'
-                propertyValidators:
-                  10: 'NaiveEmailOrEmpty'
-                  20: 'FormElementIdentifierWithinCurlyBracesExclusive'
-              1000:
-                identifier: 'format'
-                templateName: 'Inspector-SingleSelectEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.format.label'
-                propertyPath: 'options.format'
-                selectOptions:
-                  10:
-                    value: 'plaintext'
-                    label: 'formEditor.elements.Form.finisher.EmailToSender.editor.format.1'
-                  20:
-                    value: 'html'
-                    label: 'formEditor.elements.Form.finisher.EmailToSender.editor.format.2'
-              1100:
-                identifier: 'attachUploads'
-                templateName: 'Inspector-CheckboxEditor'
-                label: 'formEditor.elements.Form.finisher.EmailToSender.editor.attachUploads.label'
-                propertyPath: 'options.attachUploads'
diff --git a/typo3/sysext/form/Configuration/Yaml/FormEngineSetup.yaml b/typo3/sysext/form/Configuration/Yaml/FormEngineSetup.yaml
deleted file mode 100644 (file)
index 89221a1..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-TYPO3:
-  CMS:
-    Form:
-      prototypes:
-        standard:
-          formEngine:
-            translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
-
-          ########### TCE Forms CONFIGURATION ###########
-
-          ### FINISHERS ###
-          finishersDefinition:
-            EmailToSender:
-              FormEngine:
-                __inheritances:
-                  10: 'TYPO3.CMS.Form.mixins.FormEngineEmailMixin'
-
-            EmailToReceiver:
-              FormEngine:
-                __inheritances:
-                  10: 'TYPO3.CMS.Form.mixins.FormEngineEmailMixin'
-                label: 'tt_content.finishersDefinition.EmailToReceiver.label'
-                elements:
-                  subject:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.subject.label'
-                  recipientAddress:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.recipientAddress.label'
-                  recipientName:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.recipientName.label'
-                  senderAddress:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.senderAddress.label'
-                  senderName:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.senderName.label'
-                  replyToAddress:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.replyToAddress.label'
-                  carbonCopyAddress:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.carbonCopyAddress.label'
-                  blindCarbonCopyAddress:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.blindCarbonCopyAddress.label'
-                  format:
-                    label: 'tt_content.finishersDefinition.EmailToReceiver.format.label'
-                  translation:
-                    language:
-                      label: 'tt_content.finishersDefinition.EmailToReceiver.language.label'
-                      config:
-                        type: select
-                        renderType: 'selectSingle'
-                        minitems: 1
-                        maxitems: 1
-                        size: 1
-                        items:
-                          10:
-                            0: 'tt_content.finishersDefinition.EmailToReceiver.language.1'
-                            1: 'default'
-
-            Redirect:
-              FormEngine:
-                label: 'tt_content.finishersDefinition.Redirect.label'
-                elements:
-                  pageUid:
-                    label: 'tt_content.finishersDefinition.Redirect.pageUid.label'
-                    config:
-                      type: 'group'
-                      internal_type: 'db'
-                      allowed: 'pages'
-                      size: 1
-                      minitems: 1
-                      maxitems: 1
-                      fieldWizard:
-                        recordsOverview:
-                          disabled: 1
-                  additionalParameters:
-                    label: 'tt_content.finishersDefinition.Redirect.additionalParameters.label'
-                    config:
-                      type: 'input'
-
-      ########### MIXINS ###########
-      mixins:
-        FormEngineEmailMixin:
-          label: 'tt_content.finishersDefinition.EmailToSender.label'
-          elements:
-            subject:
-              label: 'tt_content.finishersDefinition.EmailToSender.subject.label'
-              config:
-                type: 'input'
-                eval: 'required'
-            recipientAddress:
-              label: 'tt_content.finishersDefinition.EmailToSender.recipientAddress.label'
-              config:
-                type: 'input'
-                eval: 'required'
-            recipientName:
-              label: 'tt_content.finishersDefinition.EmailToSender.recipientName.label'
-              config:
-                type: 'input'
-            senderAddress:
-              label: 'tt_content.finishersDefinition.EmailToSender.senderAddress.label'
-              config:
-                type: 'input'
-                eval: 'required'
-            senderName:
-              label: 'tt_content.finishersDefinition.EmailToSender.senderName.label'
-              config:
-                type: 'input'
-            replyToAddress:
-              label: 'tt_content.finishersDefinition.EmailToSender.replyToAddress.label'
-              config:
-                type: 'input'
-            carbonCopyAddress:
-              label: 'tt_content.finishersDefinition.EmailToSender.carbonCopyAddress.label'
-              config:
-                type: 'input'
-            blindCarbonCopyAddress:
-              label: 'tt_content.finishersDefinition.EmailToSender.blindCarbonCopyAddress.label'
-              config:
-                type: 'input'
-            format:
-              label: 'tt_content.finishersDefinition.EmailToSender.format.label'
-              config:
-                type: select
-                renderType: 'selectSingle'
-                minitems: 1
-                maxitems: 1
-                size: 1
-                items:
-                  10:
-                    0: 'tt_content.finishersDefinition.EmailToSender.format.1'
-                    1: 'html'
-                  20:
-                    0: 'tt_content.finishersDefinition.EmailToSender.format.2'
-                    1: 'plaintext'
diff --git a/typo3/sysext/form/Configuration/Yaml/FormSetup.yaml b/typo3/sysext/form/Configuration/Yaml/FormSetup.yaml
new file mode 100644 (file)
index 0000000..339002c
--- /dev/null
@@ -0,0 +1,1747 @@
+TYPO3:
+  CMS:
+    Form:
+
+      ########### FORM MANAGER CONFIGURATION (backend) ###########
+      formManager:
+        controller:
+          deleteAction:
+            errorMessage: formManagerController.deleteAction.error.body
+            errorTitle: formManagerController.deleteAction.error.title
+        dynamicRequireJsModules:
+          app: TYPO3/CMS/Form/Backend/FormManager
+          viewModel: TYPO3/CMS/Form/Backend/FormManager/ViewModel
+        javaScriptTranslationFile: 'EXT:form/Resources/Private/Language/locallang_formManager_javascript.xlf'
+        selectablePrototypesConfiguration:
+          100:
+            identifier: standard
+            label: formManager.selectablePrototypesConfiguration.standard.label
+            newFormTemplates:
+              100:
+                label: formManager.selectablePrototypesConfiguration.standard.newFormTemplates.blankForm.label
+                templatePath: 'EXT:form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/BlankForm.yaml'
+              200:
+                label: formManager.selectablePrototypesConfiguration.standard.newFormTemplates.simpleContactForm.label
+                templatePath: 'EXT:form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/SimpleContactForm.yaml'
+        stylesheets:
+          100: 'EXT:form/Resources/Public/Css/form.css'
+        translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
+
+      ########### MIXINS (frontend / backend) ###########
+      mixins:
+
+        ########### FORM ENGINE MIXINS (backend) ###########
+        FormEngineEmailMixin:
+          elements:
+            blindCarbonCopyAddress:
+              config:
+                type: input
+              label: tt_content.finishersDefinition.EmailToSender.blindCarbonCopyAddress.label
+            carbonCopyAddress:
+              config:
+                type: input
+              label: tt_content.finishersDefinition.EmailToSender.carbonCopyAddress.label
+            format:
+              config:
+                items:
+                  10:
+                    - tt_content.finishersDefinition.EmailToSender.format.1
+                    - html
+                  20:
+                    - tt_content.finishersDefinition.EmailToSender.format.2
+                    - plaintext
+                maxitems: 1
+                minitems: 1
+                renderType: selectSingle
+                size: 1
+                type: select
+              label: tt_content.finishersDefinition.EmailToSender.format.label
+            recipientAddress:
+              config:
+                eval: required
+                type: input
+              label: tt_content.finishersDefinition.EmailToSender.recipientAddress.label
+            recipientName:
+              config:
+                type: input
+              label: tt_content.finishersDefinition.EmailToSender.recipientName.label
+            replyToAddress:
+              config:
+                type: input
+              label: tt_content.finishersDefinition.EmailToSender.replyToAddress.label
+            senderAddress:
+              config:
+                eval: required
+                type: input
+              label: tt_content.finishersDefinition.EmailToSender.senderAddress.label
+            senderName:
+              config:
+                type: input
+              label: tt_content.finishersDefinition.EmailToSender.senderName.label
+            subject:
+              config:
+                eval: required
+                type: input
+              label: tt_content.finishersDefinition.EmailToSender.subject.label
+          label: tt_content.finishersDefinition.EmailToSender.label
+
+        ########### FINISHER MIXINS (frontend) ###########
+        finishersEmailMixin:
+          implementationClassName: TYPO3\CMS\Form\Domain\Finishers\EmailFinisher
+          options:
+            templateName: '{@format}.html'
+            templateRootPaths:
+              10: 'EXT:form/Resources/Private/Frontend/Templates/Finishers/Email/'
+
+        ########### FORM ELEMENT MIXINS (frontend / backend) ###########
+        formElementMixins:
+          # (backend)
+          BaseCollectionEditorsMixin:
+            __inheritances:
+              10: TYPO3.CMS.Form.mixins.formElementMixins.RemoveButtonMixin
+            100:
+              identifier: header
+              label: ''
+              templateName: Inspector-CollectionElementHeaderEditor
+
+          # (backend)
+          BaseFormElementMixin:
+            formEditor:
+              editors:
+                100:
+                  identifier: header
+                  templateName: Inspector-FormElementHeaderEditor
+                200:
+                  identifier: label
+                  label: formEditor.elements.BaseFormElementMixin.editor.label.label
+                  propertyPath: label
+                  templateName: Inspector-TextEditor
+              predefinedDefaults: {  }
+
+          # (frontend / backend)
+          FileUploadMixin:
+            __inheritances:
+              10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+            formEditor:
+              editors:
+                400:
+                  identifier: saveToFileMount
+                  label: formEditor.elements.FileUploadMixin.editor.saveToFileMount.label
+                  propertyPath: properties.saveToFileMount
+                  selectOptions:
+                    10:
+                      label: '1:/user_upload/'
+                      value: '1:/user_upload/'
+                  templateName: Inspector-SingleSelectEditor
+              predefinedDefaults:
+                properties:
+                  saveToFileMount: '1:/user_upload/'
+            implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload
+            properties:
+              saveToFileMount: '1:/user_upload/'
+
+          # (frontend / backend)
+          FormElementMixin:
+            __inheritances:
+              10: TYPO3.CMS.Form.mixins.formElementMixins.BaseFormElementMixin
+            formEditor:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.RemovableFormElementMixin
+              editors:
+                200:
+                  label: formEditor.elements.FormElement.editor.label.label
+                700:
+                  configurationOptions:
+                    numbersOfColumnsToUse:
+                      fieldExplanationText: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.fieldExplanationText
+                      label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.label
+                      propertyPath: 'properties.gridColumnClassAutoConfiguration.viewPorts.{@viewPortIdentifier}.numbersOfColumnsToUse'
+                    viewPorts:
+                      10:
+                        label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xs.label
+                        viewPortIdentifier: xs
+                      20:
+                        label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.sm.label
+                        viewPortIdentifier: sm
+                      30:
+                        label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.md.label
+                        viewPortIdentifier: md
+                      40:
+                        label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.lg.label
+                        viewPortIdentifier: lg
+                  identifier: gridColumnViewPortConfiguration
+                  label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.label
+                  templateName: Inspector-GridColumnViewPortConfigurationEditor
+                800:
+                  identifier: requiredValidator
+                  label: formEditor.elements.FormElement.editor.requiredValidator.label
+                  propertyPath: properties.fluidAdditionalAttributes.required
+                  propertyValue: required
+                  templateName: Inspector-RequiredValidatorEditor
+                  validatorIdentifier: NotEmpty
+            implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement
+            properties:
+              containerClassAttribute: input
+              elementClassAttribute: ''
+              elementErrorClassAttribute: error
+
+          # (backend)
+          MinimumMaximumEditorsMixin:
+            200:
+              identifier: minimum
+              label: formEditor.elements.MinimumMaximumEditorsMixin.editor.minimum.label
+              propertyPath: options.minimum
+              propertyValidators:
+                10: Integer
+              templateName: Inspector-TextEditor
+            300:
+              identifier: maximum
+              label: formEditor.elements.MinimumMaximumEditorsMixin.editor.maximum.label
+              propertyPath: options.maximum
+              propertyValidators:
+                10: Integer
+              templateName: Inspector-TextEditor
+
+          # (frontend / backend)
+          MultiSelectionMixin:
+            __inheritances:
+              10: TYPO3.CMS.Form.mixins.formElementMixins.SelectionMixin
+            formEditor:
+              editors:
+                300:
+                  multiSelection: true
+                  shouldShowPreselectedValueColumn: multiple
+                900:
+                  identifier: validators
+                  label: formEditor.elements.MultiSelectionMixin.editor.validators.label
+                  selectOptions:
+                    10:
+                      label: formEditor.elements.MultiSelectionMixin.editor.validators.EmptyValue.label
+                      value: ''
+                    20:
+                      label: formEditor.elements.MultiSelectionMixin.editor.validators.Count.label
+                      value: Count
+                  templateName: Inspector-ValidatorsEditor
+              propertyCollections:
+                validators:
+                  10:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        20: TYPO3.CMS.Form.mixins.formElementMixins.MinimumMaximumEditorsMixin
+                      100:
+                        label: formEditor.elements.MultiSelectionMixin.validators.Count.editor.header.label
+                    identifier: Count
+
+          # (frontend / backend)
+          ReadOnlyFormElementMixin:
+            __inheritances:
+              10: TYPO3.CMS.Form.mixins.formElementMixins.BaseFormElementMixin
+            formEditor:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.RemovableFormElementMixin
+              editors:
+                200:
+                  label: formEditor.elements.ReadOnlyFormElement.editor.label.label
+            implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement
+            renderingOptions:
+              _isReadOnlyFormElement: true
+
+          # (backend)
+          RemovableFormElementMixin:
+            editors:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.RemoveButtonMixin
+
+          # (backend)
+          RemoveButtonMixin:
+            9999:
+              identifier: removeButton
+              templateName: Inspector-RemoveElementEditor
+
+          # (frontend / backend)
+          SelectionMixin:
+            __inheritances:
+              10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+            formEditor:
+              editors:
+                300:
+                  enableAddRow: true
+                  enableDeleteRow: true
+                  identifier: options
+                  isSortable: true
+                  label: formEditor.elements.SelectionMixin.editor.options.label
+                  propertyPath: properties.options
+                  removeLastAvailableRowFlashMessageMessage: formEditor.elements.SelectionMixin.editor.options.removeLastAvailableRowFlashMessageMessage
+                  removeLastAvailableRowFlashMessageTitle: formEditor.elements.SelectionMixin.editor.options.removeLastAvailableRowFlashMessageTitle
+                  templateName: Inspector-PropertyGridEditor
+              predefinedDefaults:
+                properties:
+                  options: {  }
+
+          # (frontend / backend)
+          SingleSelectionMixin:
+            __inheritances:
+              10: TYPO3.CMS.Form.mixins.formElementMixins.SelectionMixin
+            formEditor:
+              editors:
+                300:
+                  multiSelection: false
+                  shouldShowPreselectedValueColumn: single
+
+          # (frontend / backend)
+          TextMixin:
+            __inheritances:
+              10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+            formEditor:
+              editors:
+                400:
+                  doNotSetIfPropertyValueIsEmpty: true
+                  identifier: placeholder
+                  label: formEditor.elements.TextMixin.editor.placeholder.label
+                  propertyPath: properties.fluidAdditionalAttributes.placeholder
+                  templateName: Inspector-TextEditor
+                500:
+                  identifier: defaultValue
+                  label: formEditor.elements.TextMixin.editor.defaultValue.label
+                  propertyPath: defaultValue
+                  templateName: Inspector-TextEditor
+                900:
+                  identifier: validators
+                  label: formEditor.elements.TextMixin.editor.validators.label
+                  selectOptions:
+                    10:
+                      label: formEditor.elements.TextMixin.editor.validators.EmptyValue.label
+                      value: ''
+                    20:
+                      label: formEditor.elements.TextMixin.editor.validators.Alphanumeric.label
+                      value: Alphanumeric
+                    30:
+                      label: formEditor.elements.TextMixin.editor.validators.Text.label
+                      value: Text
+                    40:
+                      label: formEditor.elements.TextMixin.editor.validators.StringLength.label
+                      value: StringLength
+                    50:
+                      label: formEditor.elements.TextMixin.editor.validators.EmailAddress.label
+                      value: EmailAddress
+                    60:
+                      label: formEditor.elements.TextMixin.editor.validators.Integer.label
+                      value: Integer
+                    70:
+                      label: formEditor.elements.TextMixin.editor.validators.Float.label
+                      value: Float
+                    80:
+                      label: formEditor.elements.TextMixin.editor.validators.NumberRange.label
+                      value: NumberRange
+                    90:
+                      label: formEditor.elements.TextMixin.editor.validators.RegularExpression.label
+                      value: RegularExpression
+                  templateName: Inspector-ValidatorsEditor
+              predefinedDefaults:
+                defaultValue: ''
+              propertyCollections:
+                validators:
+                  10:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                      100:
+                        label: formEditor.elements.TextMixin.validators.Alphanumeric.editor.header.label
+                    identifier: Alphanumeric
+                  20:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                      100:
+                        label: formEditor.elements.TextMixin.validators.Text.editor.header.label
+                    identifier: Text
+                  30:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        20: TYPO3.CMS.Form.mixins.formElementMixins.MinimumMaximumEditorsMixin
+                      100:
+                        label: formEditor.elements.TextMixin.validators.StringLength.editor.header.label
+                      200:
+                        additionalElementPropertyPaths:
+                          10: properties.fluidAdditionalAttributes.minlength
+                      300:
+                        additionalElementPropertyPaths:
+                          10: properties.fluidAdditionalAttributes.maxlength
+                    identifier: StringLength
+                  40:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                      100:
+                        label: formEditor.elements.TextMixin.validators.EmailAddress.editor.header.label
+                    identifier: EmailAddress
+                  50:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                      100:
+                        label: formEditor.elements.TextMixin.validators.Integer.editor.header.label
+                    identifier: Integer
+                  60:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                      100:
+                        label: formEditor.elements.TextMixin.validators.Float.editor.header.label
+                    identifier: Float
+                  70:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        20: TYPO3.CMS.Form.mixins.formElementMixins.MinimumMaximumEditorsMixin
+                      100:
+                        label: formEditor.elements.TextMixin.validators.NumberRange.editor.header.label
+                      200:
+                        additionalElementPropertyPaths:
+                          10: properties.fluidAdditionalAttributes.min
+                      300:
+                        additionalElementPropertyPaths:
+                          10: properties.fluidAdditionalAttributes.max
+                    identifier: NumberRange
+                  80:
+                    editors:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                      100:
+                        label: formEditor.elements.TextMixin.validators.RegularExpression.editor.header.label
+                      200:
+                        fieldExplanationText: formEditor.elements.TextMixin.validators.RegularExpression.editor.regex.fieldExplanationText
+                        identifier: regex
+                        label: formEditor.elements.TextMixin.validators.RegularExpression.editor.regex.label
+                        propertyPath: options.regularExpression
+                        propertyValidators:
+                          10: NotEmpty
+                        templateName: Inspector-TextEditor
+                    identifier: RegularExpression
+
+          # (backend)
+          formEmailFinisherMixin:
+            editors:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+              100:
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.header.label
+              200:
+                enableFormelementSelectionButton: true
+                identifier: subject
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.subject.label
+                propertyPath: options.subject
+                propertyValidators:
+                  10: NotEmpty
+                  20: FormElementIdentifierWithinCurlyBracesInclusive
+                templateName: Inspector-TextEditor
+              300:
+                enableFormelementSelectionButton: true
+                fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.recipientAddress.fieldExplanationText
+                identifier: recipientAddress
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.recipientAddress.label
+                propertyPath: options.recipientAddress
+                propertyValidators:
+                  10: NaiveEmail
+                  20: FormElementIdentifierWithinCurlyBracesExclusive
+                propertyValidatorsMode: OR
+                templateName: Inspector-TextEditor
+              400:
+                enableFormelementSelectionButton: true
+                fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.recipientName.fieldExplanationText
+                identifier: recipientName
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.recipientName.label
+                propertyPath: options.recipientName
+                propertyValidators:
+                  10: FormElementIdentifierWithinCurlyBracesInclusive
+                templateName: Inspector-TextEditor
+              500:
+                enableFormelementSelectionButton: true
+                fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.fieldExplanationText
+                identifier: senderAddress
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.label
+                propertyPath: options.senderAddress
+                propertyValidators:
+                  10: NaiveEmail
+                  20: FormElementIdentifierWithinCurlyBracesExclusive
+                propertyValidatorsMode: OR
+                templateName: Inspector-TextEditor
+              600:
+                enableFormelementSelectionButton: true
+                fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.senderName.fieldExplanationText
+                identifier: senderName
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.senderName.label
+                propertyPath: options.senderName
+                propertyValidators:
+                  10: FormElementIdentifierWithinCurlyBracesInclusive
+                templateName: Inspector-TextEditor
+              700:
+                enableFormelementSelectionButton: true
+                identifier: replyToAddress
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.replyToAddress.label
+                propertyPath: options.replyToAddress
+                propertyValidators:
+                  10: NaiveEmailOrEmpty
+                  20: FormElementIdentifierWithinCurlyBracesExclusive
+                propertyValidatorsMode: OR
+                templateName: Inspector-TextEditor
+              800:
+                enableFormelementSelectionButton: true
+                identifier: carbonCopyAddress
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.carbonCopyAddress.label
+                propertyPath: options.carbonCopyAddress
+                propertyValidators:
+                  10: NaiveEmailOrEmpty
+                  20: FormElementIdentifierWithinCurlyBracesExclusive
+                propertyValidatorsMode: OR
+                templateName: Inspector-TextEditor
+              900:
+                enableFormelementSelectionButton: true
+                identifier: blindCarbonCopyAddress
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.blindCarbonCopyAddress.label
+                propertyPath: options.blindCarbonCopyAddress
+                propertyValidators:
+                  10: NaiveEmailOrEmpty
+                  20: FormElementIdentifierWithinCurlyBracesExclusive
+                propertyValidatorsMode: OR
+                templateName: Inspector-TextEditor
+              1000:
+                identifier: format
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.format.label
+                propertyPath: options.format
+                selectOptions:
+                  10:
+                    label: formEditor.elements.Form.finisher.EmailToSender.editor.format.1
+                    value: plaintext
+                  20:
+                    label: formEditor.elements.Form.finisher.EmailToSender.editor.format.2
+                    value: html
+                templateName: Inspector-SingleSelectEditor
+              1100:
+                identifier: attachUploads
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.attachUploads.label
+                propertyPath: options.attachUploads
+                templateName: Inspector-CheckboxEditor
+
+        ########### TRANSLATION MIXINS (frontend) ###########
+        translationSettingsMixin:
+          translation:
+            translationFile: 'EXT:form/Resources/Private/Language/locallang.xlf'
+
+      ########### PERSISTENCE MANAGER CONFIGURATION (frontend / backend) ###########
+      persistenceManager:
+        allowDeleteFromExtensionPaths: false
+        allowSaveToExtensionPaths: false
+        allowedFileMounts:
+          10: '1:/form_definitions/'
+          20: '1:/user_upload/'
+
+      ########### PROTOTYPES (frontend / backend) ###########
+      prototypes:
+        standard:
+
+          ########### FINISHERS ###########
+          finishersDefinition:
+            Closure:
+              formEditor:
+                iconIdentifier: t3-form-icon-finisher
+                label: formEditor.elements.Form.finisher.Closure.editor.header.label
+                predefinedDefaults:
+                  options:
+                    closure: ''
+              implementationClassName: TYPO3\CMS\Form\Domain\Finishers\ClosureFinisher
+
+            Confirmation:
+              formEditor:
+                iconIdentifier: t3-form-icon-finisher
+                label: formEditor.elements.Form.finisher.Confirmation.editor.header.label
+                predefinedDefaults:
+                  options:
+                    contentElementUid: ''
+                    message: ''
+              implementationClassName: TYPO3\CMS\Form\Domain\Finishers\ConfirmationFinisher
+
+            DeleteUploads:
+              formEditor:
+                iconIdentifier: t3-form-icon-finisher
+                label: formEditor.elements.Form.finisher.DeleteUploads.editor.header.label
+              implementationClassName: TYPO3\CMS\Form\Domain\Finishers\DeleteUploadsFinisher
+
+            EmailToReceiver:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.finishersEmailMixin
+
+              # (backend)
+              FormEngine:
+                __inheritances:
+                  10: TYPO3.CMS.Form.mixins.FormEngineEmailMixin
+                elements:
+                  blindCarbonCopyAddress:
+                    label: tt_content.finishersDefinition.EmailToReceiver.blindCarbonCopyAddress.label
+                  carbonCopyAddress:
+                    label: tt_content.finishersDefinition.EmailToReceiver.carbonCopyAddress.label
+                  format:
+                    label: tt_content.finishersDefinition.EmailToReceiver.format.label
+                  recipientAddress:
+                    label: tt_content.finishersDefinition.EmailToReceiver.recipientAddress.label
+                  recipientName:
+                    label: tt_content.finishersDefinition.EmailToReceiver.recipientName.label
+                  replyToAddress:
+                    label: tt_content.finishersDefinition.EmailToReceiver.replyToAddress.label
+                  senderAddress:
+                    label: tt_content.finishersDefinition.EmailToReceiver.senderAddress.label
+                  senderName:
+                    label: tt_content.finishersDefinition.EmailToReceiver.senderName.label
+                  subject:
+                    label: tt_content.finishersDefinition.EmailToReceiver.subject.label
+                  translation:
+                    language:
+                      config:
+                        items:
+                          10:
+                            - tt_content.finishersDefinition.EmailToReceiver.language.1
+                            - default
+                        maxitems: 1
+                        minitems: 1
+                        renderType: selectSingle
+                        size: 1
+                        type: select
+                      label: tt_content.finishersDefinition.EmailToReceiver.language.label
+                label: tt_content.finishersDefinition.EmailToReceiver.label
+
+              # (backend)
+              formEditor:
+                iconIdentifier: t3-form-icon-finisher
+                label: formEditor.elements.Form.finisher.EmailToReceiver.editor.header.label
+                predefinedDefaults:
+                  options:
+                    attachUploads: true
+                    blindCarbonCopyAddress: ''
+                    carbonCopyAddress: ''
+                    format: html
+                    recipientAddress: ''
+                    recipientName: ''
+                    replyToAddress: ''
+                    senderAddress: ''
+                    senderName: ''
+                    subject: ''
+                    translation:
+                      language: ''
+
+            EmailToSender:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.finishersEmailMixin
+              # (backend)
+              FormEngine:
+                __inheritances:
+                  10: TYPO3.CMS.Form.mixins.FormEngineEmailMixin
+
+              # (backend)
+              formEditor:
+                iconIdentifier: t3-form-icon-finisher
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.header.label
+                predefinedDefaults:
+                  options:
+                    attachUploads: true
+                    blindCarbonCopyAddress: ''
+                    carbonCopyAddress: ''
+                    format: html
+                    recipientAddress: ''
+                    recipientName: ''
+                    replyToAddress: ''
+                    senderAddress: ''
+                    senderName: ''
+                    subject: ''
+
+            FlashMessage:
+              # (backend)
+              formEditor:
+                iconIdentifier: t3-form-icon-finisher
+                label: formEditor.elements.Form.finisher.FlashMessage.editor.header.label
+                predefinedDefaults:
+                  options:
+                    messageArguments: ''
+                    messageBody: ''
+                    messageCode: 0
+                    messageTitle: ''
+                    severity: 0
+              implementationClassName: TYPO3\CMS\Form\Domain\Finishers\FlashMessageFinisher
+
+            Redirect:
+              # (backend)
+              FormEngine:
+                elements:
+                  additionalParameters:
+                    config:
+                      type: input
+                    label: tt_content.finishersDefinition.Redirect.additionalParameters.label
+                  pageUid:
+                    config:
+                      allowed: pages
+                      fieldWizard:
+                        recordsOverview:
+                          disabled: 1
+                      internal_type: db
+                      maxitems: 1
+                      minitems: 1
+                      size: 1
+                      type: group
+                    label: tt_content.finishersDefinition.Redirect.pageUid.label
+                label: tt_content.finishersDefinition.Redirect.label
+
+              # (backend)
+              formEditor:
+                iconIdentifier: t3-form-icon-finisher
+                label: formEditor.elements.Form.finisher.Redirect.editor.header.label
+                predefinedDefaults:
+                  options:
+                    additionalParameters: ''
+                    pageUid: ''
+              implementationClassName: TYPO3\CMS\Form\Domain\Finishers\RedirectFinisher
+
+            SaveToDatabase:
+              # (backend)
+              formEditor:
+                iconIdentifier: t3-form-icon-finisher
+                label: formEditor.elements.Form.finisher.SaveToDatabase.editor.header.label
+                predefinedDefaults:
+                  options: {  }
+              implementationClassName: TYPO3\CMS\Form\Domain\Finishers\SaveToDatabaseFinisher
+
+          ########### FORM EDITOR CONFIGURATION (backend) ###########
+          formEditor:
+            addInlineSettings: {  }
+            dynamicRequireJsModules:
+              app: TYPO3/CMS/Form/Backend/FormEditor
+              mediator: TYPO3/CMS/Form/Backend/FormEditor/Mediator
+              viewModel: TYPO3/CMS/Form/Backend/FormEditor/ViewModel
+
+            formEditorFluidConfiguration:
+              layoutRootPaths:
+                10: 'EXT:form/Resources/Private/Backend/Layouts/FormEditor/'
+              partialRootPaths:
+                10: 'EXT:form/Resources/Private/Backend/Partials/FormEditor/'
+              templatePathAndFilename: 'EXT:form/Resources/Private/Backend/Templates/FormEditor/InlineTemplates.html'
+
+            formEditorPartials:
+              FormElement-AdvancedPassword: Stage/SimpleTemplate
+              FormElement-Checkbox: Stage/SimpleTemplate
+              FormElement-ContentElement: Stage/ContentElement
+              FormElement-DatePicker: Stage/SimpleTemplate
+              FormElement-Email: Stage/SimpleTemplate
+              FormElement-Fieldset: Stage/Fieldset
+              FormElement-FileUpload: Stage/FileUploadTemplate
+              FormElement-GridContainer: Stage/Fieldset
+              FormElement-GridRow: Stage/Fieldset
+              FormElement-Hidden: Stage/SimpleTemplate
+              FormElement-ImageUpload: Stage/FileUploadTemplate
+              FormElement-MultiCheckbox: Stage/SelectTemplate
+              FormElement-MultiSelect: Stage/SelectTemplate
+              FormElement-Number: Stage/SimpleTemplate
+              FormElement-Page: Stage/Page
+              FormElement-Password: Stage/SimpleTemplate
+              FormElement-RadioButton: Stage/SelectTemplate
+              FormElement-SingleSelect: Stage/SelectTemplate
+              FormElement-StaticText: Stage/StaticText
+              FormElement-SummaryPage: Stage/SummaryPage
+              FormElement-Telephone: Stage/SimpleTemplate
+              FormElement-Text: Stage/SimpleTemplate
+              FormElement-Textarea: Stage/SimpleTemplate
+              FormElement-Url: Stage/SimpleTemplate
+              FormElement-_ElementToolbar: Stage/_ElementToolbar
+              FormElement-_UnknownElement: Stage/_UnknownElement
+              Inspector-CheckboxEditor: Inspector/CheckboxEditor
+              Inspector-CollectionElementHeaderEditor: Inspector/CollectionElementHeaderEditor
+              Inspector-FinishersEditor: Inspector/FinishersEditor
+              Inspector-FormElementHeaderEditor: Inspector/FormElementHeaderEditor
+              Inspector-GridColumnViewPortConfigurationEditor: Inspector/GridColumnViewPortConfigurationEditor
+              Inspector-MultiSelectEditor: Inspector/MultiSelectEditor
+              Inspector-PropertyGridEditor: Inspector/PropertyGridEditor
+              Inspector-RemoveElementEditor: Inspector/RemoveElementEditor
+              Inspector-RequiredValidatorEditor: Inspector/RequiredValidatorEditor
+              Inspector-SingleSelectEditor: Inspector/SingleSelectEditor
+              Inspector-TextEditor: Inspector/TextEditor
+              Inspector-TextareaEditor: Inspector/TextareaEditor
+              Inspector-Typo3WinBrowserEditor: Inspector/Typo3WinBrowserEditor
+              Inspector-ValidatorsEditor: Inspector/ValidatorsEditor
+              Modal-InsertElements: Modals/InsertElements
+              Modal-InsertPages: Modals/InsertPages
+              Modal-ValidationErrors: Modals/ValidationErrors
+
+            formElementGroups:
+              container:
+                label: formEditor.formElementGroups.container.label
+              custom:
+                label: formEditor.formElementGroups.custom.label
+              html5:
+                label: formEditor.formElementGroups.html5.label
+              input:
+                label: formEditor.formElementGroups.input.label
+              page:
+                label: formEditor.formElementGroups.page.label
+              select:
+                label: formEditor.formElementGroups.select.label
+
+            formElementPropertyValidatorsDefinition:
+              FormElementIdentifierWithinCurlyBracesExclusive:
+                errorMessage: formEditor.formElementPropertyValidatorsDefinition.FormElementIdentifierWithinCurlyBraces.label
+              FormElementIdentifierWithinCurlyBracesInclusive:
+                errorMessage: formEditor.formElementPropertyValidatorsDefinition.FormElementIdentifierWithinCurlyBraces.label
+              Integer:
+                errorMessage: formEditor.formElementPropertyValidatorsDefinition.Integer.label
+              NaiveEmail:
+                errorMessage: formEditor.formElementPropertyValidatorsDefinition.NaiveEmail.label
+              NaiveEmailOrEmpty:
+                errorMessage: formEditor.formElementPropertyValidatorsDefinition.NaiveEmail.label
+              NotEmpty:
+                errorMessage: formEditor.formElementPropertyValidatorsDefinition.NotEmpty.label
+
+            maximumUndoSteps: 10
+            stylesheets:
+              200: 'EXT:form/Resources/Public/Css/form.css'
+            translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
+
+          ########### FORM ELEMENT DEFINITIONS (frontend / backend) ###########
+          formElementsDefinition:
+
+            ########### AdvancedPassword ###########
+            AdvancedPassword:
+              __inheritances:
+                10: TYPO3.CMS.Form.prototypes.standard.formElementsDefinition.Password
+              formEditor:
+                editors:
+                  300:
+                    identifier: confirmationLabel
+                    label: formEditor.elements.AdvancedPassword.editor.confirmationLabel.label
+                    propertyPath: properties.confirmationLabel
+                    templateName: Inspector-TextEditor
+                group: custom
+                groupSorting: 500
+                iconIdentifier: t3-form-icon-advanced-password
+                label: formEditor.elements.AdvancedPassword.label
+                predefinedDefaults:
+                  properties:
+                    confirmationLabel: formEditor.element.AdvancedPassword.editor.confirmationLabel.predefinedDefaults
+              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement
+              properties:
+                confirmationClassAttribute: input-medium
+                confirmationLabel: ''
+                elementClassAttribute: input-medium
+
+            ########### Checkbox ###########
+            Checkbox:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+              formEditor:
+                group: select
+                groupSorting: 100
+                iconIdentifier: t3-form-icon-checkbox
+                label: formEditor.elements.Checkbox.label
+              properties:
+                containerClassAttribute: 'input checkbox'
+                elementClassAttribute: add-on
+                value: 1
+
+            ########### ContentElement ###########
+            ContentElement:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.ReadOnlyFormElementMixin
+              formEditor:
+                editors:
+                  300:
+                    browsableType: tt_content
+                    buttonLabel: formEditor.elements.ContentElement.editor.contentElement.buttonLabel
+                    identifier: contentElement
+                    label: formEditor.elements.ContentElement.editor.contentElement.label
+                    propertyPath: properties.contentElementUid
+                    propertyValidators:
+                      10: Integer
+                      20: FormElementIdentifierWithinCurlyBracesExclusive
+                    propertyValidatorsMode: OR
+                    templateName: Inspector-Typo3WinBrowserEditor
+                group: custom
+                groupSorting: 700
+                iconIdentifier: t3-form-icon-content-element
+                label: formEditor.elements.ContentElement.label
+                predefinedDefaults:
+                  properties:
+                    contentElementUid: ''
+              properties:
+                contentElementUid: ''
+
+            ########### DatePicker ###########
+            DatePicker:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+              formEditor:
+                editors:
+                  300:
+                    identifier: dateFormat
+                    label: formEditor.elements.DatePicker.editor.dateFormat.label
+                    propertyPath: properties.dateFormat
+                    templateName: Inspector-TextEditor
+                  400:
+                    identifier: enableDatePicker
+                    label: formEditor.elements.DatePicker.editor.enableDatePicker.label
+                    propertyPath: properties.enableDatePicker
+                    templateName: Inspector-CheckboxEditor
+                  500:
+                    identifier: displayTimeSelector
+                    label: formEditor.elements.DatePicker.editor.displayTimeSelector.label
+                    propertyPath: properties.displayTimeSelector
+                    templateName: Inspector-CheckboxEditor
+                  900:
+                    identifier: validators
+                    label: formEditor.elements.DatePicker.editor.validators.label
+                    selectOptions:
+                      10:
+                        label: formEditor.elements.DatePicker.editor.validators.EmptyValue.label
+                        value: ''
+                      20:
+                        label: formEditor.elements.DatePicker.editor.validators.DateTime.label
+                        value: DateTime
+                    templateName: Inspector-ValidatorsEditor
+                group: custom
+                groupSorting: 200
+                iconIdentifier: t3-form-icon-date-picker
+                label: formEditor.elements.DatePicker.label
+                predefinedDefaults:
+                  properties:
+                    dateFormat: Y-m-d
+                    displayTimeSelector: false
+                    enableDatePicker: true
+                propertyCollections:
+                  validators:
+                    10:
+                      editors:
+                        __inheritances:
+                          10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        100:
+                          label: formEditor.elements.DatePicker.validators.DateTime.editor.header.label
+                      identifier: DateTime
+              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\DatePicker
+              properties:
+                dateFormat: Y-m-d
+                displayTimeSelector: false
+                elementClassAttribute: 'small form-control'
+                enableDatePicker: true
+                timeSelectorClassAttribute: mini
+                timeSelectorHourLabel: ''
+                timeSelectorMinuteLabel: ''
+
+            ########### Email ###########
+            Email:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
+              formEditor:
+                editors:
+                  500:
+                    propertyValidators:
+                      10: NaiveEmailOrEmpty
+                  900:
+                    selectOptions: {  }
+                group: html5
+                groupSorting: 100
+                iconIdentifier: t3-form-icon-email
+                label: formEditor.elements.Email.label
+                predefinedDefaults:
+                  validators:
+                    -
+                      identifier: EmailAddress
+                propertyCollections:
+                  validators:
+                    40:
+                      editors: {  }
+              validators:
+                -
+                  identifier: EmailAddress
+
+            ########### Fieldset ###########
+            Fieldset:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+              formEditor:
+                _isCompositeFormElement: true
+                editors:
+                  200:
+                    label: formEditor.elements.Fieldset.editor.label.label
+                group: container
+                groupSorting: 100
+                iconIdentifier: t3-form-icon-fieldset
+                label: formEditor.elements.Fieldset.label
+              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\Section
+              renderingOptions:
+                _isCompositeFormElement: true
+
+            ########### FileUpload ###########
+            FileUpload:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.FileUploadMixin
+              formEditor:
+                editors:
+                  300:
+                    identifier: allowedMimeTypes
+                    label: formEditor.elements.FileUpload.editor.allowedMimeTypes.label
+                    propertyPath: properties.allowedMimeTypes
+                    selectOptions:
+                      10:
+                        label: formEditor.elements.FileUpload.editor.allowedMimeTypes.doc
+                        value: application/msword
+                      20:
+                        label: formEditor.elements.FileUpload.editor.allowedMimeTypes.docx
+                        value: application/vnd.openxmlformats-officedocument.wordprocessingml.document
+                      30:
+                        label: formEditor.elements.FileUpload.editor.allowedMimeTypes.xls
+                        value: application/msexcel
+                      40:
+                        label: formEditor.elements.FileUpload.editor.allowedMimeTypes.xlsx
+                        value: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+                      50:
+                        label: formEditor.elements.FileUpload.editor.allowedMimeTypes.pdf
+                        value: application/pdf
+                      60:
+                        label: formEditor.elements.FileUpload.editor.allowedMimeTypes.odt
+                        value: application/vnd.oasis.opendocument.text
+                      70:
+                        label: formEditor.elements.FileUpload.editor.allowedMimeTypes.ods
+                        value: application/vnd.oasis.opendocument.spreadsheet-template
+                    templateName: Inspector-MultiSelectEditor
+                group: custom
+                groupSorting: 100
+                iconIdentifier: t3-form-icon-file-upload
+                label: formEditor.elements.FileUpload.label
+                predefinedDefaults:
+                  properties:
+                    allowedMimeTypes:
+                      - application/pdf
+              properties:
+                allowedMimeTypes:
+                  - application/msword
+                  - application/vnd.openxmlformats-officedocument.wordprocessingml.document
+                  - application/vnd.oasis.opendocument.text
+                  - application/pdf
+
+            ########### Form ###########
+            Form:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.BaseFormElementMixin
+              formEditor:
+                _isCompositeFormElement: false
+                _isTopLevelFormElement: true
+                editors:
+                  300:
+                    identifier: submitButtonLabel
+                    label: formEditor.elements.Form.editor.submitButtonLabel.label
+                    propertyPath: renderingOptions.submitButtonLabel
+                    templateName: Inspector-TextEditor
+                  900:
+                    identifier: finishers
+                    label: formEditor.elements.Form.editor.finishers.label
+                    selectOptions:
+                      10:
+                        label: formEditor.elements.Form.editor.finishers.EmptyValue.label
+                        value: ''
+                      20:
+                        label: formEditor.elements.Form.editor.finishers.EmailToSender.label
+                        value: EmailToSender
+                      30:
+                        label: formEditor.elements.Form.editor.finishers.EmailToReceiver.label
+                        value: EmailToReceiver
+                      40:
+                        label: formEditor.elements.Form.editor.finishers.Redirect.label
+                        value: Redirect
+                      50:
+                        label: formEditor.elements.Form.editor.finishers.DeleteUploads.label
+                        value: DeleteUploads
+                      60:
+                        label: formEditor.elements.Form.editor.finishers.Confirmation.label
+                        value: Confirmation
+                    templateName: Inspector-FinishersEditor
+                iconIdentifier: content-elements-mailform
+                inspectorEditorFormElementSelectorNoElements: formEditor.inspector.editor.formelement_selector.no_elements
+                modalCloseCancleButton: formEditor.modals.close.cancleButton
+                modalCloseConfirmButton: formEditor.modals.close.confirmButton
+                modalCloseDialogMessage: formEditor.modals.close.dialogMessage
+                modalCloseDialogTitle: formEditor.modals.close.dialogTitle
+                modalInsertElementsDialogTitle: formEditor.modals.insertElements.dialogTitle
+                modalInsertPagesDialogTitle: formEditor.modals.newPages.dialogTitle
+                modalRemoveElementCancleButton: formEditor.modals.removeElement.cancleButton
+                modalRemoveElementConfirmButton: formEditor.modals.removeElement.confirmButton
+                modalRemoveElementDialogMessage: formEditor.modals.removeElement.dialogMessage
+                modalRemoveElementDialogTitle: formEditor.modals.removeElement.dialogTitle
+                modalRemoveElementLastAvailablePageFlashMessageMessage: formEditor.modals.removeElement.lastAvailablePageFlashMessageMessage
+                modalRemoveElementLastAvailablePageFlashMessageTitle: formEditor.modals.removeElement.lastAvailablePageFlashMessageTitle
+                modalValidationErrorsConfirmButton: formEditor.modals.validationErrors.confirmButton
+                modalValidationErrorsDialogTitle: formEditor.modals.validationErrors.dialogTitle
+                paginationTitle: formEditor.pagination.title
+                predefinedDefaults:
+                  renderingOptions:
+                    submitButtonLabel: formEditor.elements.Form.editor.submitButtonLabel.value
+                propertyCollections:
+                  finishers:
+                    10:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.formEmailFinisherMixin
+                      identifier: EmailToSender
+                    20:
+                      __inheritances:
+                        10: TYPO3.CMS.Form.mixins.formElementMixins.formEmailFinisherMixin
+                      editors:
+                        100:
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.header.label
+                        200:
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.subject.label
+                        300:
+                          fieldExplanationText: formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientAddress.fieldExplanationText
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientAddress.label
+                        400:
+                          fieldExplanationText: formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientName.fieldExplanationText
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientName.label
+                        500:
+                          fieldExplanationText: formEditor.elements.Form.finisher.EmailToReceiver.editor.senderAddress.fieldExplanationText
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.senderAddress.label
+                        600:
+                          fieldExplanationText: formEditor.elements.Form.finisher.EmailToReceiver.editor.senderName.fieldExplanationText
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.senderName.label
+                        700:
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.replyToAddress.label
+                        800:
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.carbonCopyAddress.label
+                        900:
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.blindCarbonCopyAddress.label
+                        1000:
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.format.label
+                        1100:
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.attachUploads.label
+                        1200:
+                          identifier: language
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.language.label
+                          propertyPath: options.translation.language
+                          selectOptions:
+                            10:
+                              label: formEditor.elements.Form.finisher.EmailToReceiver.editor.language.1
+                              value: default
+                          templateName: Inspector-SingleSelectEditor
+                      identifier: EmailToReceiver
+                    30:
+                      editors:
+                        __inheritances:
+                          10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        100:
+                          label: formEditor.elements.Form.finisher.Redirect.editor.header.label
+                        200:
+                          browsableType: pages
+                          buttonLabel: formEditor.elements.Form.finisher.Redirect.editor.pageUid.buttonLabel
+                          identifier: pageUid
+                          label: formEditor.elements.Form.finisher.Redirect.editor.pageUid.label
+                          propertyPath: options.pageUid
+                          propertyValidators:
+                            10: Integer
+                            20: FormElementIdentifierWithinCurlyBracesExclusive
+                          propertyValidatorsMode: OR
+                          templateName: Inspector-Typo3WinBrowserEditor
+                        300:
+                          identifier: additionalParameters
+                          label: formEditor.elements.Form.finisher.Redirect.editor.additionalParameters.label
+                          propertyPath: options.additionalParameters
+                          templateName: Inspector-TextEditor
+                      identifier: Redirect
+                    40:
+                      editors:
+                        __inheritances:
+                          10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        100:
+                          label: formEditor.elements.Form.finisher.DeleteUploads.editor.header.label
+                      identifier: DeleteUploads
+                    50:
+                      editors:
+                        __inheritances:
+                          10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        100:
+                          label: formEditor.elements.Form.finisher.Confirmation.editor.header.label
+                        200:
+                          browsableType: tt_content
+                          buttonLabel: formEditor.elements.Form.finisher.Confirmation.editor.contentElement.buttonLabel
+                          identifier: contentElement
+                          label: formEditor.elements.Form.finisher.Confirmation.editor.contentElement.label
+                          propertyPath: options.contentElementUid
+                          propertyValidators:
+                            10: IntegerOrEmpty
+                            20: FormElementIdentifierWithinCurlyBracesExclusive
+                          propertyValidatorsMode: OR
+                          templateName: Inspector-Typo3WinBrowserEditor
+                        300:
+                          fieldExplanationText: formEditor.elements.Form.finisher.Confirmation.editor.message.fieldExplanationText
+                          identifier: message
+                          label: formEditor.elements.Form.finisher.Confirmation.editor.message.label
+                          propertyPath: options.message
+                          templateName: Inspector-TextareaEditor
+                      identifier: Confirmation
+                    60:
+                      editors:
+                        __inheritances:
+                          10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        100:
+                          label: formEditor.elements.Form.finisher.Closure.editor.header.label
+                      identifier: Closure
+                    70:
+                      editors:
+                        __inheritances:
+                          10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        100:
+                          label: formEditor.elements.Form.finisher.FlashMessage.editor.header.label
+                      identifier: FlashMessage
+                    80:
+                      editors:
+                        __inheritances:
+                          10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        100:
+                          label: formEditor.elements.Form.finisher.SaveToDatabase.editor.header.label
+                      identifier: SaveToDatabase
+                saveErrorFlashMessageMessage: formEditor.elements.Form.saveErrorFlashMessageMessage
+                saveErrorFlashMessageTitle: formEditor.elements.Form.saveErrorFlashMessageTitle
+                saveSuccessFlashMessageMessage: formEditor.elements.Form.saveSuccessFlashMessageMessage
+                saveSuccessFlashMessageTitle: formEditor.elements.Form.saveSuccessFlashMessageTitle
+
+              rendererClassName: TYPO3\CMS\Form\Domain\Renderer\FluidFormRenderer
+              renderingOptions:
+                __inheritances:
+                  10: TYPO3.CMS.Form.mixins.translationSettingsMixin
+                _isCompositeFormElement: false
+                _isTopLevelFormElement: true
+                addQueryString: false
+                additionalParams: {  }
+                argumentsToBeExcludedFromQueryString: {  }
+                controllerAction: perform
+                honeypot:
+                  enable: true
+                  formElementToUse: Honeypot
+                httpEnctype: multipart/form-data
+                httpMethod: post
+                layoutRootPaths:
+                  10: 'EXT:form/Resources/Private/Frontend/Layouts/'
+                partialRootPaths:
+                  10: 'EXT:form/Resources/Private/Frontend/Partials/'
+                skipUnknownElements: true
+                submitButtonLabel: Submit
+                templateRootPaths:
+                  10: 'EXT:form/Resources/Private/Frontend/Templates/'
+
+            ########### GridContainer ###########
+            GridContainer:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+              formEditor:
+                _isCompositeFormElement: true
+                _isGridContainerFormElement: true
+                editors:
+                  200:
+                    label: formEditor.elements.GridContainer.editor.label.label
+                iconIdentifier: t3-form-icon-gridcontainer
+                label: formEditor.elements.GridContainer.label
+              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GridContainer
+              properties:
+                elementClassAttribute: container
+                gridColumnClassAutoConfiguration:
+                  gridSize: 12
+                  viewPorts:
+                    lg:
+                      classPattern: 'col-lg-{@numbersOfColumnsToUse}'
+                    md:
+                      classPattern: 'col-md-{@numbersOfColumnsToUse}'
+                    sm:
+                      classPattern: 'col-sm-{@numbersOfColumnsToUse}'
+                    xs:
+                      classPattern: 'col-xs-{@numbersOfColumnsToUse}'
+              renderingOptions:
+                _isCompositeFormElement: true
+                _isGridContainerFormElement: true
+
+            ########### GridRow ###########
+            GridRow:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+              formEditor:
+                _isCompositeFormElement: true
+                _isGridRowFormElement: true
+                editors:
+                  200:
+                    label: formEditor.elements.GridRow.editor.label.label
+                group: container
+                groupSorting: 300
+                iconIdentifier: t3-form-icon-gridrow
+                label: formEditor.elements.GridRow.label
+              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GridRow
+              properties:
+                elementClassAttribute: row
+                gridColumnClassAutoConfiguration:
+                  gridSize: 12
+                  viewPorts:
+                    lg:
+                      classPattern: 'col-lg-{@numbersOfColumnsToUse}'
+                    md:
+                      classPattern: 'col-md-{@numbersOfColumnsToUse}'
+                    sm:
+                      classPattern: 'col-sm-{@numbersOfColumnsToUse}'
+                    xs:
+                      classPattern: 'col-xs-{@numbersOfColumnsToUse}'
+              renderingOptions:
+                _isCompositeFormElement: true
+                _isGridRowFormElement: true
+
+            ########### Hidden ###########
+            Hidden:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.FormElementMixin
+              formEditor:
+                editors:
+                  300:
+                    identifier: defaultValue
+                    label: formEditor.elements.Hidden.editor.defaultValue.label
+                    propertyPath: defaultValue
+                    templateName: Inspector-TextEditor
+                group: custom
+                groupSorting: 300
+                iconIdentifier: t3-form-icon-hidden
+                label: formEditor.elements.Hidden.label
+                predefinedDefaults:
+                  defaultValue: ''
+              renderingOptions:
+                _isHiddenFormElement: true
+
+            ########### Honeypot ###########
+            Honeypot:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
+              properties:
+                renderAsHiddenField: false
+                styleAttribute: 'position:absolute; margin:0 0 0 -999em;'
+              renderingOptions:
+                _isHiddenFormElement: true
+
+            ########### ImageUpload ###########
+            ImageUpload:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.FileUploadMixin
+              formEditor:
+                editors:
+                  300:
+                    identifier: allowedMimeTypes
+                    label: formEditor.elements.ImageUpload.editor.allowedMimeTypes.label
+                    propertyPath: properties.allowedMimeTypes
+                    selectOptions:
+                      10:
+                        label: formEditor.elements.ImageUpload.editor.allowedMimeTypes.jpg
+                        value: image/jpeg
+                      20:
+                        label: formEditor.elements.ImageUpload.editor.allowedMimeTypes.png
+                        value: image/png
+                      30:
+                        label: formEditor.elements.ImageUpload.editor.allowedMimeTypes.bmp
+                        value: image/bmp
+                    templateName: Inspector-MultiSelectEditor
+                group: custom
+                groupSorting: 400
+                iconIdentifier: t3-form-icon-image-upload
+                label: formEditor.elements.ImageUpload.label
+                predefinedDefaults:
+                  properties:
+                    allowedMimeTypes:
+                      - image/jpeg
+              properties:
+                allowedMimeTypes:
+                  - image/jpeg
+                  - image/png
+                  - image/bmp
+                elementClassAttribute: lightbox
+                imageLinkMaxWidth: 500
+                imageMaxHeight: 500
+                imageMaxWidth: 500
+
+            ########### MultiCheckbox ###########
+            MultiCheckbox:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.MultiSelectionMixin
+              formEditor:
+                editors:
+                  800: {  }
+                group: select
+                groupSorting: 400
+                iconIdentifier: t3-form-icon-multi-checkbox
+                label: formEditor.elements.MultiCheckbox.label
+              properties:
+                containerClassAttribute: 'input checkbox'
+
+            ########### MultiSelect ###########
+            MultiSelect:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.MultiSelectionMixin
+              formEditor:
+                editors:
+                  250:
+                    doNotSetIfPropertyValueIsEmpty: true
+                    fieldExplanationText: formEditor.elements.SelectionMixin.editor.inactiveOption.fieldExplanationText
+                    identifier: inactiveOption
+                    label: formEditor.elements.SelectionMixin.editor.inactiveOption.label
+                    propertyPath: properties.prependOptionLabel
+                    templateName: Inspector-TextEditor
+                group: select
+                groupSorting: 500
+                iconIdentifier: t3-form-icon-multi-select
+                label: formEditor.elements.MultiSelect.label
+              properties:
+                elementClassAttribute: xlarge
+
+            ########### Number ###########
+            Number:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
+              formEditor:
+                editors:
+                  500:
+                    propertyValidators:
+                      10: IntegerOrEmpty
+                  700:
+                    identifier: step
+                    label: formEditor.elements.TextMixin.editor.step.label
+                    propertyPath: properties.fluidAdditionalAttributes.step
+                    propertyValidators:
+                      10: Integer
+                    templateName: Inspector-TextEditor
+                  900:
+                    selectOptions:
+                      60:
+                        label: formEditor.elements.Number.editor.validators.Number.label
+                        value: Number
+                group: html5
+                groupSorting: 400
+                iconIdentifier: t3-form-icon-number
+                label: formEditor.elements.Number.label
+                predefinedDefaults:
+                  properties:
+                    fluidAdditionalAttributes:
+                      step: 1
+                  validators:
+                    -
+                      identifier: Number
+                propertyCollections:
+                  validators:
+                    60:
+                      editors:
+                        __inheritances:
+                          10: TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin
+                        100:
+                          label: formEditor.elements.TextMixin.validators.Number.editor.header.label
+                      identifier: Number
+              validators:
+                -
+                  identifier: Number
+
+            ########### Page ###########
+            Page:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.BaseFormElementMixin
+              formEditor:
+                __inheritances:
+                  10: TYPO3.CMS.Form.mixins.formElementMixins.RemovableFormElementMixin
+                _isCompositeFormElement: true
+                _isTopLevelFormElement: true
+                editors:
+                  200:
+                    label: formEditor.elements.Page.editor.label.label
+                  300:
+                    identifier: previousButtonLabel
+                    label: formEditor.elements.Page.editor.previousButtonLabel.label
+                    propertyPath: renderingOptions.previousButtonLabel
+                    templateName: Inspector-TextEditor
+                  400:
+                    identifier: nextButtonLabel
+                    label: formEditor.elements.Page.editor.nextButtonLabel.label
+                    propertyPath: renderingOptions.nextButtonLabel
+                    templateName: Inspector-TextEditor
+                group: page
+                groupSorting: 100
+                iconIdentifier: t3-form-icon-page
+                label: formEditor.elements.Page.label
+                predefinedDefaults:
+                  renderingOptions:
+                    nextButtonLabel: formEditor.elements.Page.editor.nextButtonLabel.value
+                    previousButtonLabel: formEditor.elements.Page.editor.previousButtonLabel.value
+              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\Page
+              renderingOptions:
+                _isCompositeFormElement: true
+                _isTopLevelFormElement: true
+                nextButtonLabel: 'next Page'
+                previousButtonLabel: 'previous Page'
+
+            ########### Password ###########
+            Password:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
+              formEditor:
+                group: input
+                groupSorting: 300
+                iconIdentifier: t3-form-icon-password
+                label: formEditor.elements.Password.label
+
+            ########### RadioButton ###########
+            RadioButton:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.SingleSelectionMixin
+              formEditor:
+                group: select
+                groupSorting: 300
+                iconIdentifier: t3-form-icon-radio-button
+                label: formEditor.elements.RadioButton.label
+              properties:
+                elementClassAttribute: xlarge
+
+            ########### SingleSelect ###########
+            SingleSelect:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.SingleSelectionMixin
+              formEditor:
+                editors:
+                  250:
+                    doNotSetIfPropertyValueIsEmpty: true
+                    fieldExplanationText: formEditor.elements.SelectionMixin.editor.inactiveOption.fieldExplanationText
+                    identifier: inactiveOption
+                    label: formEditor.elements.SelectionMixin.editor.inactiveOption.label
+                    propertyPath: properties.prependOptionLabel
+                    templateName: Inspector-TextEditor
+                group: select
+                groupSorting: 200
+                iconIdentifier: t3-form-icon-single-select
+                label: formEditor.elements.SingleSelect.label
+
+            ########### StaticText ###########
+            StaticText:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.ReadOnlyFormElementMixin
+              formEditor:
+                editors:
+                  300:
+                    identifier: staticText
+                    label: formEditor.elements.StaticText.editor.staticText.label
+                    propertyPath: properties.text
+                    templateName: Inspector-TextareaEditor
+                group: custom
+                groupSorting: 600
+                iconIdentifier: t3-form-icon-static-text
+                label: formEditor.elements.StaticText.label
+                predefinedDefaults:
+                  properties:
+                    text: ''
+              properties:
+                text: ''
+
+            ########### SummaryPage ###########
+            SummaryPage:
+              __inheritances:
+                10: TYPO3.CMS.Form.prototypes.standard.formElementsDefinition.Page
+              formEditor:
+                _isCompositeFormElement: false
+                _isTopLevelFormElement: true
+                editors:
+                  200:
+                    label: formEditor.elements.SummaryPage.editor.label.label
+                  300:
+                    identifier: previousButtonLabel
+                    label: formEditor.elements.SummaryPage.editor.previousButtonLabel.label
+                    propertyPath: renderingOptions.previousButtonLabel
+                    templateName: Inspector-TextEditor
+                  400:
+                    identifier: nextButtonLabel
+                    label: formEditor.elements.SummaryPage.editor.nextButtonLabel.label
+                    propertyPath: renderingOptions.nextButtonLabel
+                    templateName: Inspector-TextEditor
+                group: page
+                groupSorting: 200
+                iconIdentifier: t3-form-icon-summary-page
+                label: formEditor.elements.SummaryPage.label
+                predefinedDefaults:
+                  renderingOptions:
+                    nextButtonLabel: formEditor.elements.SummaryPage.editor.nextButtonLabel.value
+                    previousButtonLabel: formEditor.elements.SummaryPage.editor.previousButtonLabel.value
+              renderingOptions:
+                _isCompositeFormElement: false
+                _isTopLevelFormElement: true
+                nextButtonLabel: 'next Page'
+                previousButtonLabel: 'previous Page'
+
+            ########### Telephone ###########
+            Telephone:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
+              formEditor:
+                editors:
+                  900:
+                    selectOptions: {  }
+                group: html5
+                groupSorting: 200
+                iconIdentifier: t3-form-icon-telephone
+                label: formEditor.elements.Telephone.label
+                propertyCollections:
+                  validators:
+                    80:
+                      editors: {  }
+              validators:
+                -
+                  identifier: RegularExpression
+                  options:
+                    regularExpression: '/^.*$/'
+
+            ########### Text ###########
+            Text:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
+              formEditor:
+                group: input
+                groupSorting: 100
+                iconIdentifier: t3-form-icon-text
+                label: formEditor.elements.Text.label
+
+            ########### Textarea ###########
+            Textarea:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
+              formEditor:
+                editors:
+                  900:
+                    selectOptions: {  }
+                group: input
+                groupSorting: 200
+                iconIdentifier: t3-form-icon-textarea
+                label: formEditor.elements.Textarea.label
+              properties:
+                elementClassAttribute: xxlarge
+
+            ########### Url ###########
+            Url:
+              __inheritances:
+                10: TYPO3.CMS.Form.mixins.formElementMixins.TextMixin
+              formEditor:
+                editors:
+                  900:
+                    selectOptions: {  }
+                group: html5
+                groupSorting: 300
+                iconIdentifier: t3-form-icon-url
+                label: formEditor.elements.Url.label
+                propertyCollections:
+                  validators:
+                    80:
+                      editors: {  }
+              validators:
+                -
+                  identifier: RegularExpression
+                  options:
+                    regularExpression: '/^.*$/'
+
+          ########### FORM ENGINE CONFIGURATION (backend) ###########
+          formEngine:
+            translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
+
+          ########### VALIDATOR DEFINITIONS (frontend / backend) ###########
+          validatorsDefinition:
+
+            ########### Alphanumeric ###########
+            Alphanumeric:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.Alphanumeric.label
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\AlphanumericValidator
+
+            ########### Count ###########
+            Count:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.MultiSelectionMixin.validators.Count.editor.header.label
+                predefinedDefaults:
+                  options:
+                    maximum: ''
+                    minimum: ''
+              implementationClassName: TYPO3\CMS\Form\Mvc\Validation\CountValidator
+
+            ########### DateTime ###########
+            DateTime:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.DatePicker.validators.DateTime.editor.header.label
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\DateTimeValidator
+
+            ########### EmailAddress ###########
+            EmailAddress:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.EmailAddress.label
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\EmailAddressValidator
+
+            ########### Float ###########
+            Float:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.Float.label
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\FloatValidator
+
+            ########### Integer ###########
+            Integer:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.Integer.label
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\IntegerValidator
+
+            ########### NotEmpty ###########
+            NotEmpty:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.FormElement.editor.requiredValidator.label
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
+
+            ########### Number ###########
+            Number:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.Number.label
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\NumberValidator
+
+            ########### NumberRange ###########
+            NumberRange:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.NumberRange.label
+                predefinedDefaults:
+                  options:
+                    maximum: ''
+                    minimum: ''
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\NumberRangeValidator
+
+            ########### RegularExpression ###########
+            RegularExpression:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.RegularExpression.label
+                predefinedDefaults:
+                  options:
+                    regularExpression: ''
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\RegularExpressionValidator
+
+            ########### StringLength ###########
+            StringLength:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.StringLength.label
+                predefinedDefaults:
+                  options:
+                    maximum: ''
+                    minimum: ''
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\StringLengthValidator
+
+            ########### Text ###########
+            Text:
+              formEditor:
+                iconIdentifier: t3-form-icon-validator
+                label: formEditor.elements.TextMixin.editor.validators.Text.label
+              implementationClassName: TYPO3\CMS\Extbase\Validation\Validator\TextValidator
diff --git a/typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/EmailSelectEditor.html b/typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/EmailSelectEditor.html
new file mode 100644 (file)
index 0000000..136ee03
--- /dev/null
@@ -0,0 +1,67 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers" data-namespace-typo3-fluid="true">
+<div class="form-editor">
+       <div class="t3-form-control-group form-group property-grid">
+               <label><span data-template-property="label" /></label>
+               <div data-editor="new-property-grid" data-template-property="newPropertyPath">
+                       <table class="table table-hover" data-identifier="propertyGridContainer">
+                               <thead>
+                                       <tr>
+                                               <th></th>
+                                               <th>Label</th>
+                                               <th>Value</th>
+                                               <th></th>
+                                       </tr>
+                               </thead>
+                               <tbody>
+                                       <tr data-identifier="rowItem">
+                                               <td><span class="sort-row-field" data-identifier="sortRow"><core:icon identifier="actions-move-move" /></span></td>
+                                               <td data-identifier="labelContainer">
+                                                       <input type="text" class="form-control" value="" data-identifier="label" />
+                                                       <span class="input-group-btn" role="group" data-identifier="inspectorEditorFormElementSelectorControlsWrapper">
+                                                               <span class="btn-group t3-form-dropdown-buttons" data-identifier="inspectorEditorFormElementSelectorSplitButtonContainer">
+                                                                       <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false" title="{f:translate(key: 'LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.inspector.editor.formelement_selector.title')}">
+                                                                               <core:icon identifier="t3-form-icon-form-element-selector" />
+                                                                               <span class="caret"></span>
+                                                                               <span class="sr-only">Toggle Dropdown</span>
+                                                                       </button>
+                                                                       <ul class="dropdown-menu dropdown-menu-right" data-identifier="inspectorEditorFormElementSelectorSplitButtonListContainer"></ul>
+                                                               </span>
+                                                       </span>
+                                               </td>
+                                               <td data-identifier="valueContainer">
+                                                       <input type="text" class="form-control" value="" data-identifier="value" />
+                                                       <span class="input-group-btn" role="group" data-identifier="inspectorEditorFormElementSelectorControlsWrapper">
+                                                               <span class="btn-group t3-form-dropdown-buttons" data-identifier="inspectorEditorFormElementSelectorSplitButtonContainer">
+                                                                       <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false" title="{f:translate(key: 'LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.inspector.editor.formelement_selector.title')}">
+                                                                               <core:icon identifier="t3-form-icon-form-element-selector" />
+                                                                               <span class="caret"></span>
+                                                                               <span class="sr-only">Toggle Dropdown</span>
+                                                                       </button>
+                                                                       <ul class="dropdown-menu dropdown-menu-right" data-identifier="inspectorEditorFormElementSelectorSplitButtonListContainer"></ul>
+                                                               </span>
+                                                       </span>
+                                               </td>
+                                               <td>
+                                                       <div class="btn-group btn-group-sm" role="group">
+                                                               <button class="btn btn-default" title="Remove this row" data-identifier="deleteRow"><core:icon identifier="actions-delete" /></button>
+                                                       </div>
+                                               </td>
+                                       </tr>
+                                       <tr data-identifier="addRowItem">
+                                               <td>
+                                                       <div class="btn-group btn-group-sm" role="group">
+                                                               <button class="btn btn-default" title="Add a new row" data-identifier="addRow"><core:icon identifier="actions-add" /></button>
+                                                       </div>
+                                               </td>
+                                               <td></td>
+                                               <td></td>
+                                               <td></td>
+                                               <td></td>
+                                       </tr>
+                               </tbody>
+                       </table>
+               </div>
+               <div data-editor="property-grid" data-template-property="propertyPath" class="t3-form-grid" />
+       </div>
+</div>
+</html>
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Configuration/ConfigurationManagerTest.php b/typo3/sysext/form/Tests/Unit/Mvc/Configuration/ConfigurationManagerTest.php
new file mode 100644 (file)
index 0000000..d901191
--- /dev/null
@@ -0,0 +1,214 @@
+<?php
+namespace TYPO3\CMS\Form\Tests\Unit\Mvc\Configuration;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Prophecy\Argument;
+use TYPO3\CMS\Core\Configuration\Loader\FalYamlFileLoader;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader\Configuration;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManager;
+use TYPO3\CMS\Form\Mvc\Configuration\Exception\ExtensionNameRequiredException;
+use TYPO3\CMS\Form\Mvc\Configuration\Exception\NoConfigurationFoundException;
+use TYPO3\CMS\Form\Mvc\Configuration\InheritancesResolverService;
+
+/**
+ * Test case
+ */
+class ConfigurationManagerTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
+{
+
+    /**
+     * @var array A backup of registered singleton instances
+     */
+    protected $singletonInstances = [];
+
+    /**
+     * Set up
+     */
+    public function setUp()
+    {
+        $this->singletonInstances = GeneralUtility::getSingletonInstances();
+    }
+
+    /**
+     * Tear down
+     */
+    public function tearDown()
+    {
+        GeneralUtility::resetSingletonInstances($this->singletonInstances);
+        parent::tearDown();
+    }
+
+    /**
+     * @test
+     */
+    public function getConfigurationFromYamlFileThrowsExceptionIfExtensionNameIsNotGiven()
+    {
+        $this->expectException(ExtensionNameRequiredException::class);
+        $this->expectExceptionCode(1471473377);
+
+        $mockConfigurationManager = $this->getAccessibleMock(ConfigurationManager::class, [
+            'dummy',
+        ], [], '', false);
+
+        $mockConfigurationManager->_call('getConfigurationFromYamlFile', '');
+    }
+
+    /**
+     * @test
+     */
+    public function getConfigurationFromYamlFileThrowsExceptionIfNoConfigurationIsFound()
+    {
+        $this->expectException(NoConfigurationFoundException::class);
+        $this->expectExceptionCode(1471473378);
+
+        $mockConfigurationManager = $this->getAccessibleMock(ConfigurationManager::class, [
+            'getYamlSettingsFromCache',
+            'getTypoScriptSettings',
+        ], [], '', false);
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('getYamlSettingsFromCache')
+            ->willReturn([]);
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('getTypoScriptSettings')
+            ->willReturn([]);
+
+        $objectMangerProphecy = $this->prophesize(ObjectManager::class);
+        $objectMangerProphecy
+            ->get(FalYamlFileLoader::class)
+            ->willReturn(new FalYamlFileLoader);
+        $mockConfigurationManager->_set('objectManager', $objectMangerProphecy->reveal());
+
+        $input = 'form';
+        $expected = [];
+
+        $this->assertSame($expected, $mockConfigurationManager->_call('getConfigurationFromYamlFile', 'form'));
+    }
+
+    /**
+     * @test
+     */
+    public function getConfigurationFromYamlFile()
+    {
+        $mockConfigurationManager = $this->getAccessibleMock(ConfigurationManager::class, [
+            'getYamlSettingsFromCache',
+            'setYamlSettingsIntoCache',
+            'getTypoScriptSettings',
+            'overrideConfigurationByTypoScript',
+        ], [], '', false);
+
+        $objectManagerProphecy = $this->prophesize(ObjectManager::class);
+        GeneralUtility::setSingletonInstance(ObjectManager::class, $objectManagerProphecy->reveal());
+
+        /** @var File|\Prophecy\Prophecy\ObjectProphecy */
+        $file1 = $this->prophesize(File::class);
+        $file1->getContents()->willReturn(file_get_contents(__DIR__ . '/Fixtures/File1.yaml'));
+        /** @var File|\Prophecy\Prophecy\ObjectProphecy */
+        $file2 = $this->prophesize(File::class);
+        $file2->getContents()->willReturn(file_get_contents(__DIR__ . '/Fixtures/File2.yaml'));
+        /** @var File|\Prophecy\Prophecy\ObjectProphecy */
+        $file3 = $this->prophesize(File::class);
+        $file3->getContents()->willReturn(file_get_contents(__DIR__ . '/Fixtures/File3.yaml'));
+        /** @var File|\Prophecy\Prophecy\ObjectProphecy */
+        $file4 = $this->prophesize(File::class);
+        $file4->getContents()->willReturn(file_get_contents(__DIR__ . '/Fixtures/File4.yaml'));
+
+        /** @var ResourceFactory|\Prophecy\Prophecy\ObjectProphecy */
+        $resourceFactory = $this->prophesize(ResourceFactory::class);
+        $resourceFactory->retrieveFileOrFolderObject('EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File1.yaml')->willReturn($file1->reveal());
+        $resourceFactory->retrieveFileOrFolderObject('EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File2.yaml')->willReturn($file2->reveal());
+        $resourceFactory->retrieveFileOrFolderObject('EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File3.yaml')->willReturn($file3->reveal());
+        $resourceFactory->retrieveFileOrFolderObject('EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File4.yaml')->willReturn($file4->reveal());
+
+        $configuration = new Configuration();
+        $objectManagerProphecy
+            ->get(Configuration::class)
+            ->willReturn($configuration);
+
+        $objectManagerProphecy
+            ->get(FalYamlFileLoader::class, Argument::type(Configuration::class))
+            ->willReturn(new FalYamlFileLoader($configuration, $resourceFactory->reveal()));
+
+        $objectManagerProphecy
+            ->get(InheritancesResolverService::class)
+            ->willReturn(new InheritancesResolverService);
+
+        $mockConfigurationManager->_set('objectManager', $objectManagerProphecy->reveal());
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('getYamlSettingsFromCache')
+            ->willReturn([]);
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('getTypoScriptSettings')
+            ->willReturn([
+                'configurationFile' => 'EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File1.yaml',
+            ]);
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('setYamlSettingsIntoCache')
+            ->willReturn(null);
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('overrideConfigurationByTypoScript')
+            ->willReturnArgument(0);
+
+        $input = 'form';
+        $expected = [
+            'config' => [
+                'value9' => 'File 3',
+                'value10' => 'File 4',
+                'value8' => 'File 3',
+                'value1' => 'File 1',
+                'value4' => 'File 1',
+                'value5' => 'File 1',
+                'value7' => 'File 2',
+                'value11' => [
+                    'key1' => 'File 1',
+                    'key2' => 'File 1',
+                ],
+                'value12' => [
+                    'key1' => 'File 2',
+                ],
+                'value3' => 'File 1',
+            ],
+            'mixins' => [
+                'value11Mixin' => [
+                    'key1' => 'File 1',
+                    'key2' => 'File 1',
+                ],
+                'value12Mixin1' => [
+                    'key1' => 'File 2',
+                ],
+                'value12Mixin2' => [
+                    'key2' => 'File 2',
+                ],
+            ],
+        ];
+
+        $this->assertSame($expected, $mockConfigurationManager->_call('getConfigurationFromYamlFile', 'form'));
+    }
+}
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File1.yaml b/typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File1.yaml
new file mode 100644 (file)
index 0000000..112c691
--- /dev/null
@@ -0,0 +1,21 @@
+imports:
+  - { resource: "EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File2.yaml" }
+  - { resource: "EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File3.yaml" }
+
+TYPO3:
+  CMS:
+    Form:
+      config:
+        value1: File 1
+        value2: null
+        value3: File 1
+        value4: File 1
+        value5: File 1
+        value6: null
+        value12:
+          __inheritances:
+            20: null
+      mixins:
+        value11Mixin:
+          key1: File 1
+          key2: File 1
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File2.yaml b/typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File2.yaml
new file mode 100644 (file)
index 0000000..4b5a563
--- /dev/null
@@ -0,0 +1,27 @@
+TYPO3:
+  CMS:
+    Form:
+      config:
+        value1: File2 (should be overridden by File1)
+        value2: File2 (should be removed by File1)
+        value4:
+          key1: File2 (the whole array should be overridden by File1)
+        value5: File2 (should be an array through File1)
+        value6:
+          key1: File2 (the whole array should be removed by File1)
+        value7: File 2
+        value11:
+          __inheritances:
+            10: 'TYPO3.CMS.Form.mixins.value11Mixin'
+        value12:
+          __inheritances:
+            10: 'TYPO3.CMS.Form.mixins.value12Mixin1'
+            20: 'TYPO3.CMS.Form.mixins.value12Mixin2'
+      mixins:
+        value11Mixin:
+          key1: File 2 (should be overridden by File1)
+          key2: File 2
+        value12Mixin1:
+          key1: File 2
+        value12Mixin2:
+          key2: File 2
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File3.yaml b/typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File3.yaml
new file mode 100644 (file)
index 0000000..7dc85f8
--- /dev/null
@@ -0,0 +1,9 @@
+imports:
+  - { resource: "EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File4.yaml" }
+
+TYPO3:
+  CMS:
+    Form:
+      config:
+        value8: File 3
+        value9: File 3
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File4.yaml b/typo3/sysext/form/Tests/Unit/Mvc/Configuration/Fixtures/File4.yaml
new file mode 100644 (file)
index 0000000..ace3064
--- /dev/null
@@ -0,0 +1,6 @@
+TYPO3:
+  CMS:
+    Form:
+      config:
+        value9: File 4 (should be overridden by File3)
+        value10: File 4
\ No newline at end of file
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Configuration/YamlSourceTest.php b/typo3/sysext/form/Tests/Unit/Mvc/Configuration/YamlSourceTest.php
deleted file mode 100644 (file)
index ccc114f..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-namespace TYPO3\CMS\Form\Tests\Unit\Mvc\Configuration;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Form\Mvc\Configuration\Exception\NoSuchFileException;
-use TYPO3\CMS\Form\Mvc\Configuration\Exception\ParseErrorException;
-use TYPO3\CMS\Form\Mvc\Configuration\YamlSource;
-
-/**
- * Test case
- */
-class YamlSourceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
-{
-
-    /**
-     * @test
-     */
-    public function loadThrowsExceptionIfFileToLoadNotExists()
-    {
-        $this->expectException(NoSuchFileException::class);
-        $this->expectExceptionCode(1471473378);
-
-        $mockYamlSource = $this->getAccessibleMock(YamlSource::class, [
-            'dummy',
-        ], [], '', false);
-
-        $input = [
-            'EXT:form/Resources/Forms/_example.yaml'
-        ];
-
-        $mockYamlSource->_call('load', $input);
-    }
-
-    /**
-     * @test
-     */
-    public function loadThrowsExceptionIfFileToLoadIsNotValidYamlUseSymfonyParser()
-    {
-        if (!extension_loaded('yaml')) {
-            $this->expectException(ParseErrorException::class);
-            $this->expectExceptionCode(1480195405);
-
-            $mockYamlSource = $this->getAccessibleMock(YamlSource::class, [
-                'dummy',
-            ], [], '', false);
-
-            $input = [
-                'EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/Invalid.yaml'
-            ];
-
-            $mockYamlSource->_call('load', $input);
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function loadThrowsExceptionIfFileToLoadIsNotValidYamlUsePhpExtensionParser()
-    {
-        if (extension_loaded('yaml')) {
-            $this->expectException(ParseErrorException::class);
-            $this->expectExceptionCode(1391894094);
-
-            $mockYamlSource = $this->getAccessibleMock(YamlSource::class, [
-                'dummy',
-            ], [], '', false);
-
-            $input = [
-                'EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/Invalid.yaml'
-            ];
-
-            $mockYamlSource->_call('load', $input);
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function getHeaderFromFileReturnsHeaderPart()
-    {
-        $mockYamlSource = $this->getAccessibleMock(YamlSource::class, [
-            'dummy',
-        ], [], '', false);
-
-        $input = GeneralUtility::getFileAbsFileName('EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/Header.yaml');
-        $expected =
-'# Header 1
-# Header 2
-';
-
-        $this->assertSame($expected, $mockYamlSource->_call('getHeaderFromFile', $input));
-    }
-}
diff --git a/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/ConfigurationManagerTest.php b/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/ConfigurationManagerTest.php
new file mode 100644 (file)
index 0000000..e4307b8
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+namespace TYPO3\CMS\Form\Tests\Unit\Mvc\Configuration;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Configuration\Loader\FalYamlFileLoader;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManager;
+use TYPO3\CMS\Form\Mvc\Configuration\InheritancesResolverService;
+
+/**
+ * Test case
+ */
+class ConfigurationManagerTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
+{
+
+    /**
+     * @var array A backup of registered singleton instances
+     */
+    protected $singletonInstances = [];
+
+    /**
+     * Set up
+     */
+    public function setUp()
+    {
+        $this->singletonInstances = GeneralUtility::getSingletonInstances();
+    }
+
+    /**
+     * Tear down
+     */
+    public function tearDown()
+    {
+        GeneralUtility::resetSingletonInstances($this->singletonInstances);
+        parent::tearDown();
+    }
+
+    /**
+     * @test
+     */
+    public function getConfigurationFromYamlFile()
+    {
+        $mockConfigurationManager = $this->getAccessibleMock(ConfigurationManager::class, [
+            'getYamlSettingsFromCache',
+            'setYamlSettingsIntoCache',
+            'getTypoScriptSettings',
+            'overrideConfigurationByTypoScript',
+        ], [], '', false);
+
+        $objectMangerProphecy = $this->prophesize(ObjectManager::class);
+        GeneralUtility::setSingletonInstance(ObjectManager::class, $objectMangerProphecy->reveal());
+
+        $objectMangerProphecy
+            ->get(FalYamlFileLoader::class)
+            ->willReturn(new FalYamlFileLoader);
+
+        $objectMangerProphecy
+            ->get(InheritancesResolverService::class)
+            ->willReturn(new InheritancesResolverService);
+
+        $mockConfigurationManager->_set('objectManager', $objectMangerProphecy->reveal());
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('getYamlSettingsFromCache')
+            ->willReturn([]);
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('getTypoScriptSettings')
+            ->willReturn([
+                'yamlConfigurations' => [
+                    10 => 'EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/Header.yaml',
+                    20 => 'EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File1.yaml'
+                ]
+            ]);
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('setYamlSettingsIntoCache')
+            ->willReturn(null);
+
+        $mockConfigurationManager
+            ->expects($this->any())
+            ->method('overrideConfigurationByTypoScript')
+            ->willReturnArgument(0);
+
+        $input = 'form';
+        $expected = [
+            'config' => [
+                'value9' => 'File 3',
+                'value10' => 'File 4',
+                'value8' => 'File 3',
+                'value1' => 'File 1',
+                'value4' => 'File 1',
+                'value5' => 'File 1',
+                'value7' => 'File 2',
+                'value11' => [
+                    'key1' => 'File 1',
+                    'key2' => 'File 1',
+                ],
+                'value12' => [
+                    'key1' => 'File 2',
+                ],
+                'value3' => 'File 1',
+            ],
+            'mixins' => [
+                'value11Mixin' => [
+                    'key1' => 'File 1',
+                    'key2' => 'File 1',
+                ],
+                'value12Mixin1' => [
+                    'key1' => 'File 2',
+                ],
+                'value12Mixin2' => [
+                    'key2' => 'File 2',
+                ],
+            ],
+        ];
+
+        $this->assertSame($expected, $mockConfigurationManager->_call('getConfigurationFromYamlFile', 'form'));
+    }
+}
diff --git a/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File1.yaml b/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File1.yaml
new file mode 100644 (file)
index 0000000..112c691
--- /dev/null
@@ -0,0 +1,21 @@
+imports:
+  - { resource: "EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File2.yaml" }
+  - { resource: "EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File3.yaml" }
+
+TYPO3:
+  CMS:
+    Form:
+      config:
+        value1: File 1
+        value2: null
+        value3: File 1
+        value4: File 1
+        value5: File 1
+        value6: null
+        value12:
+          __inheritances:
+            20: null
+      mixins:
+        value11Mixin:
+          key1: File 1
+          key2: File 1
diff --git a/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File2.yaml b/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File2.yaml
new file mode 100644 (file)
index 0000000..4b5a563
--- /dev/null
@@ -0,0 +1,27 @@
+TYPO3:
+  CMS:
+    Form:
+      config:
+        value1: File2 (should be overridden by File1)
+        value2: File2 (should be removed by File1)
+        value4:
+          key1: File2 (the whole array should be overridden by File1)
+        value5: File2 (should be an array through File1)
+        value6:
+          key1: File2 (the whole array should be removed by File1)
+        value7: File 2
+        value11:
+          __inheritances:
+            10: 'TYPO3.CMS.Form.mixins.value11Mixin'
+        value12:
+          __inheritances:
+            10: 'TYPO3.CMS.Form.mixins.value12Mixin1'
+            20: 'TYPO3.CMS.Form.mixins.value12Mixin2'
+      mixins:
+        value11Mixin:
+          key1: File 2 (should be overridden by File1)
+          key2: File 2
+        value12Mixin1:
+          key1: File 2
+        value12Mixin2:
+          key2: File 2
diff --git a/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File3.yaml b/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File3.yaml
new file mode 100644 (file)
index 0000000..7dc85f8
--- /dev/null
@@ -0,0 +1,9 @@
+imports:
+  - { resource: "EXT:form/Tests/Unit/Mvc/Configuration/Fixtures/File4.yaml" }
+
+TYPO3:
+  CMS:
+    Form:
+      config:
+        value8: File 3
+        value9: File 3
diff --git a/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File4.yaml b/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/File4.yaml
new file mode 100644 (file)
index 0000000..ace3064
--- /dev/null
@@ -0,0 +1,6 @@
+TYPO3:
+  CMS:
+    Form:
+      config:
+        value9: File 4 (should be overridden by File3)
+        value10: File 4
\ No newline at end of file
diff --git a/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/Header.yaml b/typo3/sysext/form/Tests/Unit_Deprecated/Mvc/Configuration/Fixtures/Header.yaml
new file mode 100644 (file)
index 0000000..ab19ab6
--- /dev/null
@@ -0,0 +1,6 @@
+# Header 1
+# Header 2
+
+yaml
+
+# Comment
\ No newline at end of file
index ee892ad..4672c3a 100644 (file)
@@ -62,9 +62,20 @@ call_user_func(function () {
         '<INCLUDE_TYPOSCRIPT: source="FILE:EXT:form/Configuration/PageTS/modWizards.ts">'
     );
 
-    // Add new content element wizard entry
-    \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig(
-        '<INCLUDE_TYPOSCRIPT: source="FILE:EXT:form/Configuration/PageTS/modWizards.ts">'
+    // Add module configuration
+    \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(
+        trim('
+            module.tx_form {
+                view {
+                    templateRootPaths.10 = EXT:form/Resources/Private/Backend/Templates/
+                    partialRootPaths.10 = EXT:form/Resources/Private/Backend/Partials/
+                    layoutRootPaths.10 = EXT:form/Resources/Private/Backend/Layouts/
+                }
+                settings {
+                    configurationFile = EXT:form/Configuration/Yaml/FormSetup.yaml
+                }
+            }
+        ')
     );
 
     $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterSubmit'][1489772699]
diff --git a/typo3/sysext/form/ext_typoscript_setup.txt b/typo3/sysext/form/ext_typoscript_setup.txt
deleted file mode 100644 (file)
index ec0c566..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-module.tx_form {
-    settings {
-        yamlConfigurations {
-            10 = EXT:form/Configuration/Yaml/BaseSetup.yaml
-            20 = EXT:form/Configuration/Yaml/FormEditorSetup.yaml
-            30 = EXT:form/Configuration/Yaml/FormEngineSetup.yaml
-        }
-    }
-    view {
-        templateRootPaths.10 = EXT:form/Resources/Private/Backend/Templates/
-        partialRootPaths.10 = EXT:form/Resources/Private/Backend/Partials/
-        layoutRootPaths.10 = EXT:form/Resources/Private/Backend/Layouts/
-    }
-}