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

Lines changed: 1 addition & 1 deletion
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, [
Lines changed: 220 additions & 0 deletions
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+
}
Lines changed: 19 additions & 0 deletions
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+
}
Lines changed: 63 additions & 0 deletions
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 %}
Lines changed: 20 additions & 0 deletions
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 %}
Lines changed: 3 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy