Skip to content

Commit

Permalink
[FEATURE] Add FluidEmail option to EXT:form EmailFinisher
Browse files Browse the repository at this point in the history
EXT:form, the last system extension not using FluidEmail
was extended for the corresponing integration. To allow
extension authors to smoothly test and upgrade, the old
StandaloneView functionality remains for now.

As there is no change to the present behaviour the switch
can be done without further actions required - if using the
default templates currently.

To migrate custom templates to FluidEmail, the template files
must be changed from the {@Format}.html syntax to appropriate
names with the correct format extension like `.html` and `.txt`.
Furthermore the `templateName` option must be set with the new name.

Two fields are introduced to configure FluidEmail in the finishers:

- `useFluidEmail`: Enables sending the mails via FluidEmail
- `title`: Used for the title section of the default template

Resolves: #90728
Releases: master, 10.4
Change-Id: I378b733554ba734ad4eb6dff60e1da48ca03c972
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/65901
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Daniel Goerz <daniel.goerz@posteo.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Daniel Goerz <daniel.goerz@posteo.de>
Reviewed-by: Benni Mack <benni@typo3.org>
  • Loading branch information
o-ba authored and bmack committed Sep 29, 2020
1 parent bb460e4 commit 894e750
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 34 deletions.
10 changes: 10 additions & 0 deletions typo3/sysext/core/Classes/Mail/FluidEmail.php
Expand Up @@ -26,6 +26,7 @@
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Fluid\View\TemplatePaths;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperVariableContainer;

/**
* Send out templated HTML/plain text emails with Fluid.
Expand Down Expand Up @@ -143,6 +144,15 @@ public function getBody(): AbstractPart
return parent::getBody();
}

/**
* @return ViewHelperVariableContainer
* @internal Only used for ext:form, not part of TYPO3 Core API.
*/
public function getViewHelperVariableContainer(): ViewHelperVariableContainer
{
return $this->view->getRenderingContext()->getViewHelperVariableContainer();
}

