[FEATURE] Allow multiple recipients in email finisher 44/60544/37
authorMathias Brodala <mbrodala@pagemachine.de>
Thu, 25 Apr 2019 13:36:31 +0000 (15:36 +0200)
committerRalf Zimmermann <ralf.zimmermann@tritum.de>
Sun, 28 Apr 2019 14:18:41 +0000 (16:18 +0200)
Also deprecate the single recipients options
in favor of their list successors.

Resolves: #80420
Releases: master
Change-Id: I5bad6da8809bd362110025296564e3eff0df70a4
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60544
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog <look@susi.dev>
Tested-by: Ralf Zimmermann <ralf.zimmermann@tritum.de>
Tested-by: Björn Jacob <bjoern.jacob@tritum.de>
Reviewed-by: Susanne Moog <look@susi.dev>
Reviewed-by: Ralf Zimmermann <ralf.zimmermann@tritum.de>
Reviewed-by: Björn Jacob <bjoern.jacob@tritum.de>
21 files changed:
typo3/sysext/core/Documentation/Changelog/master/Deprecation-80420-EmailFinisherSingleAddressOptions.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-80420-AllowMultipleRecipientsInEmailFinisher.rst [new file with mode: 0644]
typo3/sysext/form/Classes/Controller/FormEditorController.php
typo3/sysext/form/Classes/Controller/FormFrontendController.php
typo3/sysext/form/Classes/Domain/Configuration/FlexformConfiguration/Processors/FinisherOptionGenerator.php
typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/FinisherOptionsFlexFormOverridesConverter.php
typo3/sysext/form/Classes/Domain/Configuration/FormDefinition/Converters/FlexFormFinisherOverridesConverterDto.php
typo3/sysext/form/Classes/Domain/Finishers/EmailFinisher.php
typo3/sysext/form/Classes/Hooks/DataStructureIdentifierHook.php
typo3/sysext/form/Configuration/Yaml/BaseSetup.yaml
typo3/sysext/form/Configuration/Yaml/FormEditorSetup.yaml
typo3/sysext/form/Configuration/Yaml/FormEngineSetup.yaml
typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/PropertyGridEditor.html
typo3/sysext/form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/SimpleContactForm.yaml
typo3/sysext/form/Resources/Private/Language/Database.xlf
typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js
typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/InspectorComponent.js
typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/ViewModel.js
typo3/sysext/form/Tests/Unit/Controller/FormEditorControllerTest.php
typo3/sysext/form/Tests/Unit/Controller/FormFrontendControllerTest.php
typo3/sysext/form/Tests/Unit/Hooks/DataStructureIdentifierHookTest.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-80420-EmailFinisherSingleAddressOptions.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-80420-EmailFinisherSingleAddressOptions.rst
new file mode 100644 (file)
index 0000000..0217489
--- /dev/null
@@ -0,0 +1,151 @@
+.. include:: ../../Includes.txt
+
+==========================================================
+Deprecation: #80420 - EmailFinisher single address options
+==========================================================
+
+See :issue:`80420`
+
+Description
+===========
+
+The :php:`EmailFinisher` of EXT:form has options to set multiple recipients for :code:`To`, :code:`CC` and :code:`BCC`. For this reason, the following options have been deprecated and will be removed in TYPO3 11.0:
+
+* :yaml:`recipientAddress`
+* :yaml:`recipientName`
+* :yaml:`replyToAddress`
+* :yaml:`carbonCopyAddress`
+* :yaml:`blindCarbonCopyAddress`
+
+If any of these options are used, their values will be automatically migrated to their replacements.
+
+Opening and saving a form with the form editor once also performs this migration and makes it permanent.
+
+
+Impact
+======
+
+Any of these options will no longer work in TYPO3 11.0.
+
+
+Affected Installations
+======================
+
+All installations which use EXT:form and its :php:`EmailFinisher`.
+
+
+Migration
+=========
+
+All single value options must be migrated to their list value successors.
+
+
+Multiple Recipients
+-------------------
+
+Change :yaml:`recipientAddress` and :yaml:`recipientName` to :yaml:`recipients`.
+
+Before:
+
+.. code-block:: yaml
+
+   finishers:
+     -
+       identifier: EmailToReceiver
+       options:
+         recipientAddress: to@example.org
+         recipientName: 'To Example'
+
+After:
+
+.. code-block:: yaml
+
+   finishers:
+     -
+       identifier: EmailToReceiver
+       options:
+         recipients:
+           to@example.org: 'To Example'
+
+
+Multiple Reply-To Recipients
+----------------------------
+
+Change :yaml:`replyToAddress` to :yaml:`replyToRecipients`. Additionally this allows for setting the name of a Reply-To recipient.
+
+Before:
+
+.. code-block:: yaml
+
+   finishers:
+     -
+       identifier: EmailToReceiver
+       options:
+         replyToAddress: rt@example.org
+
+After:
+
+.. code-block:: yaml
+
+   finishers:
+     -
+       identifier: EmailToReceiver
+       options:
+         replyToRecipients:
+           rt@example.org@example.org: 'Reply-To Example'
+
+
+Multiple Carbon Copy (CC) Recipients
+------------------------------------
+
+Change :yaml:`carbonCopyAddress` to :yaml:`carbonCopyRecipients`. Additionally this allows for setting the name of a CC recipient.
+
+Before:
+
+.. code-block:: yaml
+
+   finishers:
+     -
+       identifier: EmailToReceiver
+       options:
+         carbonCopyAddress: cc@example.org
+
+After:
+
+.. code-block:: yaml
+
+   finishers:
+     -
+       identifier: EmailToReceiver
+       options:
+         carbonCopyRecipients:
+           cc@example.org: 'CC Example'
+
+
+Multiple Blind Carbon Copy (BCC) Recipients
+-------------------------------------------
+
+Change :yaml:`blindCarbonCopyAddress` to :yaml:`blindCarbonCopyRecipients`. Additionally this allows for setting the name of a BCC recipient.
+
+Before:
+
+.. code-block:: yaml
+
+   finishers:
+     -
+       identifier: EmailToReceiver
+       options:
+         blindCarbonCopyAddress: bcc@example.org
+
+After:
+
+.. code-block:: yaml
+
+   finishers:
+     -
+       identifier: EmailToReceiver
+       options:
+         blindCarbonCopyRecipients:
+           bcc@example.org: 'BCC Example'
+
+.. index:: YAML, NotScanned, ext:form
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-80420-AllowMultipleRecipientsInEmailFinisher.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-80420-AllowMultipleRecipientsInEmailFinisher.rst
new file mode 100644 (file)
index 0000000..712afe9
--- /dev/null
@@ -0,0 +1,42 @@
+.. include:: ../../Includes.txt
+
+=============================================================
+Feature: #80420 - Allow multiple recipients in email finisher
+=============================================================
+
+See :issue:`80420`
+
+Description
+===========
+
+Mails sent by the :php:`EmailFinisher` of EXT:form can now have multiple recipients. For this the following new finisher options have been added:
+
+* :yaml:`recipients` (:code:`To`)
+* :yaml:`replyToRecipients` (:code:`Reply-To`)
+* :yaml:`carbonCopyRecipients` (:code:`CC`)
+* :yaml:`blindCarbonCopyRecipients` (:code:`BCC`)
+
+These options must contain a YAML hash with email addresses as keys and recipient names as values:
+
+.. code-block:: yaml
+
+   recipients:
+     first@example.org: First Recipient
+     second@example.org: Second Recipient
+
+Additionally this now allows for setting the name of a CC and BCC recipient:
+
+.. code-block:: yaml
+
+   carbonCopyRecipients:
+     firstCC@example.org: First CC Recipient
+
+The form editor in the backend module provides a visual UI to enter an arbitrary amount of recipients.
+
+
+Impact
+======
+
+Mails sent by EXT:form can be sent to multiple recipients, optionally via CC or BCC. Replies can be sent to multiple recipients.
+
+.. index:: ext:form
index b2f7114..8ad358e 100644 (file)
@@ -483,24 +483,48 @@ class FormEditorController extends AbstractBackendController
      */
     protected function transformFormDefinitionForFormEditor(array $formDefinition): array
     {
-        $multiValueProperties = [];
+        $multiValueFormElementProperties = [];
+        $multiValueFinisherProperties = [];
+
         foreach ($this->prototypeConfiguration['formElementsDefinition'] as $type => $configuration) {
             if (!isset($configuration['formEditor']['editors'])) {
                 continue;
             }
             foreach ($configuration['formEditor']['editors'] as $editorConfiguration) {
                 if ($editorConfiguration['templateName'] === 'Inspector-PropertyGridEditor') {
-                    $multiValueProperties[$type][] = $editorConfiguration['propertyPath'];
+                    $multiValueFormElementProperties[$type][] = $editorConfiguration['propertyPath'];
+                }
+            }
+        }
+
+        foreach ($this->prototypeConfiguration['formElementsDefinition']['Form']['formEditor']['propertyCollections']['finishers'] ?? [] as $configuration) {
+            if (!isset($configuration['editors'])) {
+                continue;
+            }
+
+            foreach ($configuration['editors'] as $editorConfiguration) {
+                if ($editorConfiguration['templateName'] === 'Inspector-PropertyGridEditor') {
+                    $multiValueFinisherProperties[$configuration['identifier']][] = $editorConfiguration['propertyPath'];
                 }
             }
         }
 
         $formDefinition = $this->filterEmptyArrays($formDefinition);
         $formDefinition = $this->migrateTranslationFileOptions($formDefinition);
+        $formDefinition = $this->migrateEmailFinisherRecipients($formDefinition);
 
         // @todo: replace with rte parsing
         $formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition);
