Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Fusion/Eel capable Flow Form Email Finisher
<?php
namespace Your\Package\Form\Finishers;
use Neos\Eel\EelEvaluatorInterface;
use Neos\Eel\Exception as EelException;
use Neos\Eel\Package as EelPackage;
use Neos\Eel\Utility as EelUtility;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\ResourceManagement\PersistentResource;
use Neos\Flow\ResourceManagement\ResourceManager;
use Neos\Form\Core\Model\FinisherContext;
use Neos\Form\Core\Model\FinisherInterface;
use Neos\Form\Core\Model\FormDefinition;
use Neos\Form\Exception\FinisherException;
use Neos\Fusion\Core\Runtime;
use Neos\SwiftMailer\Message as SwiftMailerMessage;
use Neos\Utility\Arrays;
use Neos\Utility\ObjectAccess;
/**
* A Form Finisher that can send emails
*/
class FusionEmailFinisher implements FinisherInterface
{
/**
* @var array
*/
private $options = [];
/**
* @var array
*/
private $formValues;
/**
* @var Runtime
*/
private $fusionRuntime;
/**
* @var FormDefinition
*/
private $formDefinition;
/**
* @Flow\Inject
* @var ResourceManager
*/
protected $resourceManager;
/**
* @Flow\Inject
* @var EelEvaluatorInterface
*/
protected $eelEvaluator;
/**
* @Flow\InjectConfiguration(package="Neos.Fusion", path="defaultContext")
* @var array
*/
protected $defaultContextSettings;
const FORMAT_PLAINTEXT = 'text/plain';
const FORMAT_HTML = 'text/html';
/**
* @param FinisherContext $finisherContext
* @throws FinisherException
*/
public function execute(FinisherContext $finisherContext): void
{
$renderingOptions = $finisherContext->getFormRuntime()->getRenderingOptions();
if (!isset($renderingOptions['_fusionRuntime'])) {
throw new \RuntimeException('Missing Fusion runtime. This finisher only works within Neos', 1534176825);
}
$this->fusionRuntime = $renderingOptions['_fusionRuntime'];
$this->formValues = $finisherContext->getFormValues();
$this->formDefinition = $finisherContext->getFormRuntime()->getFormDefinition();
$format = isset($this->options['isHtml']) && $this->options['isHtml'] === true ? self::FORMAT_HTML : self::FORMAT_PLAINTEXT;
$emailMessage = new SwiftMailerMessage();
$emailMessage
->setTo($this->resolveRecipient())
->setFrom($this->resolveSender())
->setSubject($this->renderSubject())
->setBody($this->renderBody(), $format);
if (isset($this->options['replyTo']) && !empty($this->options['replyTo'])) {
$emailMessage->setReplyTo($this->evaluateEelExpression($this->options['replyTo']));
}
$this->addAttachments($emailMessage);
$numberOfSentEmails = $emailMessage->send();
if ($numberOfSentEmails < 1) {
throw new FinisherException('Failed to send Email', 1540825058);
}
}
private function resolveRecipient(): string
{
if (empty($this->options['recipient'])) {
throw new \RuntimeException('Missing "sender" option', 1531131052);
}
return $this->evaluateEelExpression($this->options['recipient']);
}
private function resolveSender(): string
{
if (empty($this->options['sender'])) {
throw new \RuntimeException('Missing "sender" option', 1531131053);
}
return $this->evaluateEelExpression($this->options['sender']);
}
private function renderSubject(): string
{
if (empty($this->options['subject'])) {
throw new \RuntimeException('Missing "subject" option', 1531129369);
}
return $this->evaluateEelExpression($this->options['subject']);
}
private function renderBody(): string
{
$props = [
'formValues' => $this->formValues,
];
if (!empty($this->options['additionalBodyVariables'])) {
$props = Arrays::arrayMergeRecursiveOverrule($props, $this->options['additionalBodyVariables']);
}
$fusionContext = $this->fusionRuntime->getCurrentContext();
$fusionContext = Arrays::arrayMergeRecursiveOverrule($fusionContext, [
'overrideProps' => $props,
]);
$this->fusionRuntime->pushContextArray($fusionContext);
try {
$result = $this->fusionRuntime->render(sprintf('/<%s>', $this->options['bodyFusionPrototypeName']));
} catch (\Exception $exception) {
throw new \RuntimeException('Could not render Fusion template for email body', 1534224704, $exception);
}
$this->fusionRuntime->popContext();
return $result;
}
private function evaluateEelExpression(string $expression, array $variables = []): string
{
if (preg_match(EelPackage::EelExpressionRecognizer, $expression) === 0) {
return $expression;
}
$variables = Arrays::arrayMergeRecursiveOverrule(['formValues' => $this->formValues], $variables);
try {
return (string)EelUtility::evaluateEelExpression($expression, $this->eelEvaluator, $variables, $this->defaultContextSettings);
} catch (EelException $exception) {
throw new \RuntimeException(sprintf('The following Eel expression could not be evaluated: %s', $expression), 1534224056, $exception);
}
}
private function addAttachments(SwiftMailerMessage $emailMessage): void
{
if (!isset($this->options['attachments'])) {
return;
}
$addAttachmentClosure = function ($resource) use ($emailMessage) {
if (!$resource instanceof PersistentResource) {
return;
}
$emailMessage->attach(\Swift_Attachment::newInstance(stream_get_contents($resource->getStream()), $resource->getFilename(), $resource->getMediaType()));
$this->resourceManager->deleteResource($resource);
};
foreach ($this->options['attachments'] as $formValuePropertyPath) {
$formValue = ObjectAccess::getPropertyPath($this->formValues, $formValuePropertyPath);
if ($formValue === null) {
continue;
}
if (is_array($formValue)) {
array_map($addAttachmentClosure, $formValue);
} elseif ($formValue instanceof PersistentResource) {
call_user_func($addAttachmentClosure, $formValue);
}
}
}
public function setOptions(array $options): void
{
$this->options = $options;
}
public function setOption($optionName, $optionValue): void
{
$this->options[$optionName] = $optionValue;
}
}
@bwaidelich

This comment has been minimized.

Copy link
Owner Author

@bwaidelich bwaidelich commented Aug 6, 2020

Usage:

        // ...
        autoReplyFinisher = Your.Package:Form.Finisher.FusionEmailFinisher.Definition {
            options {
                recipient = '${formValues.email}'
                sender = ${Configuration.setting('Your.Package.forms.sender')}
                subject = 'Some subject'

                bodyFusionPrototypeName = 'Your.Package:Some.EmailBody.Component'
                additionalBodyVariables {
                    foo = ${barFromContext}
                }
                isHtml = true
            }
        }

And

prototype(Your.Package:Some.EmailBody.Component) < prototype(Neos.Fusion:Component) {

    foo = ''
    formValues = Neos.Fusion:DataStructure

    renderer = afx`
          <h1>Hello {props.formValues.name}</h1>
          <p>{props.foo}</p>
    `
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.