protected function generateTemplatedBody(): void
{
if (in_array(static::FORMAT_HTML, $this->format, true)) {
Expand Down
@@ -0,0 +1,67 @@
.. include:: ../../Includes.txt

=================================================================
Feature: #90728 - Add FluidEmail option to EXT:form EmailFinisher
=================================================================

See :issue:`90728`

Description
===========

After the introduction of FluidEmail in v10 the option to send mails in a
standardized way is now also added to the EmailFinisher of the system extension
EXT:from.

To use FluidEmail a new option `useFluidEmail` is added to both the EmailToReceiver
and EmailToSender finisher. It defaults to :php:`FALSE` so extension authors are
able to smoothly test and upgrade their forms. Furthermore a new option `title`
is available which can be used to add an E-Mail title to the default FluidEmail
template. This option is capable of rendering form element variables using the
known bracket syntax and can be overwritten in the FlexForm configuration of the
form plugin.

To customize the templates beeing used following options can be set:

* `templateName`: The template name (for both HTML and plaintext) without the extension
* `templateRootPaths`: The paths to the templates
* `partialRootPaths`: The paths to the partials
* `layoutRootPaths`: The paths to the layouts

For FluidEmail, the field `templatePathAndFilename` is not evaluated anymore.

A finisher configuration could look like this:

.. code-block:: yaml
identifier: contact
type: Form
prototypeName: standard
finishers:
-
identifier: EmailToSender
options:
subject: 'Your Message: {message}'
title: 'Hello {name}, your confirmation'
templateName: ContactForm
templateRootPaths:
100: 'EXT:sitepackage/Resources/Private/Templates/Email/'
partialRootPaths:
100: 'EXT:sitepackage/Resources/Private/Partials/Email/'
addHtmlPart: true
useFluidEmail: true
Please note that the old template name syntax `{@format}.html` does not work for
FluidEmail as each format needs a different template with the corresponing file
extension. In the example above the following files must exist in the specified
template path:

* `ContactForm.html`
* `ContactForm.txt`

Impact
======

It's now possible to use FluidEmail for sending mails in EXT:form.

.. index:: Fluid, Frontend, ext:form
145 changes: 111 additions & 34 deletions typo3/sysext/form/Classes/Domain/Finishers/EmailFinisher.php
Expand Up @@ -18,10 +18,14 @@
namespace TYPO3\CMS\Form\Domain\Finishers;

use Symfony\Component\Mime\Address;
use TYPO3\CMS\Core\Mail\FluidEmail;
use TYPO3\CMS\Core\Mail\Mailer;
use TYPO3\CMS\Core\Mail\MailMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Fluid\View\TemplatePaths;
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
Expand All @@ -33,9 +37,11 @@
*
* Options:
*
* - templatePathAndFilename (mandatory): Template path and filename for the mail body
* - layoutRootPath: root path for the layouts
* - partialRootPath: root path for the partials
* - templatePathAndFilename (mandatory for Mail): Template path and filename for the mail body
* - templateName (mandatory for FluidEmail): Template name for the mail body
* - templateRootPaths: root paths for the templates
* - layoutRootPaths: root paths for the layouts
* - partialRootPaths: root paths for the partials
* - 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
Expand Down Expand Up @@ -97,6 +103,9 @@ protected function executeInternal()
$blindCarbonCopyRecipients = $this->getRecipients('blindCarbonCopyRecipients', 'blindCarbonCopyAddress');
$addHtmlPart = $this->isHtmlPartAdded();
$attachUploads = $this->parseOption('attachUploads');
$useFluidEmail = $this->parseOption('useFluidEmail');
$title = $this->parseOption('title');
$title = is_string($title) && $title !== '' ? $title : $subject;

if (empty($subject)) {
throw new FinisherException('The option "subject" must be set for the EmailFinisher.', 1327060320);
Expand All @@ -108,9 +117,23 @@ protected function executeInternal()
throw new FinisherException('The option "senderAddress" must be set for the EmailFinisher.', 1327060210);
}

$mail = $this->objectManager->get(MailMessage::class);
$formRuntime = $this->finisherContext->getFormRuntime();

$translationService = TranslationService::getInstance();
if (is_string($this->options['translation']['language'] ?? null) && $this->options['translation']['language'] !== '') {
$languageBackup = $translationService->getLanguage();
$translationService->setLanguage($this->options['translation']['language']);
}

$mail->from(new Address($senderAddress, $senderName))
$mail = $useFluidEmail
? $this
->initializeFluidEmail($formRuntime)
->format($addHtmlPart ? FluidEmail::FORMAT_BOTH : FluidEmail::FORMAT_PLAIN)
->assign('title', $title)
: GeneralUtility::makeInstance(MailMessage::class);

$mail
->from(new Address($senderAddress, $senderName))
->to(...$recipients)
->subject($subject);

Expand All @@ -126,36 +149,30 @@ protected function executeInternal()
$mail->bcc(...$blindCarbonCopyRecipients);
}

$formRuntime = $this->finisherContext->getFormRuntime();

$translationService = TranslationService::getInstance();
if (isset($this->options['translation']['language']) && !empty($this->options['translation']['language'])) {
$languageBackup = $translationService->getLanguage();
$translationService->setLanguage($this->options['translation']['language']);
}

$parts = [
[
'format' => 'Plaintext',
'contentType' => 'text/plain',
],
];

if ($addHtmlPart) {
$parts[] = [
'format' => 'Html',
'contentType' => 'text/html',
if (!$useFluidEmail) {
$parts = [
[
'format' => 'Plaintext',
'contentType' => 'text/plain',
],
];
}

foreach ($parts as $i => $part) {
$standaloneView = $this->initializeStandaloneView($formRuntime, $part['format']);
$message = $standaloneView->render();
if ($addHtmlPart) {
$parts[] = [
'format' => 'Html',
'contentType' => 'text/html',
];
}

foreach ($parts as $i => $part) {
$standaloneView = $this->initializeStandaloneView($formRuntime, $part['format']);
$message = $standaloneView->render();

if ($part['contentType'] === 'text/plain') {
$mail->text($message);
} else {
$mail->html($message);
if ($part['contentType'] === 'text/plain') {
$mail->text($message);
} else {
$mail->html($message);
}
}
}

Expand All @@ -181,7 +198,7 @@ protected function executeInternal()
}
}

$mail->send();
$useFluidEmail ? GeneralUtility::makeInstance(Mailer::class)->send($mail) : $mail->send();
}

/**
Expand Down Expand Up @@ -231,10 +248,70 @@ protected function initializeStandaloneView(FormRuntime $formRuntime, string $fo

$standaloneView->assign('form', $formRuntime);
$standaloneView->getRenderingContext()
->getViewHelperVariableContainer()
->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);

return $standaloneView;
}

protected function initializeFluidEmail(FormRuntime $formRuntime): FluidEmail
{
$templateConfiguration = $GLOBALS['TYPO3_CONF_VARS']['MAIL'];

if (is_array($this->options['templateRootPaths'] ?? null)) {
$templateConfiguration['templateRootPaths'] = array_replace_recursive(
$templateConfiguration['templateRootPaths'],
$this->options['templateRootPaths']
);
ksort($templateConfiguration['templateRootPaths']);
}

if (is_array($this->options['partialRootPaths'] ?? null)) {
$templateConfiguration['partialRootPaths'] = array_replace_recursive(
$templateConfiguration['partialRootPaths'],
$this->options['partialRootPaths']
);
ksort($templateConfiguration['partialRootPaths']);
}

if (is_array($this->options['layoutRootPaths'] ?? null)) {
$templateConfiguration['layoutRootPaths'] = array_replace_recursive(
$templateConfiguration['layoutRootPaths'],
$this->options['layoutRootPaths']
);
ksort($templateConfiguration['layoutRootPaths']);
}

$fluidEmail = GeneralUtility::makeInstance(
FluidEmail::class,
GeneralUtility::makeInstance(TemplatePaths::class, $templateConfiguration)
);

if (!isset($this->options['templateName']) || $this->options['templateName'] === '') {
throw new FinisherException('The option "templateName" must be set to use FluidEmail.', 1599834020);
}

// Migrate old template name to default FluidEmail name
if ($this->options['templateName'] === '{@format}.html') {
$this->options['templateName'] = 'Default';
}

$fluidEmail
->setTemplate($this->options['templateName'])
->assignMultiple([
'finisherVariableProvider' => $this->finisherContext->getFinisherVariableProvider(),
'form' => $formRuntime
]);

if (is_array($this->options['variables'] ?? null)) {
$fluidEmail->assignMultiple($this->options['variables']);
}

$fluidEmail
->getViewHelperVariableContainer()
->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);

return $standaloneView;
return $fluidEmail;
}

/**
Expand Down
Expand Up @@ -26,6 +26,8 @@ TYPO3:
attachUploads: true
translation:
language: ''
useFluidEmail: false
title: ''
FormEngine:
label: tt_content.finishersDefinition.EmailToReceiver.label
elements:
Expand Down Expand Up @@ -149,3 +151,7 @@ TYPO3:
10:
- tt_content.finishersDefinition.EmailToReceiver.language.1
- default
title:
label: tt_content.finishersDefinition.EmailToReceiver.title.label
config:
type: input
Expand Up @@ -24,6 +24,8 @@ TYPO3:
blindCarbonCopyRecipients: { }
addHtmlPart: true
attachUploads: true
useFluidEmail: false
title: ''
FormEngine:
label: tt_content.finishersDefinition.EmailToSender.label
elements:
Expand Down Expand Up @@ -147,3 +149,7 @@ TYPO3:
10:
- tt_content.finishersDefinition.EmailToSender.language.1
- default
title:
label: tt_content.finishersDefinition.EmailToSender.title.label
config:
type: input
30 changes: 30 additions & 0 deletions typo3/sysext/form/Configuration/Yaml/FormElements/Form.yaml
Expand Up @@ -193,6 +193,21 @@ TYPO3:
10:
value: default
label: formEditor.elements.Form.finisher.EmailToSender.editor.language.1
1300:
identifier: useFluidEmail
templateName: Inspector-CheckboxEditor
label: formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.label
propertyPath: options.useFluidEmail
fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.fieldExplanationText
1400:
identifier: title
templateName: Inspector-TextEditor
label: formEditor.elements.Form.finisher.EmailToSender.editor.title.label
propertyPath: options.title
fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.title.fieldExplanationText
enableFormelementSelectionButton: true
propertyValidators:
10: FormElementIdentifierWithinCurlyBracesInclusive
9999:
identifier: removeButton
templateName: Inspector-RemoveElementEditor
Expand Down Expand Up @@ -319,6 +334,21 @@ TYPO3:
10:
value: default
label: formEditor.elements.Form.finisher.EmailToReceiver.editor.language.1
1300:
identifier: useFluidEmail
templateName: Inspector-CheckboxEditor
label: formEditor.elements.Form.finisher.EmailToReceiver.editor.useFluidEmail.label
propertyPath: options.useFluidEmail
fieldExplanationText: formEditor.elements.Form.finisher.EmailToReceiver.editor.useFluidEmail.fieldExplanationText
1400:
identifier: title
templateName: Inspector-TextEditor
label: formEditor.elements.Form.finisher.EmailToReceiver.editor.title.label
propertyPath: options.title
fieldExplanationText: formEditor.elements.Form.finisher.EmailToReceiver.editor.title.fieldExplanationText
enableFormelementSelectionButton: true
propertyValidators:
10: FormElementIdentifierWithinCurlyBracesInclusive
9999:
identifier: removeButton
templateName: Inspector-RemoveElementEditor
Expand Down
15 changes: 15 additions & 0 deletions typo3/sysext/form/Configuration/Yaml/Legacy/mixins.yaml
Expand Up @@ -963,6 +963,21 @@ TYPO3:
10:
value: default
label: formEditor.elements.Form.finisher.EmailToSender.editor.language.1
1300:
identifier: useFluidEmail
templateName: Inspector-CheckboxEditor
label: formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.label
propertyPath: options.useFluidEmail
fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.fieldExplanationText
1400:
identifier: title
templateName: Inspector-TextEditor
label: formEditor.elements.Form.finisher.EmailToSender.editor.title.label
propertyPath: options.title
enableFormelementSelectionButton: true
propertyValidators:
10: FormElementIdentifierWithinCurlyBracesInclusive
fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.title.fieldExplanationText
9999:
identifier: removeButton
templateName: Inspector-RemoveElementEditor
Expand Down

0 comments on commit 894e750

Please sign in to comment.