-        $formDefinition = $this->transformMultiValueElementsForFormEditor($formDefinition, $multiValueProperties);
+        $formDefinition = $this->transformMultiValuePropertiesForFormEditor(
+            $formDefinition,
+            'type',
+            $multiValueFormElementProperties
+        );
+        $formDefinition = $this->transformMultiValuePropertiesForFormEditor(
+            $formDefinition,
+            'identifier',
+            $multiValueFinisherProperties
+        );
 
         $formDefinitionConversionService = $this->getFormDefinitionConversionService();
         $formDefinition = $formDefinitionConversionService->addHmacData($formDefinition);
@@ -539,39 +563,54 @@ class FormEditorController extends AbstractBackendController
      * ]
      *
      * @param array $formDefinition
+     * @param string $identifierProperty
      * @param array $multiValueProperties
      * @return array
      */
-    protected function transformMultiValueElementsForFormEditor(
+    protected function transformMultiValuePropertiesForFormEditor(
         array $formDefinition,
+        string $identifierProperty,
         array $multiValueProperties
     ): array {
         $output = $formDefinition;
         foreach ($formDefinition as $key => $value) {
-            if (isset($value['type']) && array_key_exists($value['type'], $multiValueProperties)) {
-                $multiValuePropertiesForType = $multiValueProperties[$value['type']];
-                foreach ($multiValuePropertiesForType as $multiValueProperty) {
+            $identifier = $value[$identifierProperty] ?? null;
+
+            if (array_key_exists($identifier, $multiValueProperties)) {
+                $multiValuePropertiesForIdentifier = $multiValueProperties[$identifier];
+
+                foreach ($multiValuePropertiesForIdentifier as $multiValueProperty) {
                     if (!ArrayUtility::isValidPath($value, $multiValueProperty, '.')) {
                         continue;
                     }
+
                     $multiValuePropertyData = ArrayUtility::getValueByPath($value, $multiValueProperty, '.');
+
                     if (!is_array($multiValuePropertyData)) {
                         continue;
                     }
+
                     $newMultiValuePropertyData = [];
+
                     foreach ($multiValuePropertyData as $k => $v) {
                         $newMultiValuePropertyData[] = [
                             '_label' => $v,
-                            '_value' => $k
+                            '_value' => $k,
                         ];
                     }
+
                     $value = ArrayUtility::setValueByPath($value, $multiValueProperty, $newMultiValuePropertyData, '.');
                 }
             }
 
             $output[$key] = $value;
+
             if (is_array($value)) {
-                $output[$key] = $this->transformMultiValueElementsForFormEditor($value, $multiValueProperties);
+                $output[$key] = $this->transformMultiValuePropertiesForFormEditor(
+                    $value,
+                    $identifierProperty,
+                    $multiValueProperties
+                );
             }
         }
 
@@ -649,6 +688,48 @@ class FormEditorController extends AbstractBackendController
     }
 
     /**
+     * Migrate single recipient options to their list successors
+     *
+     * @param array $formDefinition
+     * @return array
+     */
+    protected function migrateEmailFinisherRecipients(array $formDefinition): array
+    {
+        foreach ($formDefinition['finishers'] ?? [] as $i => $finisherConfiguration) {
+            if (!in_array($finisherConfiguration['identifier'], ['EmailToSender', 'EmailToReceiver'], true)) {
+                continue;
+            }
+
+            $recipientAddress = $finisherConfiguration['options']['recipientAddress'] ?? '';
+            $recipientName = $finisherConfiguration['options']['recipientName'] ?? '';
+            $carbonCopyAddress = $finisherConfiguration['options']['carbonCopyAddress'] ?? '';
+            $blindCarbonCopyAddress = $finisherConfiguration['options']['blindCarbonCopyAddress'] ?? '';
+
+            if (!empty($recipientAddress)) {
+                $finisherConfiguration['options']['recipients'][$recipientAddress] = $recipientName;
+            }
+
+            if (!empty($carbonCopyAddress)) {
+                $finisherConfiguration['options']['carbonCopyRecipients'][$carbonCopyAddress] = '';
+            }
+
+            if (!empty($blindCarbonCopyAddress)) {
+                $finisherConfiguration['options']['blindCarbonCopyRecipients'][$blindCarbonCopyAddress] = '';
+            }
+
+            unset(
+                $finisherConfiguration['options']['recipientAddress'],
+                $finisherConfiguration['options']['recipientName'],
+                $finisherConfiguration['options']['carbonCopyAddress'],
+                $finisherConfiguration['options']['blindCarbonCopyAddress']
+            );
+            $formDefinition['finishers'][$i] = $finisherConfiguration;
+        }
+
+        return $formDefinition;
+    }
+
+    /**
      * @return FormDefinitionConversionService
      */
     protected function getFormDefinitionConversionService(): FormDefinitionConversionService
index 044b9d4..d356666 100644 (file)
@@ -114,6 +114,7 @@ class FormFrontendController extends ActionController
                     $prototypeFinisherDefinition = $prototypeConfiguration['finishersDefinition'][$finisherIdentifier] ?? [];
                     $converterDto = GeneralUtility::makeInstance(
                         FlexFormFinisherOverridesConverterDto::class,
+                        $prototypeFinisherDefinition,
                         $formFinisherDefinition,
                         $finisherIdentifier,
                         $flexFormSheetSettings
@@ -124,7 +125,7 @@ class FormFrontendController extends ActionController
                         GeneralUtility::makeInstance(
                             ArrayProcessing::class,
                             'modifyFinisherOptionsFromFlexFormOverrides',
-                            '^(.*)\.config\.type$',
+                            '^(.*)(?:(?<!\.TCEforms)\.config\.type|\.section)$',
                             GeneralUtility::makeInstance(FinisherOptionsFlexFormOverridesConverter::class, $converterDto)
                         )
                     );
index eb7aeeb..f733489 100644 (file)
@@ -93,7 +93,9 @@ class FinisherOptionGenerator extends AbstractProcessor
             $elementConfiguration['label'] .= sprintf(' (%s: "' . $optionValue . '")', $this->languageService->getLL('default'));
         }
 
-        $elementConfiguration['config']['default'] = $optionValue;
+        if (isset($elementConfiguration['config'])) {
+            $elementConfiguration['config']['default'] = $optionValue;
+        }
 
         $sheetElements = $this->converterDto->getResult();
         $sheetElements['settings.finishers.' . $finisherIdentifier . '.' . $optionKey]['TCEforms'] = $elementConfiguration;
index 94077cd..4069979 100644 (file)
@@ -53,6 +53,7 @@ class FinisherOptionsFlexFormOverridesConverter
     public function __invoke(string $_, $__, array $matches): void
     {
         [, $optionKey] = $matches;
+        $prototypeFinisherDefinition = $this->converterDto->getPrototypeFinisherDefinition();
         $finisherDefinition = $this->converterDto->getFinisherDefinition();
         $finisherIdentifier = $this->converterDto->getFinisherIdentifier();
         $flexFormSheetSettings = $this->converterDto->getFlexFormSheetSettings();
@@ -67,6 +68,24 @@ class FinisherOptionsFlexFormOverridesConverter
             return;
         }
 
+        $fieldConfiguration = $prototypeFinisherDefinition['FormEngine']['elements'][$optionKey];
+
+        if ($fieldConfiguration['section'] ?? false) {
+            $processedOptionValue = [];
+
+            foreach ($value ?: [] as $optionListValue) {
+                $key = $optionListValue[$fieldConfiguration['sectionItemKey']];
+                $value = $optionListValue[$fieldConfiguration['sectionItemValue']];
+                $processedOptionValue[$key] = $value;
+            }
+
+            if (empty($processedOptionValue)) {
+                $value = $optionValue;
+            } else {
+                $value = $processedOptionValue;
+            }
+        }
+
         $finisherDefinition = ArrayUtility::setValueByPath($finisherDefinition, 'options.' . $optionKey, $value, '.');
 
         $this->converterDto->setFinisherDefinition($finisherDefinition);
index e719e66..e3a28b9 100644 (file)
@@ -23,6 +23,11 @@ class FlexFormFinisherOverridesConverterDto
     /**
      * @var array
      */
+    protected $prototypeFinisherDefinition = [];
+
+    /**
+     * @var array
+     */
     protected $finisherDefinition = [];
 
     /**
@@ -36,15 +41,18 @@ class FlexFormFinisherOverridesConverterDto
     protected $flexFormSheetSettings = [];
 
     /**
+     * @param array $prototypeFinisherDefinition
      * @param array $finisherDefinition
      * @param string $finisherIdentifier
      * @param array $flexFormSheetSettings
      */
     public function __construct(
+        array $prototypeFinisherDefinition,
         array $finisherDefinition,
         string $finisherIdentifier,
         array $flexFormSheetSettings
     ) {
+        $this->prototypeFinisherDefinition = $prototypeFinisherDefinition;
         $this->finisherDefinition = $finisherDefinition;
         $this->finisherIdentifier = $finisherIdentifier;
         $this->flexFormSheetSettings = $flexFormSheetSettings;
@@ -53,6 +61,14 @@ class FlexFormFinisherOverridesConverterDto
     /**
      * @return array
      */
+    public function getPrototypeFinisherDefinition(): array
+    {
+        return $this->prototypeFinisherDefinition;
+    }
+
+    /**
+     * @return array
+     */
     public function getFinisherDefinition(): array
     {
         return $this->finisherDefinition;
index 979bfbe..212a5bc 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Form\Domain\Finishers;
  */
 
 use TYPO3\CMS\Core\Mail\MailMessage;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Extbase\Domain\Model\FileReference;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
@@ -35,17 +36,16 @@ use TYPO3\CMS\Form\ViewHelpers\RenderRenderableViewHelper;
  * - variables: associative array of variables which are available inside the Fluid template
  *
  * The following options control the mail sending. In all of them, placeholders in the form
- * of {...} are replaced with the corresponding form value; i.e. {email} as recipientAddress
+ * of {...} are replaced with the corresponding form value; i.e. {email} as senderAddress
  * makes the recipient address configurable.
  *
  * - subject (mandatory): Subject of the email
- * - recipientAddress (mandatory): Email address of the recipient
- * - recipientName: Human-readable name of the recipient
+ * - recipients (mandatory): Email addresses and human-readable names of the recipients
  * - senderAddress (mandatory): Email address of the sender
  * - senderName: Human-readable name of the sender
- * - replyToAddress: Email address of to be used as reply-to email (use multiple addresses with an array)
- * - carbonCopyAddress: Email address of the copy recipient (use multiple addresses with an array)
- * - blindCarbonCopyAddress: Email address of the blind copy recipient (use multiple addresses with an array)
+ * - replyToRecipients: Email addresses and human-readable names of the reply-to recipients
+ * - carbonCopyRecipients: Email addresses and human-redable names of the copy recipients
+ * - blindCarbonCopyRecipients: Email addresses and human-readable names of the blind copy recipients
  * - format: format of the email (one of the FORMAT_* constants). By default mails are sent as HTML
  *
  * Scope: frontend
@@ -87,21 +87,20 @@ class EmailFinisher extends AbstractFinisher
         }
 
         $subject = $this->parseOption('subject');
-        $recipientAddress = $this->parseOption('recipientAddress');
-        $recipientName = $this->parseOption('recipientName');
+        $recipients = $this->getRecipients('recipients', 'recipientAddress', 'recipientName');
         $senderAddress = $this->parseOption('senderAddress');
         $senderName = $this->parseOption('senderName');
-        $replyToAddress = $this->parseOption('replyToAddress');
-        $carbonCopyAddress = $this->parseOption('carbonCopyAddress');
-        $blindCarbonCopyAddress = $this->parseOption('blindCarbonCopyAddress');
+        $replyToRecipients = $this->getRecipients('replyToRecipients', 'replyToAddress');
+        $carbonCopyRecipients = $this->getRecipients('carbonCopyRecipients', 'carbonCopyAddress');
+        $blindCarbonCopyRecipients = $this->getRecipients('blindCarbonCopyRecipients', 'blindCarbonCopyAddress');
         $format = $this->parseOption('format');
         $attachUploads = $this->parseOption('attachUploads');
 
         if (empty($subject)) {
             throw new FinisherException('The option "subject" must be set for the EmailFinisher.', 1327060320);
         }
-        if (empty($recipientAddress)) {
-            throw new FinisherException('The option "recipientAddress" must be set for the EmailFinisher.', 1327060200);
+        if (empty($recipients)) {
+            throw new FinisherException('The option "recipients" must be set for the EmailFinisher.', 1327060200);
         }
         if (empty($senderAddress)) {
             throw new FinisherException('The option "senderAddress" must be set for the EmailFinisher.', 1327060210);
@@ -110,19 +109,19 @@ class EmailFinisher extends AbstractFinisher
         $mail = $this->objectManager->get(MailMessage::class);
 
         $mail->setFrom([$senderAddress => $senderName])
-            ->setTo([$recipientAddress => $recipientName])
+            ->setTo($recipients)
             ->setSubject($subject);
 
-        if (!empty($replyToAddress)) {
-            $mail->setReplyTo($replyToAddress);
+        if (!empty($replyToRecipients)) {
+            $mail->setReplyTo($replyToRecipients);
         }
 
-        if (!empty($carbonCopyAddress)) {
-            $mail->setCc($carbonCopyAddress);
+        if (!empty($carbonCopyRecipients)) {
+            $mail->setCc($carbonCopyRecipients);
         }
 
-        if (!empty($blindCarbonCopyAddress)) {
-            $mail->setBcc($blindCarbonCopyAddress);
+        if (!empty($blindCarbonCopyRecipients)) {
+            $mail->setBcc($blindCarbonCopyRecipients);
         }
 
         if ($format === self::FORMAT_PLAINTEXT) {
@@ -202,4 +201,56 @@ class EmailFinisher extends AbstractFinisher
 
         return $standaloneView;
     }
+
+    /**
+     * Get mail recipients
+     *
+     * @param string $listOption List option name
+     * @param string $singleAddressOption Single address option
+     * @param string $singleAddressName Single address name
+     * @return array
+     *
+     * @deprecated since TYPO3 v10.0, will be removed in TYPO3 v11.0.
+     */
+    protected function getRecipients(
+        string $listOption,
+        string $singleAddressOption,
+        string $singleAddressNameOption = null
+    ): array {
+        $recipients = $this->parseOption($listOption);
+        $singleAddress = $this->parseOption($singleAddressOption);
+        $singleAddressName = '';
+
+        $recipients = $recipients ?? [];
+
+        if (!empty($singleAddress)) {
+            trigger_error(sprintf(
+                'EmailFinisher option "%s" is deprecated and will be removed in TYPO3 v11.0. Use "%s" instead.',
+                $singleAddressOption,
+                $listOption
+            ), E_USER_DEPRECATED);
+
+            if (!empty($singleAddressNameOption)) {
+                trigger_error(sprintf(
+                    'EmailFinisher option "%s" is deprecated and will be removed in TYPO3 v11.0. Use "%s" instead.',
+                    $singleAddressNameOption,
+                    $listOption
+                ), E_USER_DEPRECATED);
+                $singleAddressName = $this->parseOption($singleAddressNameOption);
+            }
+
+            $recipients[$singleAddress] = $singleAddressName ?: '';
+        }
+
+        // Drop entries without mail address
+        $recipients = array_filter($recipients, function ($value, $key) {
+            if (MathUtility::canBeInterpretedAsInteger($key)) {
+                return !empty($value);
+            }
+
+            return !empty($key);
+        }, ARRAY_FILTER_USE_BOTH);
+
+        return $recipients;
+    }
 }
index 079bc46..ccb7a68 100644 (file)
@@ -81,12 +81,12 @@ class DataStructureIdentifierHook
             }
 
             // Add bool - finisher override active or not
-            $identifier['ext-form-overrideFinishers'] = false;
+            $identifier['ext-form-overrideFinishers'] = '';
             if (
                 isset($currentFlexData['data']['sDEF']['lDEF']['settings.overrideFinishers']['vDEF'])
                 && (int)$currentFlexData['data']['sDEF']['lDEF']['settings.overrideFinishers']['vDEF'] === 1
             ) {
-                $identifier['ext-form-overrideFinishers'] = true;
+                $identifier['ext-form-overrideFinishers'] = 'enabled';
             }
         }
         return $identifier;
@@ -177,7 +177,7 @@ class DataStructureIdentifierHook
                         );
                     }
 
-                    if ($identifier['ext-form-overrideFinishers']) {
+                    if ($identifier['ext-form-overrideFinishers'] === 'enabled') {
                         ArrayUtility::mergeRecursiveWithOverrule(
                             $dataStructure,
                             $newSheets
@@ -192,6 +192,7 @@ class DataStructureIdentifierHook
                 $this->addInvalidFrameworkConfigurationFlashMessage($e);
             }
         }
+
         return $dataStructure;
     }
 
@@ -256,7 +257,8 @@ class DataStructureIdentifierHook
                 GeneralUtility::makeInstance(
                     ArrayProcessing::class,
                     'convertToFlexFormSheets',
-                    '^(.*)\.config\.type$',
+                    // Either process leaf form field configuration not within a FlexForm section or FlexForm section
+                    '^(.*)(?:(?<!\.TCEforms)\.config\.type|\.section)$',
                     GeneralUtility::makeInstance(FinisherOptionGenerator::class, $converterDto)
                 )
             );
index e5a2cae..f9e992a 100644 (file)
@@ -448,13 +448,12 @@ TYPO3:
           implementationClassName: 'TYPO3\CMS\Form\Domain\Finishers\EmailFinisher'
           options:
             #subject: ''
-            #recipientAddress: ''
-            #recipientName: ''
+            #recipients: {}
             #senderAddress:
             #senderName: ''
-            #replyToAddress: ''
-            #carbonCopyAddress: ''
-            #blindCarbonCopyAddress: ''
+            #replyToRecipients: {}
+            #carbonCopyRecipients: {}
+            #blindCarbonCopyRecipients: {}
             #format: 'html'
             #attachUploads: true
             #translation:
index f0059e7..29bd516 100644 (file)
@@ -220,24 +220,21 @@ TYPO3:
                           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'
+                        350:
+                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.recipients.label'
+                          fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.recipients.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'
+                        750:
+                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.replyToRecipients.label'
+                        850:
+                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.carbonCopyRecipients.label'
+                        950:
+                          label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.blindCarbonCopyRecipients.label'
                         1000:
                           label: 'formEditor.elements.Form.finisher.EmailToReceiver.editor.format.label'
                         1100:
@@ -915,13 +912,12 @@ TYPO3:
                 predefinedDefaults:
                   options:
                     subject: ''
-                    recipientAddress: ''
-                    recipientName: ''
+                    recipients: {}
                     senderAddress: ''
                     senderName: ''
-                    replyToAddress: ''
-                    carbonCopyAddress: ''
-                    blindCarbonCopyAddress: ''
+                    replyToRecipients: {}
+                    carbonCopyRecipients: {}
+                    blindCarbonCopyRecipients: {}
                     format: 'html'
                     attachUploads: true
 
@@ -932,13 +928,12 @@ TYPO3:
                 predefinedDefaults:
                   options:
                     subject: ''
-                    recipientAddress: ''
-                    recipientName: ''
+                    recipients: {}
                     senderAddress: ''
                     senderName: ''
-                    replyToAddress: ''
-                    carbonCopyAddress: ''
-                    blindCarbonCopyAddress: ''
+                    replyToRecipients: {}
+                    carbonCopyRecipients: {}
+                    blindCarbonCopyRecipients: {}
                     format: 'html'
                     attachUploads: true
                     translation:
@@ -1407,11 +1402,11 @@ TYPO3:
                   templateName: 'Inspector-PropertyGridEditor'
                   label: 'formEditor.elements.SelectionMixin.editor.options.label'
                   propertyPath: 'properties.options'
+                  propertyValidators:
+                    10: 'NotEmpty'
                   isSortable: true
                   enableAddRow: true
                   enableDeleteRow: true
-                  removeLastAvailableRowFlashMessageTitle: 'formEditor.elements.SelectionMixin.editor.options.removeLastAvailableRowFlashMessageTitle'
-                  removeLastAvailableRowFlashMessageMessage: 'formEditor.elements.SelectionMixin.editor.options.removeLastAvailableRowFlashMessageMessage'
 
           SingleSelectionMixin:
             formEditor:
@@ -1521,26 +1516,23 @@ TYPO3:
                 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'
+              350:
+                identifier: 'recipients'
+                templateName: 'Inspector-PropertyGridEditor'
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.label
+                propertyPath: 'options.recipients'
                 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'
+                  10: 'NotEmpty'
+                fieldExplanationText: 'formEditor.elements.Form.finisher.EmailToSender.editor.recipients.fieldExplanationText'
+                isSortable: true
+                enableAddRow: true
+                enableDeleteRow: true
+                useLabelAsFallbackValue: false
+                gridColumns:
+                  - name: value
+                    title: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.value.title
+                  - name: label
+                    title: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.label.title
               500:
                 identifier: 'senderAddress'
                 templateName: 'Inspector-TextEditor'
@@ -1561,36 +1553,48 @@ TYPO3:
                 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'
+              750:
+                identifier: 'replyToRecipients'
+                templateName: 'Inspector-PropertyGridEditor'
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.replyToRecipients.label
+                propertyPath: 'options.replyToRecipients'
+                isSortable: true
+                enableAddRow: true
+                enableDeleteRow: true
+                useLabelAsFallbackValue: false
+                gridColumns:
+                  - name: value
+                    title: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.value.title
+                  - name: label
+                    title: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.label.title
+              850:
+                identifier: 'carbonCopyRecipients'
+                templateName: 'Inspector-PropertyGridEditor'
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.carbonCopyRecipients.label
+                propertyPath: 'options.carbonCopyRecipients'
+                isSortable: true
+                enableAddRow: true
+                enableDeleteRow: true
+                useLabelAsFallbackValue: false
+                gridColumns:
+                  - name: value
+                    title: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.value.title
+                  - name: label
+                    title: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.label.title
+              950:
+                identifier: 'blindCarbonCopyRecipients'
+                templateName: 'Inspector-PropertyGridEditor'
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.blindCarbonCopyRecipients.label
+                propertyPath: 'options.blindCarbonCopyRecipients'
+                isSortable: true
+                enableAddRow: true
+                enableDeleteRow: true
+                useLabelAsFallbackValue: false
+                gridColumns:
+                  - name: value
+                    title: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.value.title
+                  - name: label
+                    title: formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.label.title
               1000:
                 identifier: 'format'
                 templateName: 'Inspector-SingleSelectEditor'
index 7ec85d3..fcebcae 100644 (file)
@@ -44,20 +44,18 @@ TYPO3:
                 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'
+                  recipients:
+                    title: 'tt_content.finishersDefinition.EmailToReceiver.recipients.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'
+                  replyToRecipients:
+                    title: 'tt_content.finishersDefinition.EmailToReceiver.replyToRecipients.label'
+                  carbonCopyRecipients:
+                    title: 'tt_content.finishersDefinition.EmailToReceiver.carbonCopyRecipients.label'
+                  blindCarbonCopyRecipients:
+                    title: 'tt_content.finishersDefinition.EmailToReceiver.blindCarbonCopyRecipients.label'
                   format:
                     label: 'tt_content.finishersDefinition.EmailToReceiver.format.label'
                   translation:
@@ -105,15 +103,30 @@ TYPO3:
               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'
+            recipients:
+              title: 'tt_content.finishersDefinition.EmailToSender.recipients.label'
+              type: 'array'
+              # The "section*" options are processed in
+              # FormFrontendController::overrideByFlexFormSettings()
+              section: true
+              sectionItemKey: email
+              sectionItemValue: name
+              el:
+                _arrayContainer:
+                  type: 'array'
+                  title: tt_content.finishersDefinition.EmailToSender.recipients.item.label
+                  el:
+                    email:
+                      TCEforms:
+                        label: tt_content.finishersDefinition.EmailToSender.recipients.email.label
+                        config:
+                          type: input
+                          eval: required,email
+                    name:
+                      TCEforms:
+                        label: tt_content.finishersDefinition.EmailToSender.recipients.name.label
+                        config:
+                          type: input
             senderAddress:
               label: 'tt_content.finishersDefinition.EmailToSender.senderAddress.label'
               config:
@@ -123,18 +136,78 @@ TYPO3:
               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'
+            replyToRecipients:
+              title: 'tt_content.finishersDefinition.EmailToSender.replyToRecipients.label'
+              type: 'array'
+              # The "section*" options are processed in
+              # FormFrontendController::overrideByFlexFormSettings()
+              section: true
+              sectionItemKey: email
+              sectionItemValue: name
+              el:
+                _arrayContainer:
+                  type: 'array'
+                  title: tt_content.finishersDefinition.EmailToSender.replyToRecipients.item.label
+                  el:
+                    email:
+                      TCEforms:
+                        label: tt_content.finishersDefinition.EmailToSender.recipients.email.label
+                        config:
+                          type: input
+                          eval: required,email
+                    name:
+                      TCEforms:
+                        label: tt_content.finishersDefinition.EmailToSender.recipients.name.label
+                        config:
+                          type: input
+            carbonCopyRecipients:
+              title: 'tt_content.finishersDefinition.EmailToSender.carbonCopyRecipients.label'
+              type: 'array'
+              # The "section*" options are processed in
+              # FormFrontendController::overrideByFlexFormSettings()
+              section: true
+              sectionItemKey: email
+              sectionItemValue: name
+              el:
+                _arrayContainer:
+                  type: 'array'
+                  title: tt_content.finishersDefinition.EmailToSender.carbonCopyRecipients.item.label
+                  el:
+                    email:
+                      TCEforms:
+                        label: tt_content.finishersDefinition.EmailToSender.recipients.email.label
+                        config:
+                          type: input
+                          eval: required,email
+                    name:
+                      TCEforms:
+                        label: tt_content.finishersDefinition.EmailToSender.recipients.name.label
+                        config:
+                          type: input
+            blindCarbonCopyRecipients:
+              title: 'tt_content.finishersDefinition.EmailToSender.blindCarbonCopyRecipients.label'
+              type: 'array'
+              # The "section*" options are processed in
+              # FormFrontendController::overrideByFlexFormSettings()
+              section: true
+              sectionItemKey: email
+              sectionItemValue: name
+              el:
+                _arrayContainer:
+                  type: 'array'
+                  title: tt_content.finishersDefinition.EmailToSender.blindCarbonCopyRecipients.item.label
+                  el:
+                    email:
+                      TCEforms:
+                        label: tt_content.finishersDefinition.EmailToSender.recipients.email.label
+                        config:
+                          type: input
+                          eval: required,email
+                    name:
+                      TCEforms:
+                        label: tt_content.finishersDefinition.EmailToSender.recipients.name.label
+                        config:
+                          type: input
             format:
               label: 'tt_content.finishersDefinition.EmailToSender.format.label'
               config:
index e133235..355a9e1 100644 (file)
@@ -5,20 +5,20 @@
         <div data-editor="new-property-grid" data-template-property="newPropertyPath">
             <table class="table table-hover" data-identifier="propertyGridContainer">
                 <thead>
-                    <tr>
+                    <tr data-identifier="headerRow">
                         <th></th>
-                        <th>Label</th>
-                        <th>Value</th>
-                        <th>Selected</th>
+                        <th data-identifier="column" data-column="label"></th>
+                        <th data-identifier="column" data-column="value"></th>
+                        <th data-identifier="column" data-column="selected"></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><input type="text" class="form-control" value="" data-identifier="label" /></td>
-                        <td><input type="text" class="form-control" value="" data-identifier="value" /></td>
-                        <td><input type="checkbox" data-identifier="selectValue" /></td>
+                        <td data-identifier="column" data-column="label"><input type="text" class="form-control" value="" data-identifier="label" /></td>
+                        <td data-identifier="column" data-column="value"><input type="text" class="form-control" value="" data-identifier="value" /></td>
+                        <td data-identifier="column" data-column="selected"><input type="checkbox" data-identifier="selectValue" /></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>
                                 <button data-random-id data-random-id-attribute="id" data-random-id-number="1" 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 data-identifier="column" data-column="label"></td>
+                        <td data-identifier="column" data-column="value"></td>
+                        <td data-identifier="column" data-column="selected"></td>
                         <td></td>
                     </tr>
                 </tbody>
             </table>
         </div>
         <div data-editor="property-grid" data-template-property="propertyPath" class="t3-form-grid" />
+        <span class="inspector-editor-hint" data-template-property="fieldExplanationText" />
+        <span data-template-property="validationErrors" />
     </div>
 </div>
 </html>
index e6bce2b..5783420 100644 (file)
@@ -7,13 +7,13 @@ finishers:
     identifier: EmailToReceiver
     options:
       subject: 'Your message: {subject}'
-      recipientAddress: 'your.company@example.com'
-      recipientName: 'Your Company name'
+      recipients:
+        your.company@example.com: Your Company name'
       senderAddress: '{email}'
       senderName: '{name}'
-      replyToAddress: ''
-      carbonCopyAddress: ''
-      blindCarbonCopyAddress: ''
+      replyToRecipients: {}
+      carbonCopyRecipients: {}
+      blindCarbonCopyRecipients: {}
       format: 'html'
       attachUploads: 'true'
       translation:
index ee04f24..1e98122 100644 (file)
             <trans-unit id="tt_content.finishersDefinition.EmailToSender.subject.label" xml:space="preserve">
                 <source>Subject of the email</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToSender.recipientAddress.label" xml:space="preserve">
-                <source>Email address of the recipient</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.recipients.label" xml:space="preserve">
+                <source>Recipients</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToSender.recipientName.label" xml:space="preserve">
-                <source>Human-readable name of the recipient</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.recipients.item.label" xml:space="preserve">
+                <source>Recipient</source>
+            </trans-unit>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.recipients.email.label" xml:space="preserve">
+                <source>Email Address</source>
+            </trans-unit>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.recipients.name.label" xml:space="preserve">
+                <source>Name</source>
             </trans-unit>
             <trans-unit id="tt_content.finishersDefinition.EmailToSender.senderAddress.label" xml:space="preserve">
                 <source>Email address of the sender</source>
             <trans-unit id="tt_content.finishersDefinition.EmailToSender.senderName.label" xml:space="preserve">
                 <source>Human-readable name of the sender</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToSender.replyToAddress.label" xml:space="preserve">
-                <source>Email address of to be used as reply-to email</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.replyToRecipients.label" xml:space="preserve">
+                <source>Reply-To Recipients</source>
+            </trans-unit>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.replyToRecipients.item.label" xml:space="preserve">
+                <source>Reply-To Recipient</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToSender.carbonCopyAddress.label" xml:space="preserve">
-                <source>Email address of the copy recipient</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.carbonCopyRecipients.label" xml:space="preserve">
+                <source>CC Recipients</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToSender.blindCarbonCopyAddress.label" xml:space="preserve">
-                <source>Email address of the blind copy recipient</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.carbonCopyRecipients.item.label" xml:space="preserve">
+                <source>CC Recipient</source>
+            </trans-unit>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.blindCarbonCopyRecipients.label" xml:space="preserve">
+                <source>BCC Recipients</source>
+            </trans-unit>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.blindCarbonCopyRecipients.item.label" xml:space="preserve">
+                <source>BCC Recipient</source>
             </trans-unit>
             <trans-unit id="tt_content.finishersDefinition.EmailToSender.format.label" xml:space="preserve">
                 <source>The format of the email</source>
             <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.subject.label" xml:space="preserve">
                 <source>Subject of the email</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.recipientAddress.label" xml:space="preserve">
-                <source>Email address of the recipient</source>
-            </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.recipientName.label" xml:space="preserve">
-                <source>Human-readable name of the recipient</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.recipients.label" xml:space="preserve">
+                <source>Email addresses and human-readable names of the recipients</source>
             </trans-unit>
             <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.senderAddress.label" xml:space="preserve">
                 <source>Email address of the sender</source>
             <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.senderName.label" xml:space="preserve">
                 <source>Human-readable name of the sender</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.replyToAddress.label" xml:space="preserve">
-                <source>Email address of to be used as reply-to email</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.replyToRecipients.label" xml:space="preserve">
+                <source>Reply-To Recipients</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.carbonCopyAddress.label" xml:space="preserve">
-                <source>Email address of the copy recipient</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.carbonCopyRecipients.label" xml:space="preserve">
+                <source>CC Recipients</source>
             </trans-unit>
-            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.blindCarbonCopyAddress.label" xml:space="preserve">
-                <source>Email address of the blind copy recipient</source>
+            <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.blindCarbonCopyRecipients.label" xml:space="preserve">
+                <source>BCC Recipients</source>
             </trans-unit>
             <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.format.label" xml:space="preserve">
                 <source>Format of the email</source>
             <trans-unit id="formEditor.elements.SelectionMixin.editor.options.label" xml:space="preserve">
                 <source>Choices</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.SelectionMixin.editor.options.removeLastAvailableRowFlashMessageTitle" xml:space="preserve">
-                <source>Error</source>
-            </trans-unit>
-            <trans-unit id="formEditor.elements.SelectionMixin.editor.options.removeLastAvailableRowFlashMessageMessage" xml:space="preserve">
-                <source>There must be at least one row within your options.</source>
-            </trans-unit>
 
             <trans-unit id="formEditor.elements.MultiSelectionMixin.editor.validators.label" xml:space="preserve">
                 <source>Validators</source>
             <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.subject.label" xml:space="preserve">
                 <source>Subject</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.recipientAddress.label" xml:space="preserve">
-                <source>Recipient address</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.recipients.label" xml:space="preserve">
+                <source>Recipients</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.recipientAddress.fieldExplanationText" xml:space="preserve">
-                <source>The email address of the website visitor to whom the email should be sent. You can use the "insert formelement identifier" dropdown to choose a form element which holds the website visitors email address.</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.recipients.fieldExplanationText" xml:space="preserve">
+                <source>The email addresses and names of the website visitors to which the email should be sent.</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.recipientName.label" xml:space="preserve">
-                <source>Recipient name</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.value.title" xml:space="preserve">
+                <source>Email Address</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.recipientName.fieldExplanationText" xml:space="preserve">
-                <source>The name of the website visitor which appears in the website visitor email client. You can use the "insert formelement identifier" dropdown to choose one or more form elements which holds the website visitors name.</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.recipients.gridColumns.label.title" xml:space="preserve">
+                <source>Name</source>
             </trans-unit>
             <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.label" xml:space="preserve">
                 <source>Sender address</source>
             <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.senderName.fieldExplanationText" xml:space="preserve">
                 <source>The name of the sender (e.g. your company's name) which appears in the website visitor email client.</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.replyToAddress.label" xml:space="preserve">
-                <source>Reply-to address</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.replyToRecipients.label" xml:space="preserve">
+                <source>Reply-To Recipients</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.carbonCopyAddress.label" xml:space="preserve">
-                <source>CC address</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.carbonCopyRecipients.label" xml:space="preserve">
+                <source>CC Recipients</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.blindCarbonCopyAddress.label" xml:space="preserve">
-                <source>BCC address</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.blindCarbonCopyRecipients.label" xml:space="preserve">
+                <source>BCC Recipients</source>
             </trans-unit>
             <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.format.label" xml:space="preserve">
                 <source>Format</source>
             <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.subject.label" xml:space="preserve">
                 <source>Subject</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientAddress.label" xml:space="preserve">
-                <source>Recipient address</source>
-            </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientAddress.fieldExplanationText" xml:space="preserve">
-                <source>The email address of the recipient to whom the email should be sent (e.g. your company's email address).</source>
-            </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientName.label" xml:space="preserve">
-                <source>Recipient name</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.recipients.label" xml:space="preserve">
+                <source>Recipients</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.recipientName.fieldExplanationText" xml:space="preserve">
-                <source>The name of the recipient which appears in your email client (e.g. your company's name).</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.recipients.fieldExplanationText" xml:space="preserve">
+                <source>The email addresses and names of the recipients to which the email should be sent (e.g. your company's email addresses).</source>
             </trans-unit>
             <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.senderAddress.label" xml:space="preserve">
                 <source>Sender address</source>
             <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.senderName.fieldExplanationText" xml:space="preserve">
                 <source>The name of the sender which appears in your email client. If you want, you can use the "insert formelement identifier" dropdown to choose one or more form elements which holds the website visitors name.</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.replyToAddress.label" xml:space="preserve">
-                <source>Reply-to address</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.replyToRecipients.label" xml:space="preserve">
+                <source>Reply-To Recipients</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.carbonCopyAddress.label" xml:space="preserve">
-                <source>CC address</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.carbonCopyRecipients.label" xml:space="preserve">
+                <source>CC Recipients</source>
             </trans-unit>
-            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.blindCarbonCopyAddress.label" xml:space="preserve">
-                <source>BCC address</source>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.blindCarbonCopyRecipients.label" xml:space="preserve">
+                <source>BCC Recipients</source>
             </trans-unit>
             <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.format.label" xml:space="preserve">
                 <source>Format</source>
index b924c3f..d22a014 100644 (file)
@@ -143,6 +143,16 @@ define(['jquery'], function($) {
        * @param mixed value
        * @return bool
        */
+      function isNonEmptyArray(value) {
+        return ('array' === $.type(value) && value.length > 0);
+      };
+
+      /**
+       * @public
+       *
+       * @param mixed value
+       * @return bool
+       */
       function isNonEmptyString(value) {
         return ('string' === $.type(value) && value.length > 0);
       };
@@ -236,6 +246,7 @@ define(['jquery'], function($) {
       return {
         assert: assert,
         convertToSimpleObject: convertToSimpleObject,
+        isNonEmptyArray: isNonEmptyArray,
         isNonEmptyString: isNonEmptyString,
         isUndefinedOrNull: isUndefinedOrNull,
         buildPropertyPath: buildPropertyPath
index cf54f42..a1a0805 100644 (file)
@@ -93,12 +93,14 @@ define(['jquery',
 
         inspectorFinishers: 'inspectorFinishers',
         inspectorValidators: 'inspectorValidators',
+        propertyGridEditorHeaderRow: 'headerRow',
         propertyGridEditorAddRow: 'addRow',
         propertyGridEditorAddRowItem: 'addRowItem',
         propertyGridEditorContainer: 'propertyGridContainer',
         propertyGridEditorDeleteRow: 'deleteRow',
         propertyGridEditorLabel: 'label',
         propertyGridEditorRowItem: 'rowItem',
+        propertyGridEditorColumn: 'column',
         propertyGridEditorSelectValue: 'selectValue',
         propertyGridEditorSortRow: 'sortRow',
         propertyGridEditorValue: 'value',
@@ -536,6 +538,7 @@ define(['jquery',
       });
 
       getCurrentlySelectedFormElement().set(propertyPathPrefix + propertyPath, newPropertyData);
+      _validateCollectionElement(propertyPathPrefix + propertyPath, editorHtml);
     };
 
     /**
@@ -1727,8 +1730,8 @@ define(['jquery',
      * @throws 1475419232
      */
     function renderPropertyGridEditor(editorConfiguration, editorHtml, collectionElementIdentifier, collectionName) {
-      var addRowTemplate, defaultValue, multiSelection, propertyData, propertyPathPrefix,
-        rowItemTemplate, setData;
+      var addRowTemplate, gridColumns, defaultValue, multiSelection, propertyData, propertyPathPrefix,
+        rowItemTemplate, setData, useLabelAsFallbackValue;
       assert(
         'object' === $.type(editorConfiguration),
         'Invalid parameter "editorConfiguration"',
@@ -1765,7 +1768,18 @@ define(['jquery',
         1475419232
       );
 
-      getHelper().getTemplatePropertyDomElement('label', editorHtml).append(editorConfiguration['label']);
+      getHelper().getTemplatePropertyDomElement('label', editorHtml)
+        .append(editorConfiguration['label']);
+      if (getUtility().isNonEmptyString(editorConfiguration['fieldExplanationText'])) {
+        getHelper()
+          .getTemplatePropertyDomElement('fieldExplanationText', editorHtml)
+          .text(editorConfiguration['fieldExplanationText']);
+      } else {
+        getHelper()
+          .getTemplatePropertyDomElement('fieldExplanationText', editorHtml)
+          .remove();
+      }
+
       propertyPathPrefix = getFormEditorApp().buildPropertyPath(
         undefined,
         collectionElementIdentifier,
@@ -1777,6 +1791,65 @@ define(['jquery',
         propertyPathPrefix = propertyPathPrefix + '.';
       }
 
+      if (getUtility().isUndefinedOrNull(editorConfiguration['useLabelAsFallbackValue'])) {
+        useLabelAsFallbackValue = true;
+      } else {
+        useLabelAsFallbackValue = editorConfiguration['useLabelAsFallbackValue'];
+      }
+
+      gridColumns = [
+        {name: 'label', title: 'Label'},
+        {name: 'value', title: 'Value'},
+        {name: 'selected', title: 'Selected'},
+      ];
+      if (getUtility().isNonEmptyArray(editorConfiguration['gridColumns'])) {
+        gridColumns = editorConfiguration['gridColumns'];
+      }
+      var orderedGridColumnNames = gridColumns.map(function(item) {
+        return item.name;
+      });
+      var orderedGridColumnTitles = gridColumns.map(function(item) {
+        return item.title || null;
+      });
+
+      $([
+        getHelper().getDomElementDataIdentifierSelector('propertyGridEditorHeaderRow'),
+        getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'),
+        getHelper().getDomElementDataIdentifierSelector('propertyGridEditorAddRowItem'),
+      ].join(','), $(editorHtml)).each(function (i, row) {
+        var $columns = $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorColumn'), row);
+        var $columnsAfter = $columns.last().nextAll();
+        var columnsByName = {};
+
+        // Collect columns by names, skip undesired columns
+        $columns
+          .detach()
+          .each(function(i, element) {
+            var $column = $(element);
+            var columnName = $column.data('column');
+
+            if (!orderedGridColumnNames.includes(columnName)) {
+              return;
+            }
+
+            columnsByName[columnName] = $column;
+          });
+
+        // Insert columns in desired order
+        orderedGridColumnNames.forEach(function(columnName, i) {
+          var $column = columnsByName[columnName];
+
+          if ($column.is('th')) {
+            $column.append(orderedGridColumnTitles[i]);
+          }
+
+          $column.appendTo(row);
+        });
+
+        // Insert remaining columns
+        $columnsAfter.appendTo(row);
+      });
+
       if (getUtility().isUndefinedOrNull(editorConfiguration['multiSelection'])) {
         multiSelection = false;
       } else {
@@ -1793,26 +1866,18 @@ define(['jquery',
         $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorDeleteRow'),
           $(rowItemTemplate)
         ).on('click', function() {
-          if ($(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'), $(editorHtml)).length > 1) {
-            $(this)
-              .closest(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'))
-              .off()
-              .empty()
-              .remove();
-
-            _setPropertyGridData(
-              $(editorHtml),
-              multiSelection,
-              editorConfiguration['propertyPath'],
-              propertyPathPrefix
-            );
-          } else {
-            Notification.error(
-              editorConfiguration['removeLastAvailableRowFlashMessageTitle'],
-              editorConfiguration['removeLastAvailableRowFlashMessageMessage'],
-              2
-            );
-          }
+          $(this)
+            .closest(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'))
+            .off()
+            .empty()
+            .remove();
+
+          _setPropertyGridData(
+            $(editorHtml),
+            multiSelection,
+            editorConfiguration['propertyPath'],
+            propertyPathPrefix
+          );
         });
       } else {
         $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorDeleteRow'), $(rowItemTemplate))
@@ -1828,7 +1893,12 @@ define(['jquery',
             revert: 'true',
             items: getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'),
             update: function(e, o) {
-              _setPropertyGridData($(editorHtml), multiSelection, editorConfiguration['propertyPath'], propertyPathPrefix);
+              _setPropertyGridData(
+                $(editorHtml),
+                multiSelection,
+                editorConfiguration['propertyPath'],
+                propertyPathPrefix
+              );
             }
           });
       } else {
@@ -1846,30 +1916,42 @@ define(['jquery',
             .not($(this))
             .prop('checked', false);
         }
-        _setPropertyGridData($(editorHtml), multiSelection, editorConfiguration['propertyPath'], propertyPathPrefix);
+        _setPropertyGridData(
+          $(editorHtml),
+          multiSelection,
+          editorConfiguration['propertyPath'],
+          propertyPathPrefix
+        );
       });
 
       $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorLabel') + ',' +
         getHelper().getDomElementDataIdentifierSelector('propertyGridEditorValue'),
         $(rowItemTemplate)
       ).on('keyup paste', function() {
-        _setPropertyGridData($(editorHtml), multiSelection, editorConfiguration['propertyPath'], propertyPathPrefix);
+        _setPropertyGridData(
+          $(editorHtml),
+          multiSelection,
+          editorConfiguration['propertyPath'],
+          propertyPathPrefix
+         );
       });
 
-      $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorLabel'),
-        $(rowItemTemplate)
-      ).on('focusout', function() {
-        if ('' === $(this)
-            .closest(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'))
-            .find(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorValue'))
-            .val()
-        ) {
-          $(this)
-            .closest(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'))
-            .find(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorValue'))
-            .val($(this).val());
-        }
-      });
+      if (useLabelAsFallbackValue) {
+        $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorLabel'),
+          $(rowItemTemplate)
+        ).on('focusout', function() {
+          if ('' === $(this)
+              .closest(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'))
+              .find(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorValue'))
+              .val()
+          ) {
+            $(this)
+              .closest(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorRowItem'))
+              .find(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorValue'))
+              .val($(this).val());
+          }
+        });
+      }
 
       if (!!editorConfiguration['enableAddRow']) {
         addRowTemplate = $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorAddRowItem'), $(editorHtml)).clone();
@@ -1879,6 +1961,13 @@ define(['jquery',
           $(this)
             .closest(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorAddRowItem'))
             .before($(rowItemTemplate).clone(true, true));
+
+            _setPropertyGridData(
+              $(editorHtml),
+              multiSelection,
+              editorConfiguration['propertyPath'],
+              propertyPathPrefix
+            );
         });
         $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorContainer'), $(editorHtml))
           .prepend($(addRowTemplate).clone(true, true));
@@ -1916,6 +2005,7 @@ define(['jquery',
 
         $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorLabel'), $(newRowTemplate)).val(label);
         $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorValue'), $(newRowTemplate)).val(value);
+
         if (isPreselected) {
           $(getHelper().getDomElementDataIdentifierSelector('propertyGridEditorSelectValue'), $(newRowTemplate))
             .prop('checked', true);
@@ -1949,6 +2039,8 @@ define(['jquery',
           }
         }
       }
+
+      _validateCollectionElement(propertyPathPrefix + editorConfiguration['propertyPath'], editorHtml);
     };
 
     /**
index 094ff84..9b6e8c7 100644 (file)
@@ -201,7 +201,8 @@ define(['jquery',
      */
     function _addPropertyValidators() {
       getFormEditorApp().addPropertyValidationValidator('NotEmpty', function(formElement, propertyPath) {
-        if (formElement.get(propertyPath) === '') {
+        var value = formElement.get(propertyPath);
+        if (value === '' || $.isArray(value) && !value.length) {
           return getFormEditorApp().getFormElementPropertyValidatorDefinition('NotEmpty')['errorMessage'] || 'invalid value';
         }
       });
index ddf9dbb..100f757 100644 (file)
@@ -392,7 +392,7 @@ class FormEditorControllerTest extends UnitTestCase
     /**
      * @test
      */
-    public function transformMultiValueElementsForFormEditorConvertMultiValueDataIntoMetaData()
+    public function transformMultiValuePropertiesForFormEditorConvertMultiValueDataIntoMetaData()
     {
         $mockController = $this->getAccessibleMock(FormEditorController::class, [
             'dummy'
@@ -468,7 +468,7 @@ class FormEditorControllerTest extends UnitTestCase
             ],
         ];
 
-        $this->assertSame($expected, $mockController->_call('transformMultiValueElementsForFormEditor', $input, $multiValueProperties));
+        $this->assertSame($expected, $mockController->_call('transformMultiValuePropertiesForFormEditor', $input, 'type', $multiValueProperties));
     }
 
     /**
index b816709..6d6d897 100644 (file)
@@ -81,9 +81,28 @@ class FormFrontendControllerTest extends UnitTestCase
                 'data' => [
                     $sheetIdentifier => [
                         'lDEF' => [
-                            'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden'],
-                            'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden'],
-                            'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden'],
+                            'settings.finishers.EmailToReceiver.subject' => [
+                                'vDEF' => 'Message Subject overridden',
+                            ],
+                            'settings.finishers.EmailToReceiver.recipients' => [
+                                'el' => [
+                                    'abc' => [
+                                        '_arrayContainer' => [
+                                            'el' => [
+                                                'email' => [
+                                                    'vDEF' => 'your.company@example.com overridden',
+                                                ],
+                                                'name' => [
+                                                    'vDEF' => '',
+                                                ],
+                                            ],
+                                        ],
+                                    ],
+                                ],
+                            ],
+                            'settings.finishers.EmailToReceiver.format' => [
+                                'vDEF' => 'html overridden',
+                            ],
                         ],
                     ],
                 ],
@@ -105,7 +124,7 @@ class FormFrontendControllerTest extends UnitTestCase
                     'FormEngine' => [
                         'elements' => [
                             'subject' => [],
-                            'recipientAddress' => [],
+                            'recipients' => [],
                             'format' => [],
                         ],
                     ],
@@ -115,6 +134,15 @@ class FormFrontendControllerTest extends UnitTestCase
 
         $mockController->_set('settings', [
             'overrideFinishers' => 0,
+            'finishers' => [
+                'EmailToReceiver' => [
+                    'subject' => 'Message Subject overridden',
+                    'recipients' => [
+                        'your.company@example.com overridden' => '',
+                    ],
+                    'format' => 'html overridden',
+                ],
+            ],
         ]);
 
         $input = [
@@ -125,8 +153,10 @@ class FormFrontendControllerTest extends UnitTestCase
                 0 => [
                     'identifier' => 'EmailToReceiver',
                     'options' => [
-                        'subject' => 'Mesage Subject',
-                        'recipientAddress' => 'your.company@example.com',
+                        'subject' => 'Message Subject',
+                        'recipients' => [
+                            'your.company@example.com' => '',
+                        ],
                         'format' => 'html',
                     ],
                 ],
@@ -141,8 +171,10 @@ class FormFrontendControllerTest extends UnitTestCase
                 0 => [
                     'identifier' => 'EmailToReceiver',
                     'options' => [
-                        'subject' => 'Mesage Subject',
-                        'recipientAddress' => 'your.company@example.com',
+                        'subject' => 'Message Subject',
+                        'recipients' => [
+                            'your.company@example.com' => '',
+                        ],
                         'format' => 'html',
                     ],
                 ],
@@ -186,9 +218,28 @@ class FormFrontendControllerTest extends UnitTestCase
                 'data' => [
                     $sheetIdentifier => [
                         'lDEF' => [
-                            'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden'],
-                            'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden'],
-                            'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden'],
+                            'settings.finishers.EmailToReceiver.subject' => [
+                                'vDEF' => 'Message Subject overridden',
+                            ],
+                            'settings.finishers.EmailToReceiver.recipients' => [
+                                'el' => [
+                                    'abc' => [
+                                        '_arrayContainer' => [
+                                            'el' => [
+                                                'email' => [
+                                                    'vDEF' => 'your.company@example.com overridden',
+                                                ],
+                                                'name' => [
+                                                    'vDEF' => '',
+                                                ],
+                                            ],
+                                        ],
+                                    ],
+                                ],
+                            ],
+                            'settings.finishers.EmailToReceiver.format' => [
+                                'vDEF' => 'html overridden',
+                            ],
                         ],
                     ],
                 ],
@@ -209,9 +260,22 @@ class FormFrontendControllerTest extends UnitTestCase
                 'EmailToReceiver' => [
                     'FormEngine' => [
                         'elements' => [
-                            'subject' => ['config' => ['type' => 'input']],
-                            'recipientAddress' => ['config' => ['type' => 'input']],
-                            'format' => ['config' => ['type' => 'input']],
+                            'subject' => [
+                                'config' => [
+                                    'type' => 'input',
+                                ],
+                            ],
+                            'recipients' => [
+                                'type' => 'array',
+                                'section' => true,
+                                'sectionItemKey' => 'email',
+                                'sectionItemValue' => 'name',
+                            ],
+                            'format' => [
+                                'config' => [
+                                    'type' => 'input',
+                                ],
+                            ],
                         ],
                     ],
                 ],
@@ -220,6 +284,18 @@ class FormFrontendControllerTest extends UnitTestCase
 
         $mockController->_set('settings', [
             'overrideFinishers' => 1,
+            'finishers' => [
+                'EmailToReceiver' => [
+                    'subject' => 'Message Subject overridden',
+                    'recipients' => [
+                        'abcxyz' => [
+                            'email' => 'your.company@example.com overridden',
+                            'name' => '',
+                        ],
+                    ],
+                    'format' => 'html overridden',
+                ],
+            ],
         ]);
 
         $input = [
@@ -230,8 +306,10 @@ class FormFrontendControllerTest extends UnitTestCase
                 0 => [
                     'identifier' => 'EmailToReceiver',
                     'options' => [
-                        'subject' => 'Mesage Subject',
-                        'recipientAddress' => 'your.company@example.com',
+                        'subject' => 'Message Subject',
+                        'recipients' => [
+                            'your.company@example.com' => '',
+                        ],
                         'format' => 'html',
                     ],
                 ],
@@ -246,8 +324,10 @@ class FormFrontendControllerTest extends UnitTestCase
                 0 => [
                     'identifier' => 'EmailToReceiver',
                     'options' => [
-                        'subject' => 'Mesage Subject overridden',
-                        'recipientAddress' => 'your.company@example.com overridden',
+                        'subject' => 'Message Subject overridden',
+                        'recipients' => [
+                            'your.company@example.com overridden' => '',
+                        ],
                         'format' => 'html overridden',
                     ],
                 ],
@@ -291,9 +371,28 @@ class FormFrontendControllerTest extends UnitTestCase
                 'data' => [
                     $sheetIdentifier => [
                         'lDEF' => [
-                            'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden'],
-                            'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden'],
-                            'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden'],
+                            'settings.finishers.EmailToReceiver.subject' => [
+                                'vDEF' => 'Message Subject overridden',
+                            ],
+                            'settings.finishers.EmailToReceiver.recipients' => [
+                                'el' => [
+                                    'abc' => [
+                                        '_arrayContainer' => [
+                                            'el' => [
+                                                'email' => [
+                                                    'vDEF' => 'your.company@example.com overridden',
+                                                ],
+                                                'name' => [
+                                                    'vDEF' => '',
+                                                ],
+                                            ],
+                                        ],
+                                    ],
+                                ],
+                            ],
+                            'settings.finishers.EmailToReceiver.format' => [
+                                'vDEF' => 'html overridden',
+                            ],
                         ],
                     ],
                 ],
@@ -314,8 +413,17 @@ class FormFrontendControllerTest extends UnitTestCase
                 'EmailToReceiver' => [
                     'FormEngine' => [
                         'elements' => [
-                            'subject' => ['config' => ['type' => 'input']],
-                            'recipientAddress' => ['config' => ['type' => 'input']],
+                            'subject' => [
+                                'config' => [
+                                    'type' => 'input',
+                                ],
+                            ],
+                            'recipients' => [
+                                'type' => 'array',
+                                'section' => true,
+                                'sectionItemKey' => 'email',
+                                'sectionItemValue' => 'name',
+                            ],
                         ],
                     ],
                 ],
@@ -326,8 +434,13 @@ class FormFrontendControllerTest extends UnitTestCase
             'overrideFinishers' => 1,
             'finishers' => [
                 'EmailToReceiver' => [
-                    'subject' => 'Mesage Subject overridden',
-                    'recipientAddress' => 'your.company@example.com overridden',
+                    'subject' => 'Message Subject overridden',
+                    'recipients' => [
+                        'abcxyz' => [
+                            'email' => 'your.company@example.com overridden',
+                            'name' => '',
+                        ],
+                    ],
                     'format' => 'html overridden',
                 ],
             ],
@@ -341,8 +454,10 @@ class FormFrontendControllerTest extends UnitTestCase
                 0 => [
                     'identifier' => 'EmailToReceiver',
                     'options' => [
-                        'subject' => 'Mesage Subject',
-                        'recipientAddress' => 'your.company@example.com',
+                        'subject' => 'Message Subject',
+                        'recipients' => [
+                            'your.company@example.com' => '',
+                        ],
                         'format' => 'html',
                     ],
                 ],
@@ -357,135 +472,16 @@ class FormFrontendControllerTest extends UnitTestCase
                 0 => [
                     'identifier' => 'EmailToReceiver',
                     'options' => [
-                        'subject' => 'Mesage Subject overridden',
-                        'recipientAddress' => 'your.company@example.com overridden',
-                        'format' => 'html',
-                    ],
-                ],
-            ],
-        ];
-
-        $this->assertSame($expected, $mockController->_call('overrideByFlexFormSettings', $input));
-    }
-
-    /**
-     * @test
-     */
-    public function overrideByFlexFormSettingsReturnsOverriddenConfigurationWhileMultipleSheetsExists()
-    {
-        $mockController = $this->getAccessibleMock(FormFrontendController::class, [
-            'dummy'
-        ], [], '', false);
-
-        $configurationServiceProphecy = $this->prophesize(ConfigurationService::class);
-
-        $objectManagerMock = $this->createMock(ObjectManager::class);
-        $objectManagerMock
-            ->expects($this->any())
-            ->method('get')
-            ->with(ConfigurationService::class)
-            ->willReturn($configurationServiceProphecy->reveal());
-
-        $sheetIdentifier = md5(
-            implode('', [
-                '1:/foo',
-                'standard',
-                'ext-form-identifier',
-                'EmailToReceiver'
-            ])
-        );
-
-        $anotherSheetIdentifier = md5(
-            implode('', [
-                '1:/foobar',
-                'standard',
-                'another-ext-form-identifier',
-                'EmailToReceiver'
-            ])
-        );
-
-        $flexFormTools = new FlexFormTools;
-        $contentObject = new \stdClass();
-        $contentObject->data = [
-            'pi_flexform' => $flexFormTools->flexArray2Xml([
-                'data' => [
-                    $sheetIdentifier => [
-                        'lDEF' => [
-                            'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden 1'],
-                            'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden 1'],
-                            'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden 1'],
-                        ],
-                    ],
-                    $anotherSheetIdentifier => [
-                        'lDEF' => [
-                            'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden 2'],
-                            'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden 2'],
-                            'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden 2'],
-                        ],
-                    ],
-                ],
-            ]),
-        ];
-
-        $frontendConfigurationManager = $this->createMock(FrontendConfigurationManager::class);
-        $frontendConfigurationManager
-            ->expects($this->any())
-            ->method('getContentObject')
-            ->willReturn($contentObject);
-
-        $mockController->_set('configurationManager', $frontendConfigurationManager);
-        $mockController->_set('objectManager', $objectManagerMock);
-
-        $configurationServiceProphecy->getPrototypeConfiguration(Argument::cetera())->willReturn([
-            'finishersDefinition' => [
-                'EmailToReceiver' => [
-                    'FormEngine' => [
-                        'elements' => [
-                            'subject' => ['config' => ['type' => 'input']],
-                            'recipientAddress' => ['config' => ['type' => 'input']],
-                            'format' => ['config' => ['type' => 'input']],
+                        'subject' => 'Message Subject overridden',
+                        'recipients' => [
+                            'your.company@example.com overridden' => '',
                         ],
-                    ],
-                ],
-            ],
-        ]);
-
-        $mockController->_set('settings', [
-            'overrideFinishers' => 1,
-        ]);
-
-        $input = [
-            'persistenceIdentifier' => '1:/foo',
-            'identifier' => 'ext-form-identifier',
-            'prototypeName' => 'standard',
-            'finishers' => [
-                0 => [
-                    'identifier' => 'EmailToReceiver',
-                    'options' => [
-                        'subject' => 'Mesage Subject',
-                        'recipientAddress' => 'your.company@example.com',
                         'format' => 'html',
                     ],
                 ],
             ],
         ];
 
-        $expected = [
-            'persistenceIdentifier' => '1:/foo',
-            'identifier' => 'ext-form-identifier',
-            'prototypeName' => 'standard',
-            'finishers' => [
-                0 => [
-                    'identifier' => 'EmailToReceiver',
-                    'options' => [
-                        'subject' => 'Mesage Subject overridden 1',
-                        'recipientAddress' => 'your.company@example.com overridden 1',
-                        'format' => 'html overridden 1',
-                    ],
-                ],
-            ],
-        ];
-
         $this->assertSame($expected, $mockController->_call('overrideByFlexFormSettings', $input));
     }
 
index 1da37eb..3a961fe 100644 (file)
@@ -77,7 +77,7 @@ class DataStructureIdentifierHookTest extends UnitTestCase
             []
         );
         $this->assertEquals(
-            ['ext-form-persistenceIdentifier' => '', 'ext-form-overrideFinishers' => false],
+            ['ext-form-persistenceIdentifier' => '', 'ext-form-overrideFinishers' => ''],
             $result
         );
     }
@@ -109,7 +109,7 @@ class DataStructureIdentifierHookTest extends UnitTestCase
         $expected = [
             'aKey' => 'aValue',
             'ext-form-persistenceIdentifier' => '1:user_upload/karl.yml',
-            'ext-form-overrideFinishers' => false,
+            'ext-form-overrideFinishers' => '',
         ];
         $result = (new DataStructureIdentifierHook())->getDataStructureIdentifierPostProcess(
             [],
@@ -144,7 +144,7 @@ class DataStructureIdentifierHookTest extends UnitTestCase
         ];
         $expected = [
             'ext-form-persistenceIdentifier' => '',
-            'ext-form-overrideFinishers' => true,
+            'ext-form-overrideFinishers' => 'enabled',
         ];
         $result = (new DataStructureIdentifierHook())->getDataStructureIdentifierPostProcess(
             [],