251 lines
9.4 KiB
PHP
251 lines
9.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Core\Mail;
|
|
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use Symfony\Component\Mime\Email;
|
|
use Symfony\Component\Mime\Header\Headers;
|
|
use Symfony\Component\Mime\Part\AbstractPart;
|
|
use TYPO3\CMS\Core\Http\NormalizedParams;
|
|
use TYPO3\CMS\Core\Information\Typo3Information;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Core\View\ViewFactoryData;
|
|
use TYPO3\CMS\Core\View\ViewFactoryInterface;
|
|
use TYPO3\CMS\Fluid\View\FluidViewAdapter;
|
|
use TYPO3\CMS\Fluid\View\TemplatePaths;
|
|
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperVariableContainer;
|
|
|
|
/**
|
|
* Send out templated HTML/plain text emails with Fluid.
|
|
*
|
|
* @todo: This construct needs an overhaul because it violates "Composition over inheritance".
|
|
* This is obvious when looking at __construct() already. FluidEmail extends symfony Email
|
|
* (which is a symfony Message), and thus has to deal with things it shouldn't at this point.
|
|
* The repeated calls to $this->resetBody() are the proof something is really wrong here.
|
|
* At first glance it looks as if something like this should happen: The class responsibility
|
|
* should be rendering of the subject and body only. It should probably be created using a factory,
|
|
* returning an instance of some interface, with the factory interface being injected to consumers.
|
|
* This would allow rendering emails with some different template engine, by injecting a different
|
|
* factory interface implementation that returns some different class. With subject and body being
|
|
* created by this, a symfony email should be created (maybe with a facade or factory again). And
|
|
* then hand the mail over to something that can send it.
|
|
* Working on this may go along with a renaming of the class structure, and we may want to
|
|
* ensure abstraction does not explode too much along the way ...
|
|
*/
|
|
class FluidEmail extends Email
|
|
{
|
|
public const FORMAT_HTML = 'html';
|
|
public const FORMAT_PLAIN = 'plain';
|
|
public const FORMAT_BOTH = 'both';
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
protected array $format = ['html', 'plain'];
|
|
protected string $templateName = 'Default';
|
|
protected FluidViewAdapter $view;
|
|
|
|
/**
|
|
* @internal use TemplatedEmailFactory instead
|
|
*/
|
|
public function __construct(?TemplatePaths $templatePaths = null, ?Headers $headers = null, ?AbstractPart $body = null)
|
|
{
|
|
parent::__construct($headers, $body);
|
|
$viewFactory = GeneralUtility::makeInstance(ViewFactoryInterface::class);
|
|
$view = $viewFactory->create(new ViewFactoryData());
|
|
if (!$view instanceof FluidViewAdapter) {
|
|
throw new \RuntimeException(
|
|
'Class FluidEmail can only deal with Fluid views via FluidViewAdapter',
|
|
1724686399
|
|
);
|
|
}
|
|
$this->view = $view;
|
|
// @todo: This is where the problem starts: TemplatePaths() is hardcoded fluid, and part of the
|
|
// current FluidEmail API. We can not put this into ViewFactoryData() directly. While
|
|
// we *could* unpack the paths and format to an array again, we should probably better
|
|
// redesign this implementation and work on the main comment above along the way.
|
|
// Also note methods like getViewHelperVariableContainer() are hard-bound to fluid, too.
|
|
if ($templatePaths === null) {
|
|
$templatePaths = new TemplatePaths();
|
|
$templatePaths->setTemplateRootPaths($GLOBALS['TYPO3_CONF_VARS']['MAIL']['templateRootPaths'] ?? []);
|
|
$templatePaths->setLayoutRootPaths($GLOBALS['TYPO3_CONF_VARS']['MAIL']['layoutRootPaths'] ?? []);
|
|
$templatePaths->setPartialRootPaths($GLOBALS['TYPO3_CONF_VARS']['MAIL']['partialRootPaths'] ?? []);
|
|
}
|
|
|
|
$this->view->getRenderingContext()->setTemplatePaths($templatePaths);
|
|
$this->view->assignMultiple($this->getDefaultVariables());
|
|
$this->format($GLOBALS['TYPO3_CONF_VARS']['MAIL']['format'] ?? self::FORMAT_BOTH);
|
|
}
|
|
|
|
public function format(string $format): static
|
|
{
|
|
$this->format = match ($format) {
|
|
self::FORMAT_BOTH => [self::FORMAT_HTML, self::FORMAT_PLAIN],
|
|
self::FORMAT_HTML => [self::FORMAT_HTML],
|
|
self::FORMAT_PLAIN => [self::FORMAT_PLAIN],
|
|
default => throw new \InvalidArgumentException('Setting FluidEmail->format() must be either "html", "plain" or "both", no other formats are currently supported', 1580743847),
|
|
};
|
|
$this->resetBody();
|
|
return $this;
|
|
}
|
|
|
|
public function setTemplate(string $templateName): static
|
|
{
|
|
$this->templateName = $templateName;
|
|
$this->resetBody();
|
|
return $this;
|
|
}
|
|
|
|
public function assign($key, $value): static
|
|
{
|
|
$this->view->assign($key, $value);
|
|
$this->resetBody();
|
|
return $this;
|
|
}
|
|
|
|
public function assignMultiple(array $values): static
|
|
{
|
|
$this->view->assignMultiple($values);
|
|
$this->resetBody();
|
|
return $this;
|
|
}
|
|
|
|
/*
|
|
* Shorthand setters
|
|
*/
|
|
public function setRequest(ServerRequestInterface $request): static
|
|
{
|
|
$this->view->getRenderingContext()->setAttribute(ServerRequestInterface::class, $request);
|
|
$this->view->assign('request', $request);
|
|
if ($request->getAttribute('normalizedParams') instanceof NormalizedParams) {
|
|
$this->view->assign('normalizedParams', $request->getAttribute('normalizedParams'));
|
|
} else {
|
|
$this->view->assign('normalizedParams', NormalizedParams::createFromServerParams($_SERVER));
|
|
}
|
|
$this->resetBody();
|
|
return $this;
|
|
}
|
|
|
|
protected function getDefaultVariables(): array
|
|
{
|
|
return [
|
|
'typo3' => [
|
|
'sitename' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
|
|
'formats' => [
|
|
'date' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'],
|
|
'time' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
|
|
],
|
|
'systemConfiguration' => $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'],
|
|
'information' => GeneralUtility::makeInstance(Typo3Information::class),
|
|
],
|
|
];
|
|
}
|
|
|
|
public function ensureValidity(): void
|
|
{
|
|
$this->generateTemplatedBody();
|
|
parent::ensureValidity();
|
|
}
|
|
|
|
public function getBody(): AbstractPart
|
|
{
|
|
$this->generateTemplatedBody();
|
|
return parent::getBody();
|
|
}
|
|
|
|
/**
|
|
* @return resource|string|null
|
|
*/
|
|
public function getHtmlBody(bool $forceBodyGeneration = false)
|
|
{
|
|
if ($forceBodyGeneration) {
|
|
$this->generateTemplatedBody('html');
|
|
} elseif (parent::getHtmlBody() === null) {
|
|
$this->generateTemplatedBody();
|
|
}
|
|
return parent::getHtmlBody();
|
|
}
|
|
|
|
/**
|
|
* @return resource|string|null
|
|
*/
|
|
public function getTextBody(bool $forceBodyGeneration = false)
|
|
{
|
|
if ($forceBodyGeneration) {
|
|
$this->generateTemplatedBody('plain');
|
|
} elseif (parent::getTextBody() === null) {
|
|
$this->generateTemplatedBody();
|
|
}
|
|
return parent::getTextBody();
|
|
}
|
|
|
|
/**
|
|
* @internal Only used for ext:form, not part of TYPO3 Core API.
|
|
*/
|
|
public function getViewHelperVariableContainer(): ViewHelperVariableContainer
|
|
{
|
|
// the variables are possibly modified in ext:form, so content must be rendered
|
|
$this->resetBody();
|
|
return $this->view->getRenderingContext()->getViewHelperVariableContainer();
|
|
}
|
|
|
|
protected function generateTemplatedBody(string $forceFormat = ''): void
|
|
{
|
|
// Use a local variable to allow forcing a specific format
|
|
$format = $forceFormat ? [$forceFormat] : $this->format;
|
|
|
|
$tryToRenderSubjectSection = false;
|
|
if (in_array(static::FORMAT_HTML, $format, true) && ($forceFormat || parent::getHtmlBody() === null)) {
|
|
$this->html($this->renderContent('html'));
|
|
$tryToRenderSubjectSection = true;
|
|
}
|
|
if (in_array(static::FORMAT_PLAIN, $format, true) && ($forceFormat || parent::getTextBody() === null)) {
|
|
$this->text(trim($this->renderContent('txt')));
|
|
$tryToRenderSubjectSection = true;
|
|
}
|
|
|
|
if ($tryToRenderSubjectSection) {
|
|
$subjectFromTemplate = $this->view->renderSection(
|
|
'Subject',
|
|
$this->view->getRenderingContext()->getVariableProvider()->getAll(),
|
|
true
|
|
);
|
|
if (!empty($subjectFromTemplate)) {
|
|
$this->subject($subjectFromTemplate);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function renderContent(string $format): string
|
|
{
|
|
$this->view->getRenderingContext()->getTemplatePaths()->setFormat($format);
|
|
return $this->view->render($this->templateName);
|
|
}
|
|
|
|
public function getView(): FluidViewAdapter
|
|
{
|
|
return $this->view;
|
|
}
|
|
|
|
protected function resetBody(): void
|
|
{
|
|
$this->html(null);
|
|
$this->text(null);
|
|
}
|
|
}
|