Skip to content

Commit 8ac1213

Browse files
committed
[Twig] Add NotificationEmail
1 parent 24faadc commit 8ac1213

File tree

10 files changed

+2011
-3
lines changed

10 files changed

+2011
-3
lines changed

src/Symfony/Bridge/Twig/Mime/BodyRenderer.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function render(Message $message): void
4747

4848
$messageContext = $message->getContext();
4949
if (isset($messageContext['email'])) {
50-
throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', TemplatedEmail::class));
50+
throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', \get_class($message)));
5151
}
5252

5353
$vars = array_merge($this->context, $messageContext, [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Twig\Mime;
13+
14+
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
15+
use Symfony\Component\Mime\Header\Headers;
16+
use Symfony\Component\Mime\Part\AbstractPart;
17+
use Twig\Extra\CssInliner\CssInlinerExtension;
18+
use Twig\Extra\Inky\InkyExtension;
19+
use Twig\Extra\Markdown\MarkdownExtension;
20+
21+
/**
22+
* @author Fabien Potencier <fabien@symfony.com>
23+
*/
24+
class NotificationEmail extends TemplatedEmail
25+
{
26+
public const URGENT = 'urgent';
27+
public const HIGH = 'high';
28+
public const MEDIUM = 'medium';
29+
public const LOW = 'low';
30+
31+
private static $htmlTemplatePath = '@email/notification.html.twig';
32+
private static $textTemplatePath = '@email/notification.txt.twig';
33+
34+
private $context = [
35+
'importance' => 'low',
36+
'content' => '',
37+
'exception' => false,
38+
'action_url' => null,
39+
'action_text' => null,
40+
'markdown' => false,
41+
'raw' => false,
42+
];
43+
44+
public function __construct(Headers $headers = null, AbstractPart $body = null)
45+
{
46+
if (!class_exists(CssInlinerExtension::class)) {
47+
throw new \LogicException(sprintf('You cannot use "%s" if the CSS Inliner Twig extension is not available; try running "composer require twig/cssinliner-extra".', static::class));
48+
}
49+
50+
if (!class_exists(InkyExtension::class)) {
51+
throw new \LogicException(sprintf('You cannot use "%s" if the Inky Twig extension is not available; try running "composer require twig/inky-extra".', static::class));
52+
}
53+
54+
parent::__construct($headers, $body);
55+
}
56+
57+
/**
58+
* @return $this
59+
*/
60+
public function markdown()
61+
{
62+
if (!class_exists(MarkdownExtension::class)) {
63+
throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__));
64+
}
65+
66+
$this->context['markdown'] = true;
67+
68+
return $this;
69+
}
70+
71+
/**
72+
* @return $this
73+
*/
74+
public function content(string $content, bool $raw = false)
75+
{
76+
$this->context['content'] = $content;
77+
$this->context['raw'] = $raw;
78+
79+
return $this;
80+
}
81+
82+
/**
83+
* @return $this
84+
*/
85+
public function action(string $text, string $url)
86+
{
87+
$this->context['action_text'] = $text;
88+
$this->context['action_url'] = $url;
89+
90+
return $this;
91+
}
92+
93+
/**
94+
* @return self
95+
*/
96+
public function importance(string $importance)
97+
{
98+
$this->context['importance'] = $importance;
99+
100+
return $this;
101+
}
102+
103+
/**
104+
* @param \Throwable|FlattenException
105+
*
106+
* @return $this
107+
*/
108+
public function exception($exception)
109+
{
110+
$exceptionAsString = $this->getExceptionAsString($exception);
111+
112+
$this->context['exception'] = true;
113+
$this->attach($exceptionAsString, 'exception.txt', 'text/plain');
114+
$this->importance(self::URGENT);
115+
116+
if (!$this->getSubject()) {
117+
$this->subject($exception->getMessage());
118+
}
119+
120+
return $this;
121+
}
122+
123+
public static function setDefaultTemplates(string $htmlTemplatePath = null, string $textTemplatePath = null)
124+
{
125+
if ($htmlTemplatePath) {
126+
self::$htmlTemplatePath = $htmlTemplatePath;
127+
}
128+
129+
if ($textTemplatePath) {
130+
self::$textTemplatePath = $textTemplatePath;
131+
}
132+
}
133+
134+
public function getTextTemplate(): ?string
135+
{
136+
if ($template = parent::getTextTemplate()) {
137+
return $template;
138+
}
139+
140+
return self::$textTemplatePath;
141+
}
142+
143+
public function getHtmlTemplate(): ?string
144+
{
145+
if ($template = parent::getHtmlTemplate()) {
146+
return $template;
147+
}
148+
149+
return self::$htmlTemplatePath;
150+
}
151+
152+
public function getContext(): array
153+
{
154+
return array_merge($this->context, parent::getContext());
155+
}
156+
157+
public function getPreparedHeaders(): Headers
158+
{
159+
$headers = parent::getPreparedHeaders();
160+
161+
$importance = $this->context['importance'] ?? self::LOW;
162+
$this->priority($this->determinePriority($importance));
163+
$headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject()));
164+
165+
return $headers;
166+
}
167+
168+
private function determinePriority(string $importance): int
169+
{
170+
switch ($importance) {
171+
case self::URGENT:
172+
return self::PRIORITY_HIGHEST;
173+
case self::HIGH:
174+
return self::PRIORITY_HIGH;
175+
case self::MEDIUM:
176+
return self::PRIORITY_NORMAL;
177+
case self::LOW:
178+
return self::PRIORITY_LOW;
179+
default:
180+
return self::PRIORITY_LOWEST;
181+
}
182+
}
183+
184+
private function getExceptionAsString($exception): string
185+
{
186+
if (class_exists(FlattenException::class)) {
187+
$exception = $exception instanceof FlattenException ? $exception : FlattenException::createFromThrowable($exception);
188+
189+
return $exception->getAsString();
190+
}
191+
192+
$message = \get_class($exception);
193+
if ('' != $exception->getMessage()) {
194+
$message .= ': '.$exception->getMessage();
195+
}
196+
197+
$message .= ' in '.$exception->getFile().':'.$exception->getLine()."\n";
198+
$message .= "Stack trace:\n".$exception->getTraceAsString()."\n\n";
199+
200+
return rtrim($message);
201+
}
202+
203+
/**
204+
* @internal
205+
*/
206+
public function __serialize(): array
207+
{
208+
return [$this->context, parent::__serialize()];
209+
}
210+
211+
/**
212+
* @internal
213+
*/
214+
public function __unserialize(array $data): void
215+
{
216+
[$this->context, $parentData] = $data;
217+
218+
parent::__unserialize($parentData);
219+
}
220+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
body {
2+
background: #f3f3f3;
3+
}
4+
5+
.wrapper.secondary {
6+
background: #f3f3f3;
7+
}
8+
9+
.container.body_alert {
10+
border-top: 8px solid #ec5840;
11+
}
12+
13+
.container.body_warning {
14+
border-top: 8px solid #ffae00;
15+
}
16+
17+
.container.body_default {
18+
border-top: 8px solid #aaaaaa;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{% filter inky_to_html|inline_css %}
2+
<html>
3+
<head>
4+
<style>
5+
{% block style %}
6+
{{ source("@email/zurb.css") }}
7+
{{ source("@email/notification.css") }}
8+
{% endblock %}
9+
</style>
10+
</head>
11+
<body>
12+
<spacer size="32"></spacer>
13+
<container class="body_{{ ("urgent" == importance ? "alert" : ("high" == importance ? "warning" : "default")) }}">
14+
<spacer size="16"></spacer>
15+
<row>
16+
<columns large="12" small="12">
17+
{% block lead %}
18+
<small><strong>{{ importance|upper }}</strong></small>
19+
<p class="lead">
20+
{{ email.subject }}
21+
</p>
22+
{% endblock %}
23+
24+
{% block content %}
25+
{% if markdown %}
26+
{{ include('@email/notification_body_markdown.html.twig') }}
27+
{% else %}
28+
{{ (raw ? content|raw : content)|nl2br }}
29+
{% endif %}
30+
{% endblock %}
31+
32+
{% block action %}
33+
{% if action_url %}
34+
<spacer size="16"></spacer>
35+
<button href="{{ action_url }}">{{ action_text }}</button>
36+
{% endif %}
37+
{% endblock %}
38+
39+
{% block exception %}
40+
{% if exception %}
41+
<spacer size="16"></spacer>
42+
<p><em>Exception stack trace attached.</em></p>
43+
{% endif %}
44+
{% endblock %}
45+
</columns>
46+
</row>
47+
48+
<wrapper class="secondary">
49+
<spacer size="16"></spacer>
50+
{% block footer %}
51+
<row>
52+
<columns small="12" large="6">
53+
{% block footer_content %}
54+
<p><small>Notification e-mail sent by Symfony</small></p>
55+
{% endblock %}
56+
</columns>
57+
</row>
58+
{% endblock %}
59+
</wrapper>
60+
</container>
61+
</body>
62+
</html>
63+
{% endfilter %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{% block lead %}
2+
{{ email.subject }}
3+
{% endblock %}
4+
5+
{% block content %}
6+
{{ content }}
7+
{% endblock %}
8+
9+
{% block action %}
10+
{% if action_url %}
11+
{{ action_url }}: {{ action_text }}
12+
{% endif %}
13+
{% endblock %}
14+
15+
{% block exception %}
16+
{% if exception %}
17+
Exception stack trace attached.
18+
{{ exception }}
19+
{% endif %}
20+
{% endblock %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% filter markdown_to_html %}
2+
{{ raw ? content|raw : content }}
3+
{% endfilter %}

0 commit comments

Comments
 (0)