Skip to content

Commit 49cf6eb

Browse files
committed
[Twig] Add NotificationEmail
1 parent 24faadc commit 49cf6eb

File tree

14 files changed

+2122
-14
lines changed

14 files changed

+2122
-14
lines changed

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@
119119
"egulias/email-validator": "~1.2,>=1.2.8|~2.0",
120120
"symfony/phpunit-bridge": "^3.4.31|^4.3.4|~5.0",
121121
"symfony/security-acl": "~2.8|~3.0",
122-
"phpdocumentor/reflection-docblock": "^3.0|^4.0"
122+
"phpdocumentor/reflection-docblock": "^3.0|^4.0",
123+
"twig/cssinliner-extra": "^2.12",
124+
"twig/inky-extra": "^2.12",
125+
"twig/markdown-extra": "^2.12"
123126
},
124127
"conflict": {
125128
"masterminds/html5": "<2.6",

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: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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 IMPORTANCE_URGENT = 'urgent';
27+
public const IMPORTANCE_HIGH = 'high';
28+
public const IMPORTANCE_MEDIUM = 'medium';
29+
public const IMPORTANCE_LOW = 'low';
30+
31+
private const DEFAULT_HTML_TEMPLATE_PATH = '@email/notification.html.twig';
32+
private const DEFAULT_TEXT_TEMPLATE_PATH = '@email/notification.txt.twig';
33+
34+
private static $htmlTemplatePath = self::DEFAULT_HTML_TEMPLATE_PATH;
35+
private static $textTemplatePath = self::DEFAULT_TEXT_TEMPLATE_PATH;
36+
37+
private $context = [
38+
'importance' => self::IMPORTANCE_LOW,
39+
'content' => '',
40+
'exception' => false,
41+
'action_text' => null,
42+
'action_url' => null,
43+
'markdown' => false,
44+
'raw' => false,
45+
];
46+
47+
public function __construct(Headers $headers = null, AbstractPart $body = null)
48+
{
49+
if (!class_exists(CssInlinerExtension::class)) {
50+
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));
51+
}
52+
53+
if (!class_exists(InkyExtension::class)) {
54+
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));
55+
}
56+
57+
parent::__construct($headers, $body);
58+
}
59+
60+
/**
61+
* @return $this
62+
*/
63+
public function markdown()
64+
{
65+
if (!class_exists(MarkdownExtension::class)) {
66+
throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__));
67+
}
68+
69+
$this->context['markdown'] = true;
70+
71+
return $this;
72+
}
73+
74+
/**
75+
* @return $this
76+
*/
77+
public function content(string $content, bool $raw = false)
78+
{
79+
$this->context['content'] = $content;
80+
$this->context['raw'] = $raw;
81+
82+
return $this;
83+
}
84+
85+
/**
86+
* @return $this
87+
*/
88+
public function action(string $text, string $url)
89+
{
90+
$this->context['action_text'] = $text;
91+
$this->context['action_url'] = $url;
92+
93+
return $this;
94+
}
95+
96+
/**
97+
* @return self
98+
*/
99+
public function importance(string $importance)
100+
{
101+
$this->context['importance'] = $importance;
102+
103+
return $this;
104+
}
105+
106+
/**
107+
* @param \Throwable|FlattenException
108+
*
109+
* @return $this
110+
*/
111+
public function exception($exception)
112+
{
113+
$exceptionAsString = $this->getExceptionAsString($exception);
114+
115+
$this->context['exception'] = true;
116+
$this->attach($exceptionAsString, 'exception.txt', 'text/plain');
117+
$this->importance(self::IMPORTANCE_URGENT);
118+
119+
if (!$this->getSubject()) {
120+
$this->subject($exception->getMessage());
121+
}
122+
123+
return $this;
124+
}
125+
126+
public static function setDefaultTemplates(?string $htmlTemplatePath = null, ?string $textTemplatePath = null)
127+
{
128+
self::$htmlTemplatePath = $htmlTemplatePath ?: self::DEFAULT_HTML_TEMPLATE_PATH;
129+
self::$textTemplatePath = $textTemplatePath ?: self::DEFAULT_TEXT_TEMPLATE_PATH;
130+
}
131+
132+
public function getTextTemplate(): ?string
133+
{
134+
if ($template = parent::getTextTemplate()) {
135+
return $template;
136+
}
137+
138+
return self::$textTemplatePath;
139+
}
140+
141+
public function getHtmlTemplate(): ?string
142+
{
143+
if ($template = parent::getHtmlTemplate()) {
144+
return $template;
145+
}
146+
147+
return self::$htmlTemplatePath;
148+
}
149+
150+
public function getContext(): array
151+
{
152+
return array_merge($this->context, parent::getContext());
153+
}
154+
155+
public function getPreparedHeaders(): Headers
156+
{
157+
$headers = parent::getPreparedHeaders();
158+
159+
$importance = $this->context['importance'] ?? IMPORTANCE_LOW;
160+
$this->priority($this->determinePriority($importance));
161+
$headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject()));
162+
163+
return $headers;
164+
}
165+
166+
private function determinePriority(string $importance): int
167+
{
168+
switch ($importance) {
169+
case self::IMPORTANCE_URGENT:
170+
return self::PRIORITY_HIGHEST;
171+
case self::IMPORTANCE_HIGH:
172+
return self::PRIORITY_HIGH;
173+
case self::IMPORTANCE_MEDIUM:
174+
return self::PRIORITY_NORMAL;
175+
case self::IMPORTANCE_LOW:
176+
return self::PRIORITY_LOW;
177+
default:
178+
return self::PRIORITY_LOWEST;
179+
}
180+
}
181+
182+
private function getExceptionAsString($exception): string
183+
{
184+
if (class_exists(FlattenException::class)) {
185+
$exception = $exception instanceof FlattenException ? $exception : FlattenException::createFromThrowable($exception);
186+
187+
return $exception->getAsString();
188+
}
189+
190+
$message = \get_class($exception);
191+
if ('' != $exception->getMessage()) {
192+
$message .= ': '.$exception->getMessage();
193+
}
194+
195+
$message .= ' in '.$exception->getFile().':'.$exception->getLine()."\n";
196+
$message .= "Stack trace:\n".$exception->getTraceAsString()."\n\n";
197+
198+
return rtrim($message);
199+
}
200+
201+
/**
202+
* @internal
203+
*/
204+
public function __serialize(): array
205+
{
206+
return [$this->context, parent::__serialize()];
207+
}
208+
209+
/**
210+
* @internal
211+
*/
212+
public function __unserialize(array $data): void
213+
{
214+
[$this->context, $parentData] = $data;
215+
216+
parent::__unserialize($parentData);
217+
}
218+
}
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_foundation_emails_2.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