+ *
+ * @final since Symfony 4.4
*/
class CsrfRuntime
{
diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
index 88b75368da203..8937c890e3a99 100644
--- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
@@ -17,12 +17,15 @@
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\Template;
+use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFunction;
/**
* Provides integration of the dump() function with Twig.
*
* @author Nicolas Grekas
+ *
+ * @final since Symfony 4.4
*/
class DumpExtension extends AbstractExtension
{
@@ -35,6 +38,9 @@ public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null)
$this->dumper = $dumper;
}
+ /**
+ * @return TwigFunction[]
+ */
public function getFunctions()
{
return [
@@ -42,6 +48,9 @@ public function getFunctions()
];
}
+ /**
+ * @return TokenParserInterface[]
+ */
public function getTokenParsers()
{
return [new DumpTokenParser()];
@@ -55,7 +64,7 @@ public function getName()
public function dump(Environment $env, $context)
{
if (!$env->isDebug()) {
- return;
+ return null;
}
if (2 === \func_num_args()) {
@@ -72,8 +81,8 @@ public function dump(Environment $env, $context)
unset($vars[0], $vars[1]);
}
- $dump = fopen('php://memory', 'r+b');
- $this->dumper = $this->dumper ?: new HtmlDumper();
+ $dump = fopen('php://memory', 'r+');
+ $this->dumper = $this->dumper ?? new HtmlDumper();
$this->dumper->setCharset($env->getCharset());
foreach ($vars as $value) {
diff --git a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php
index 21f6be4d6ec6d..af7be97c4f9bd 100644
--- a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php
@@ -19,11 +19,15 @@
* ExpressionExtension gives a way to create Expressions from a template.
*
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class ExpressionExtension extends AbstractExtension
{
/**
* {@inheritdoc}
+ *
+ * @return TwigFunction[]
*/
public function getFunctions()
{
diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php
index 909e20d58d690..174a5cc3fe4bb 100644
--- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php
@@ -15,6 +15,7 @@
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormView;
use Twig\Extension\AbstractExtension;
+use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
@@ -24,11 +25,15 @@
*
* @author Fabien Potencier
* @author Bernhard Schussek
+ *
+ * @final since Symfony 4.4
*/
class FormExtension extends AbstractExtension
{
/**
* {@inheritdoc}
+ *
+ * @return TokenParserInterface[]
*/
public function getTokenParsers()
{
@@ -40,6 +45,8 @@ public function getTokenParsers()
/**
* {@inheritdoc}
+ *
+ * @return TwigFunction[]
*/
public function getFunctions()
{
@@ -60,6 +67,8 @@ public function getFunctions()
/**
* {@inheritdoc}
+ *
+ * @return TwigFilter[]
*/
public function getFilters()
{
@@ -71,6 +80,8 @@ public function getFilters()
/**
* {@inheritdoc}
+ *
+ * @return TwigTest[]
*/
public function getTests()
{
@@ -100,7 +111,7 @@ public function getName()
*
* @see ChoiceView::isSelected()
*/
-function twig_is_selected_choice(ChoiceView $choice, $selectedValue)
+function twig_is_selected_choice(ChoiceView $choice, $selectedValue): bool
{
if (\is_array($selectedValue)) {
return \in_array($choice->value, $selectedValue, true);
@@ -112,7 +123,7 @@ function twig_is_selected_choice(ChoiceView $choice, $selectedValue)
/**
* @internal
*/
-function twig_is_root_form(FormView $formView)
+function twig_is_root_form(FormView $formView): bool
{
return null === $formView->parent;
}
diff --git a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php
index e7421b16d72e3..f3c42482f9d1d 100644
--- a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php
@@ -22,6 +22,8 @@
* Twig extension for the Symfony HttpFoundation component.
*
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class HttpFoundationExtension extends AbstractExtension
{
@@ -42,7 +44,7 @@ public function __construct($urlHelper)
throw new \TypeError(sprintf('The first argument must be an instance of "%s" or an instance of "%s".', UrlHelper::class, RequestStack::class));
}
- @trigger_error(sprintf('Passing a "%s" instance as the first argument to the "%s" constructor is deprecated since Symfony 4.3, pass a "%s" instance instead.', RequestStack::class, __CLASS__, UrlHelper::class), E_USER_DEPRECATED);
+ @trigger_error(sprintf('Passing a "%s" instance as the first argument to the "%s" constructor is deprecated since Symfony 4.3, pass a "%s" instance instead.', RequestStack::class, __CLASS__, UrlHelper::class), \E_USER_DEPRECATED);
$requestContext = null;
if (2 === \func_num_args()) {
@@ -57,6 +59,8 @@ public function __construct($urlHelper)
/**
* {@inheritdoc}
+ *
+ * @return TwigFunction[]
*/
public function getFunctions()
{
diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php
index f8b93ada15475..286bc420c66c5 100644
--- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php
@@ -19,9 +19,14 @@
* Provides integration with the HttpKernel component.
*
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class HttpKernelExtension extends AbstractExtension
{
+ /**
+ * @return TwigFunction[]
+ */
public function getFunctions()
{
return [
diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php
index fcd7c24416fbe..edcf8a4dc0e6f 100644
--- a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php
+++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php
@@ -18,6 +18,8 @@
* Provides integration with the HttpKernel component.
*
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class HttpKernelRuntime
{
@@ -40,7 +42,7 @@ public function __construct(FragmentHandler $handler)
*/
public function renderFragment($uri, $options = [])
{
- $strategy = isset($options['strategy']) ? $options['strategy'] : 'inline';
+ $strategy = $options['strategy'] ?? 'inline';
unset($options['strategy']);
return $this->handler->render($uri, $strategy, $options);
diff --git a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php
index e8bc6190cd65a..a6648dc072db1 100644
--- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php
@@ -19,6 +19,8 @@
* LogoutUrlHelper provides generator functions for the logout URL to Twig.
*
* @author Jeremy Mikola
+ *
+ * @final since Symfony 4.4
*/
class LogoutUrlExtension extends AbstractExtension
{
@@ -31,6 +33,8 @@ public function __construct(LogoutUrlGenerator $generator)
/**
* {@inheritdoc}
+ *
+ * @return TwigFunction[]
*/
public function getFunctions()
{
diff --git a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php
index 21214f81196ad..a46f2cdbb8936 100644
--- a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php
@@ -17,6 +17,8 @@
/**
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class ProfilerExtension extends BaseProfilerExtension
{
@@ -31,6 +33,9 @@ public function __construct(Profile $profile, Stopwatch $stopwatch = null)
$this->events = new \SplObjectStorage();
}
+ /**
+ * @return void
+ */
public function enter(Profile $profile)
{
if ($this->stopwatch && $profile->isTemplate()) {
@@ -40,6 +45,9 @@ public function enter(Profile $profile)
parent::enter($profile);
}
+ /**
+ * @return void
+ */
public function leave(Profile $profile)
{
parent::leave($profile);
diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php
index 67fbe8d3910a3..1ba528546d6d2 100644
--- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php
@@ -22,6 +22,8 @@
* Provides integration of the Routing component with Twig.
*
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class RoutingExtension extends AbstractExtension
{
@@ -33,9 +35,9 @@ public function __construct(UrlGeneratorInterface $generator)
}
/**
- * Returns a list of functions to add to the existing list.
+ * {@inheritdoc}
*
- * @return array An array of functions
+ * @return TwigFunction[]
*/
public function getFunctions()
{
@@ -93,7 +95,7 @@ public function getUrl($name, $parameters = [], $schemeRelative = false)
*
* @final
*/
- public function isUrlGenerationSafe(Node $argsNode)
+ public function isUrlGenerationSafe(Node $argsNode): array
{
// support named arguments
$paramsNode = $argsNode->hasNode('parameters') ? $argsNode->getNode('parameters') : (
diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php
index 439c31aad3df2..4acd7bbf9cc72 100644
--- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php
@@ -21,6 +21,8 @@
* SecurityExtension exposes security context features.
*
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class SecurityExtension extends AbstractExtension
{
@@ -50,6 +52,8 @@ public function isGranted($role, $object = null, $field = null)
/**
* {@inheritdoc}
+ *
+ * @return TwigFunction[]
*/
public function getFunctions()
{
diff --git a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php
index 19dfed23e3bcd..f4b9a24ced5cd 100644
--- a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php
@@ -14,11 +14,14 @@
use Symfony\Bridge\Twig\TokenParser\StopwatchTokenParser;
use Symfony\Component\Stopwatch\Stopwatch;
use Twig\Extension\AbstractExtension;
+use Twig\TokenParser\TokenParserInterface;
/**
* Twig extension for the stopwatch helper.
*
* @author Wouter J
+ *
+ * @final since Symfony 4.4
*/
class StopwatchExtension extends AbstractExtension
{
@@ -36,6 +39,9 @@ public function getStopwatch()
return $this->stopwatch;
}
+ /**
+ * @return TokenParserInterface[]
+ */
public function getTokenParsers()
{
return [
diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php
index 9e927ecdf0320..96df106307da7 100644
--- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php
@@ -24,6 +24,10 @@
use Twig\TokenParser\AbstractTokenParser;
use Twig\TwigFilter;
+// Help opcache.preload discover always-needed symbols
+class_exists(TranslatorInterface::class);
+class_exists(TranslatorTrait::class);
+
/**
* Provides integration of the Translation component with Twig.
*
@@ -42,7 +46,7 @@ class TranslationExtension extends AbstractExtension
public function __construct($translator = null, NodeVisitorInterface $translationNodeVisitor = null)
{
if (null !== $translator && !$translator instanceof LegacyTranslatorInterface && !$translator instanceof TranslatorInterface) {
- throw new \TypeError(sprintf('Argument 1 passed to %s() must be an instance of %s, %s given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
+ throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an instance of "%s", "%s" given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
}
$this->translator = $translator;
$this->translationNodeVisitor = $translationNodeVisitor;
@@ -68,6 +72,8 @@ public function getTranslator()
/**
* {@inheritdoc}
+ *
+ * @return TwigFilter[]
*/
public function getFilters()
{
@@ -100,6 +106,8 @@ public function getTokenParsers()
/**
* {@inheritdoc}
+ *
+ * @return NodeVisitorInterface[]
*/
public function getNodeVisitors()
{
diff --git a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php
index 0ca519ee72423..c2c6f8ba8fcf6 100644
--- a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php
@@ -11,9 +11,9 @@
namespace Symfony\Bridge\Twig\Extension;
-use Fig\Link\GenericLinkProvider;
-use Fig\Link\Link;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\WebLink\GenericLinkProvider;
+use Symfony\Component\WebLink\Link;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
@@ -21,6 +21,8 @@
* Twig extension for the Symfony WebLink component.
*
* @author KΓ©vin Dunglas
+ *
+ * @final since Symfony 4.4
*/
class WebLinkExtension extends AbstractExtension
{
@@ -33,6 +35,8 @@ public function __construct(RequestStack $requestStack)
/**
* {@inheritdoc}
+ *
+ * @return TwigFunction[]
*/
public function getFunctions()
{
diff --git a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php
index 85b4f7a4d73cd..b5f3badea88fd 100644
--- a/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php
@@ -21,6 +21,8 @@
* WorkflowExtension.
*
* @author GrΓ©goire Pineau
+ *
+ * @final since Symfony 4.4
*/
class WorkflowExtension extends AbstractExtension
{
@@ -31,6 +33,9 @@ public function __construct(Registry $workflowRegistry)
$this->workflowRegistry = $workflowRegistry;
}
+ /**
+ * @return TwigFunction[]
+ */
public function getFunctions()
{
return [
@@ -112,7 +117,7 @@ public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null)
* Use a string (the place name) to get place metadata
* Use a Transition instance to get transition metadata
*/
- public function getMetadata($subject, string $key, $metadataSubject = null, string $name = null): ?string
+ public function getMetadata($subject, string $key, $metadataSubject = null, string $name = null)
{
return $this
->workflowRegistry
diff --git a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php
index 3284ec5cd3d06..02d19d8ae3d3b 100644
--- a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php
@@ -20,11 +20,15 @@
* Provides integration of the Yaml component with Twig.
*
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class YamlExtension extends AbstractExtension
{
/**
* {@inheritdoc}
+ *
+ * @return TwigFilter[]
*/
public function getFilters()
{
diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php
index 8dc8998a747d5..1e97ce3371d1d 100644
--- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php
+++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php
@@ -154,7 +154,7 @@ protected function loadResourcesFromTheme($cacheKey, &$theme)
{
if (!$theme instanceof Template) {
/* @var Template $theme */
- $theme = $this->environment->loadTemplate($theme);
+ $theme = $this->environment->load($theme)->unwrap();
}
if (null === $this->template) {
diff --git a/src/Symfony/Bridge/Twig/LICENSE b/src/Symfony/Bridge/Twig/LICENSE
index a677f43763ca4..88bf75bb4d6a2 100644
--- a/src/Symfony/Bridge/Twig/LICENSE
+++ b/src/Symfony/Bridge/Twig/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2019 Fabien Potencier
+Copyright (c) 2004-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php
index 2f1a1fb049e7f..166b3c195ff17 100644
--- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php
+++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php
@@ -13,6 +13,7 @@
use League\HTMLToMarkdown\HtmlConverter;
use Symfony\Component\Mime\BodyRendererInterface;
+use Symfony\Component\Mime\Exception\InvalidArgumentException;
use Symfony\Component\Mime\Message;
use Twig\Environment;
@@ -44,7 +45,20 @@ public function render(Message $message): void
return;
}
- $vars = array_merge($this->context, $message->getContext(), [
+ $messageContext = $message->getContext();
+
+ $previousRenderingKey = $messageContext[__CLASS__] ?? null;
+ unset($messageContext[__CLASS__]);
+ $currentRenderingKey = $this->getFingerPrint($message);
+ if ($previousRenderingKey === $currentRenderingKey) {
+ return;
+ }
+
+ if (isset($messageContext['email'])) {
+ throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', \get_class($message)));
+ }
+
+ $vars = array_merge($this->context, $messageContext, [
'email' => new WrappedTemplatedEmail($this->twig, $message),
]);
@@ -60,6 +74,24 @@ public function render(Message $message): void
if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) {
$message->text($this->convertHtmlToText(\is_resource($html) ? stream_get_contents($html) : $html));
}
+ $message->context($message->getContext() + [__CLASS__ => $currentRenderingKey]);
+ }
+
+ private function getFingerPrint(TemplatedEmail $message): string
+ {
+ $messageContext = $message->getContext();
+ unset($messageContext[__CLASS__]);
+
+ $payload = [$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()];
+ try {
+ $serialized = serialize($payload);
+ } catch (\Exception $e) {
+ // Serialization of 'Closure' is not allowed
+ // Happens when context contain a closure, in that case, we assume that context always change.
+ $serialized = random_bytes(8);
+ }
+
+ return md5($serialized);
}
private function convertHtmlToText(string $html): string
@@ -68,6 +100,6 @@ private function convertHtmlToText(string $html): string
return $this->converter->convert($html);
}
- return strip_tags($html);
+ return strip_tags(preg_replace('{<(head|style)\b.*?\1>}is', '', $html));
}
}
diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php
new file mode 100644
index 0000000000000..382928a982da2
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php
@@ -0,0 +1,230 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\Twig\Mime;
+
+use Symfony\Component\ErrorHandler\Exception\FlattenException;
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Part\AbstractPart;
+use Twig\Extra\CssInliner\CssInlinerExtension;
+use Twig\Extra\Inky\InkyExtension;
+use Twig\Extra\Markdown\MarkdownExtension;
+
+/**
+ * @author Fabien Potencier
+ */
+class NotificationEmail extends TemplatedEmail
+{
+ public const IMPORTANCE_URGENT = 'urgent';
+ public const IMPORTANCE_HIGH = 'high';
+ public const IMPORTANCE_MEDIUM = 'medium';
+ public const IMPORTANCE_LOW = 'low';
+
+ private $theme = 'default';
+ private $context = [
+ 'importance' => self::IMPORTANCE_LOW,
+ 'content' => '',
+ 'exception' => false,
+ 'action_text' => null,
+ 'action_url' => null,
+ 'markdown' => false,
+ 'raw' => false,
+ ];
+
+ public function __construct(Headers $headers = null, AbstractPart $body = null)
+ {
+ $missingPackages = [];
+ if (!class_exists(CssInlinerExtension::class)) {
+ $missingPackages['twig/cssinliner-extra'] = 'CSS Inliner';
+ }
+
+ if (!class_exists(InkyExtension::class)) {
+ $missingPackages['twig/inky-extra'] = 'Inky';
+ }
+
+ if ($missingPackages) {
+ throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available; try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages))));
+ }
+
+ parent::__construct($headers, $body);
+ }
+
+ /**
+ * @return $this
+ */
+ public function markdown(string $content)
+ {
+ if (!class_exists(MarkdownExtension::class)) {
+ throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__));
+ }
+
+ $this->context['markdown'] = true;
+
+ return $this->content($content);
+ }
+
+ /**
+ * @return $this
+ */
+ public function content(string $content, bool $raw = false)
+ {
+ $this->context['content'] = $content;
+ $this->context['raw'] = $raw;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function action(string $text, string $url)
+ {
+ $this->context['action_text'] = $text;
+ $this->context['action_url'] = $url;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function importance(string $importance)
+ {
+ $this->context['importance'] = $importance;
+
+ return $this;
+ }
+
+ /**
+ * @param \Throwable|FlattenException $exception
+ *
+ * @return $this
+ */
+ public function exception($exception)
+ {
+ if (!$exception instanceof \Throwable && !$exception instanceof FlattenException) {
+ throw new \LogicException(sprintf('"%s" accepts "%s" or "%s" instances.', __METHOD__, \Throwable::class, FlattenException::class));
+ }
+
+ $exceptionAsString = $this->getExceptionAsString($exception);
+
+ $this->context['exception'] = true;
+ $this->attach($exceptionAsString, 'exception.txt', 'text/plain');
+ $this->importance(self::IMPORTANCE_URGENT);
+
+ if (!$this->getSubject()) {
+ $this->subject($exception->getMessage());
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function theme(string $theme)
+ {
+ $this->theme = $theme;
+
+ return $this;
+ }
+
+ public function getTextTemplate(): ?string
+ {
+ if ($template = parent::getTextTemplate()) {
+ return $template;
+ }
+
+ return '@email/'.$this->theme.'/notification/body.txt.twig';
+ }
+
+ public function getHtmlTemplate(): ?string
+ {
+ if ($template = parent::getHtmlTemplate()) {
+ return $template;
+ }
+
+ return '@email/'.$this->theme.'/notification/body.html.twig';
+ }
+
+ public function getContext(): array
+ {
+ return array_merge($this->context, parent::getContext());
+ }
+
+ public function getPreparedHeaders(): Headers
+ {
+ $headers = parent::getPreparedHeaders();
+
+ $importance = $this->context['importance'] ?? self::IMPORTANCE_LOW;
+ $this->priority($this->determinePriority($importance));
+ $headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject()));
+
+ return $headers;
+ }
+
+ private function determinePriority(string $importance): int
+ {
+ switch ($importance) {
+ case self::IMPORTANCE_URGENT:
+ return self::PRIORITY_HIGHEST;
+ case self::IMPORTANCE_HIGH:
+ return self::PRIORITY_HIGH;
+ case self::IMPORTANCE_MEDIUM:
+ return self::PRIORITY_NORMAL;
+ case self::IMPORTANCE_LOW:
+ default:
+ return self::PRIORITY_LOW;
+ }
+ }
+
+ private function getExceptionAsString($exception): string
+ {
+ if (class_exists(FlattenException::class)) {
+ $exception = $exception instanceof FlattenException ? $exception : FlattenException::createFromThrowable($exception);
+
+ return $exception->getAsString();
+ }
+
+ $message = \get_class($exception);
+ if ('' !== $exception->getMessage()) {
+ $message .= ': '.$exception->getMessage();
+ }
+
+ $message .= ' in '.$exception->getFile().':'.$exception->getLine()."\n";
+ $message .= "Stack trace:\n".$exception->getTraceAsString()."\n\n";
+
+ return rtrim($message);
+ }
+
+ /**
+ * @internal
+ */
+ public function __serialize(): array
+ {
+ return [$this->context, $this->theme, parent::__serialize()];
+ }
+
+ /**
+ * @internal
+ */
+ public function __unserialize(array $data): void
+ {
+ if (3 === \count($data)) {
+ [$this->context, $this->theme, $parentData] = $data;
+ } else {
+ // Backwards compatibility for deserializing data structures that were serialized without the theme
+ [$this->context, $parentData] = $data;
+ }
+
+ parent::__unserialize($parentData);
+ }
+}
diff --git a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php
index afde5f933d30a..f1726914b490b 100644
--- a/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php
+++ b/src/Symfony/Bridge/Twig/Mime/WrappedTemplatedEmail.php
@@ -12,7 +12,6 @@
namespace Symfony\Bridge\Twig\Mime;
use Symfony\Component\Mime\Address;
-use Symfony\Component\Mime\NamedAddress;
use Twig\Environment;
/**
@@ -33,9 +32,7 @@ public function __construct(Environment $twig, TemplatedEmail $message)
public function toName(): string
{
- $to = $this->message->getTo()[0];
-
- return $to instanceof NamedAddress ? $to->getName() : '';
+ return $this->message->getTo()[0]->getName();
}
public function image(string $image, string $contentType = null): string
@@ -63,7 +60,7 @@ public function attach(string $file, string $name = null, string $contentType =
/**
* @return $this
*/
- public function setSubject(string $subject)
+ public function setSubject(string $subject): self
{
$this->message->subject($subject);
@@ -78,7 +75,7 @@ public function getSubject(): ?string
/**
* @return $this
*/
- public function setReturnPath(string $address)
+ public function setReturnPath(string $address): self
{
$this->message->returnPath($address);
@@ -93,15 +90,15 @@ public function getReturnPath(): string
/**
* @return $this
*/
- public function addFrom(string $address, string $name = null)
+ public function addFrom(string $address, string $name = ''): self
{
- $this->message->addFrom($name ? new NamedAddress($address, $name) : new Address($address));
+ $this->message->addFrom(new Address($address, $name));
return $this;
}
/**
- * @return (Address|NamedAddress)[]
+ * @return Address[]
*/
public function getFrom(): array
{
@@ -111,7 +108,7 @@ public function getFrom(): array
/**
* @return $this
*/
- public function addReplyTo(string $address)
+ public function addReplyTo(string $address): self
{
$this->message->addReplyTo($address);
@@ -129,15 +126,15 @@ public function getReplyTo(): array
/**
* @return $this
*/
- public function addTo(string $address, string $name = null)
+ public function addTo(string $address, string $name = ''): self
{
- $this->message->addTo($name ? new NamedAddress($address, $name) : new Address($address));
+ $this->message->addTo(new Address($address, $name));
return $this;
}
/**
- * @return (Address|NamedAddress)[]
+ * @return Address[]
*/
public function getTo(): array
{
@@ -147,15 +144,15 @@ public function getTo(): array
/**
* @return $this
*/
- public function addCc(string $address, string $name = null)
+ public function addCc(string $address, string $name = ''): self
{
- $this->message->addCc($name ? new NamedAddress($address, $name) : new Address($address));
+ $this->message->addCc(new Address($address, $name));
return $this;
}
/**
- * @return (Address|NamedAddress)[]
+ * @return Address[]
*/
public function getCc(): array
{
@@ -165,15 +162,15 @@ public function getCc(): array
/**
* @return $this
*/
- public function addBcc(string $address, string $name = null)
+ public function addBcc(string $address, string $name = ''): self
{
- $this->message->addBcc($name ? new NamedAddress($address, $name) : new Address($address));
+ $this->message->addBcc(new Address($address, $name));
return $this;
}
/**
- * @return (Address|NamedAddress)[]
+ * @return Address[]
*/
public function getBcc(): array
{
@@ -183,9 +180,9 @@ public function getBcc(): array
/**
* @return $this
*/
- public function setPriority(int $priority)
+ public function setPriority(int $priority): self
{
- $this->message->setPriority($priority);
+ $this->message->priority($priority);
return $this;
}
diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php
index c9cf1e1689ee6..d82d9ade1feaf 100644
--- a/src/Symfony/Bridge/Twig/Node/DumpNode.php
+++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php
@@ -16,6 +16,8 @@
/**
* @author Julien Galenski
+ *
+ * @final since Symfony 4.4
*
741A
/
class DumpNode extends Node
{
@@ -33,7 +35,7 @@ public function __construct($varPrefix, Node $values = null, int $lineno, string
}
/**
- * {@inheritdoc}
+ * @return void
*/
public function compile(Compiler $compiler)
{
diff --git a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php
index c99675cab3168..b17243060f302 100644
--- a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php
+++ b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php
@@ -17,6 +17,8 @@
/**
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class FormThemeNode extends Node
{
@@ -25,6 +27,9 @@ public function __construct(Node $form, Node $resources, int $lineno, string $ta
parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag);
}
+ /**
+ * @return void
+ */
public function compile(Compiler $compiler)
{
$compiler
diff --git a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php
index dc7d860793f48..29402a8024fae 100644
--- a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php
+++ b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php
@@ -21,9 +21,14 @@
* is "foo", the block "foo" will be rendered.
*
* @author Bernhard Schussek
+ *
+ * @final since Symfony 4.4
*/
class RenderBlockNode extends FunctionExpression
{
+ /**
+ * @return void
+ */
public function compile(Compiler $compiler)
{
$compiler->addDebugInfo($this);
diff --git a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php
index 612bec14e5329..bf22c329d6a13 100644
--- a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php
+++ b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php
@@ -18,9 +18,14 @@
/**
* @author Bernhard Schussek
+ *
+ * @final since Symfony 4.4
*/
class SearchAndRenderBlockNode extends FunctionExpression
{
+ /**
+ * @return void
+ */
public function compile(Compiler $compiler)
{
$compiler->addDebugInfo($this);
@@ -28,7 +33,6 @@ public function compile(Compiler $compiler)
preg_match('/_([^_]+)$/', $this->getAttribute('name'), $matches);
- $label = null;
$arguments = iterator_to_array($this->getNode('arguments'));
$blockNameSuffix = $matches[1];
@@ -41,7 +45,7 @@ public function compile(Compiler $compiler)
// The "label" function expects the label in the second and
// the variables in the third argument
$label = $arguments[1];
- $variables = isset($arguments[2]) ? $arguments[2] : null;
+ $variables = $arguments[2] ?? null;
$lineno = $label->getTemplateLine();
if ($label instanceof ConstantExpression) {
diff --git a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php
index 3844b2124c38f..b4dd8a9b37b4f 100644
--- a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php
+++ b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php
@@ -19,6 +19,8 @@
* Represents a stopwatch node.
*
* @author Wouter J
+ *
+ * @final since Symfony 4.4
*/
class StopwatchNode extends Node
{
@@ -27,6 +29,9 @@ public function __construct(Node $name, Node $body, AssignNameExpression $var, i
parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag);
}
+ /**
+ * @return void
+ */
public function compile(Compiler $compiler)
{
$compiler
diff --git a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php
index 7f8024aa85640..49ceac1404c5e 100644
--- a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php
+++ b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php
@@ -17,6 +17,8 @@
/**
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class TransDefaultDomainNode extends Node
{
@@ -25,6 +27,9 @@ public function __construct(AbstractExpression $expr, int $lineno = 0, string $t
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}
+ /**
+ * @return void
+ */
public function compile(Compiler $compiler)
{
// noop as this node is just a marker for TranslationDefaultDomainNodeVisitor
diff --git a/src/Symfony/Bridge/Twig/Node/TransNode.php b/src/Symfony/Bridge/Twig/Node/TransNode.php
index cedc6b740e08d..bc87d75bd7db2 100644
--- a/src/Symfony/Bridge/Twig/Node/TransNode.php
+++ b/src/Symfony/Bridge/Twig/Node/TransNode.php
@@ -20,10 +20,12 @@
use Twig\Node\TextNode;
// BC/FC with namespaced Twig
-class_exists('Twig\Node\Expression\ArrayExpression');
+class_exists(ArrayExpression::class);
/**
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class TransNode extends Node
{
@@ -46,6 +48,9 @@ public function __construct(Node $body, Node $domain = null, AbstractExpression
parent::__construct($nodes, [], $lineno, $tag);
}
+ /**
+ * @return void
+ */
public function compile(Compiler $compiler)
{
$compiler->addDebugInfo($this);
@@ -55,9 +60,7 @@ public function compile(Compiler $compiler)
$defaults = $this->getNode('vars');
$vars = null;
}
- list($msg, $defaults) = $this->compileString($this->getNode('body'), $defaults, (bool) $vars);
-
- $method = !$this->hasNode('count') ? 'trans' : 'transChoice';
+ [$msg, $defaults] = $this->compileString($this->getNode('body'), $defaults, (bool) $vars);
$compiler
->write('echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans(')
diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php
index 04b68ef6be199..72badea2d2bd0 100644
--- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php
+++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php
@@ -27,6 +27,8 @@
/**
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
{
@@ -39,6 +41,8 @@ public function __construct()
/**
* {@inheritdoc}
+ *
+ * @return Node
*/
protected function doEnterNode(Node $node, Environment $env)
{
@@ -91,6 +95,8 @@ protected function doEnterNode(Node $node, Environment $env)
/**
* {@inheritdoc}
+ *
+ * @return Node|null
*/
protected function doLeaveNode(Node $node, Environment $env)
{
@@ -107,16 +113,15 @@ protected function doLeaveNode(Node $node, Environment $env)
/**
* {@inheritdoc}
+ *
+ * @return int
*/
public function getPriority()
{
return -10;
}
- /**
- * @return bool
- */
- private function isNamedArguments($arguments)
+ private function isNamedArguments(Node $arguments): bool
{
foreach ($arguments as $name => $node) {
if (!\is_int($name)) {
@@ -127,7 +132,7 @@ private function isNamedArguments($arguments)
return false;
}
- private function getVarName()
+ private function getVarName(): string
{
return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false));
}
diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
index 3da4141cdd2e0..b9b5959bbd766 100644
--- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
+++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php
@@ -22,20 +22,28 @@
* TranslationNodeVisitor extracts translation messages.
*
* @author Fabien Potencier
+ *
+ * @final since Symfony 4.4
*/
class TranslationNodeVisitor extends AbstractNodeVisitor
{
- const UNDEFINED_DOMAIN = '_undefined';
+ public const UNDEFINED_DOMAIN = '_undefined';
private $enabled = false;
private $messages = [];
+ /**
+ * @return void
+ */
public function enable()
{
$this->enabled = true;
$this->messages = [];
}
+ /**
+ * @return void
+ */
public function disable()
{
$this->enabled = false;
@@ -49,6 +57,8 @@ public function getMessages()
/**
* {@inheritdoc}
+ *
+ * @return Node
*/
protected function doEnterNode(Node $node, Environment $env)
{
@@ -89,6 +99,8 @@ protected function doEnterNode(Node $node, Environment $env)
/**
* {@inheritdoc}
+ *
+ * @return Node|null
*/
protected function doLeaveNode(Node $node, Environment $env)
{
@@ -97,6 +109,8 @@ protected function doLeaveNode(Node $node, Environment $env)
/**
* {@inheritdoc}
+ *
+ * @return int
*/
public function getPriority()
{
diff --git a/src/Symfony/Bridge/Twig/README.md b/src/Symfony/Bridge/Twig/README.md
index 602f5a54c3dd6..533d573dbcabe 100644
--- a/src/Symfony/Bridge/Twig/README.md
+++ b/src/Symfony/Bridge/Twig/README.md
@@ -1,13 +1,13 @@
Twig Bridge
===========
-Provides integration for [Twig](https://twig.symfony.com/) with various
-Symfony components.
+The Twig bridge provides integration for [Twig](https://twig.symfony.com/) with
+various Symfony components.
Resources
---------
- * [Contributing](https://symfony.com/doc/current/contributing/index.html)
- * [Report issues](https://github.com/symfony/symfony/issues) and
- [send Pull Requests](https://github.com/symfony/symfony/pulls)
- in the [main Symfony repository](https://github.com/symfony/symfony)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/default/notification/body.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Email/default/notification/body.html.twig
new file mode 100644
index 0000000000000..9027546861a14
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/default/notification/body.html.twig
@@ -0,0 +1 @@
+{% extends "@email/zurb_2/notification/body.html.twig" %}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/default/notification/body.txt.twig b/src/Symfony/Bridge/Twig/Resources/views/Email/default/notification/body.txt.twig
new file mode 100644
index 0000000000000..37671b1f28455
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/default/notification/body.txt.twig
@@ -0,0 +1 @@
+{% extends "@email/zurb_2/notification/body.txt.twig" %}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/main.css b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/main.css
new file mode 100644
index 0000000000000..b826813ec5d76
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/main.css
@@ -0,0 +1,1667 @@
+/*
+ * Copyright (c) 2017 ZURB, inc. -- MIT License
+ *
+ * https://raw.githubusercontent.com/foundation/foundation-emails/v2.2.1/dist/foundation-emails.css
+ */
+
+.wrapper {
+ width: 100%;
+}
+
+#outlook a {
+ padding: 0;
+}
+
+body {
+ width: 100% !important;
+ min-width: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ margin: 0;
+ Margin: 0;
+ padding: 0;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.ExternalClass {
+ width: 100%;
+}
+
+.ExternalClass,
+.ExternalClass p,
+.ExternalClass span,
+.ExternalClass font,
+.ExternalClass td,
+.ExternalClass div {
+ line-height: 100%;
+}
+
+#backgroundTable {
+ margin: 0;
+ Margin: 0;
+ padding: 0;
+ width: 100% !important;
+ line-height: 100% !important;
+}
+
+img {
+ outline: none;
+ text-decoration: none;
+ -ms-interpolation-mode: bicubic;
+ width: auto;
+ max-width: 100%;
+ clear: both;
+ display: block;
+}
+
+center {
+ width: 100%;
+ min-width: 580px;
+}
+
+a img {
+ border: none;
+}
+
+p {
+ margin: 0 0 0 10px;
+ Margin: 0 0 0 10px;
+}
+
+table {
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+
+td {
+ word-wrap: break-word;
+ -webkit-hyphens: auto;
+ -moz-hyphens: auto;
+ hyphens: auto;
+ border-collapse: collapse !important;
+}
+
+table,
+tr,
+td {
+ padding: 0;
+ vertical-align: top;
+ text-align: left;
+}
+
+@media only screen {
+ html {
+ min-height: 100%;
+ background: #f3f3f3;
+ }
+}
+
+table.body {
+ background: #f3f3f3;
+ height: 100%;
+ width: 100%;
+}
+
+table.container {
+ background: #fefefe;
+ width: 580px;
+ margin: 0 auto;
+ Margin: 0 auto;
+ text-align: inherit;
+}
+
+table.row {
+ padding: 0;
+ width: 100%;
+ position: relative;
+}
+
+table.spacer {
+ width: 100%;
+}
+
+table.spacer td {
+ mso-line-height-rule: exactly;
+}
+
+table.container table.row {
+ display: table;
+}
+
+td.columns,
+td.column,
+th.columns,
+th.column {
+ margin: 0 auto;
+ Margin: 0 auto;
+ padding-left: 16px;
+ padding-bottom: 16px;
+}
+
+td.columns .column,
+td.columns .columns,
+td.column .column,
+td.column .columns,
+th.columns .column,
+th.columns .columns,
+th.column .column,
+th.column .columns {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+td.columns .column center,
+td.columns .columns center,
+td.column .column center,
+td.column .columns center,
+th.columns .column center,
+th.columns .columns center,
+th.column .column center,
+th.column .columns center {
+ min-width: none !important;
+}
+
+td.columns.last,
+td.column.last,
+th.columns.last,
+th.column.last {
+ padding-right: 16px;
+}
+
+td.columns table:not(.button),
+td.column table:not(.button),
+th.columns table:not(.button),
+th.column table:not(.button) {
+ width: 100%;
+}
+
+td.large-1,
+th.large-1 {
+ width: 32.33333px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-1.first,
+th.large-1.first {
+ padding-left: 16px;
+}
+
+td.large-1.last,
+th.large-1.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-1,
+.collapse>tbody>tr>th.large-1 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 48.33333px;
+}
+
+.collapse td.large-1.first,
+.collapse th.large-1.first,
+.collapse td.large-1.last,
+.collapse th.large-1.last {
+ width: 56.33333px;
+}
+
+td.large-1 center,
+th.large-1 center {
+ min-width: 0.33333px;
+}
+
+.body .columns td.large-1,
+.body .column td.large-1,
+.body .columns th.large-1,
+.body .column th.large-1 {
+ width: 8.33333%;
+}
+
+td.large-2,
+th.large-2 {
+ width: 80.66667px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-2.first,
+th.large-2.first {
+ padding-left: 16px;
+}
+
+td.large-2.last,
+th.large-2.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-2,
+.collapse>tbody>tr>th.large-2 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 96.66667px;
+}
+
+.collapse td.large-2.first,
+.collapse th.large-2.first,
+.collapse td.large-2.last,
+.collapse th.large-2.last {
+ width: 104.66667px;
+}
+
+td.large-2 center,
+th.large-2 center {
+ min-width: 48.66667px;
+}
+
+.body .columns td.large-2,
+.body .column td.large-2,
+.body .columns th.large-2,
+.body .column th.large-2 {
+ width: 16.66667%;
+}
+
+td.large-3,
+th.large-3 {
+ width: 129px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-3.first,
+th.large-3.first {
+ padding-left: 16px;
+}
+
+td.large-3.last,
+th.large-3.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-3,
+.collapse>tbody>tr>th.large-3 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 145px;
+}
+
+.collapse td.large-3.first,
+.collapse th.large-3.first,
+.collapse td.large-3.last,
+.collapse th.large-3.last {
+ width: 153px;
+}
+
+td.large-3 center,
+th.large-3 center {
+ min-width: 97px;
+}
+
+.body .columns td.large-3,
+.body .column td.large-3,
+.body .columns th.large-3,
+.body .column th.large-3 {
+ width: 25%;
+}
+
+td.large-4,
+th.large-4 {
+ width: 177.33333px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-4.first,
+th.large-4.first {
+ padding-left: 16px;
+}
+
+td.large-4.last,
+th.large-4.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-4,
+.collapse>tbody>tr>th.large-4 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 193.33333px;
+}
+
+.collapse td.large-4.first,
+.collapse th.large-4.first,
+.collapse td.large-4.last,
+.collapse th.large-4.last {
+ width: 201.33333px;
+}
+
+td.large-4 center,
+th.large-4 center {
+ min-width: 145.33333px;
+}
+
+.body .columns td.large-4,
+.body .column td.large-4,
+.body .columns th.large-4,
+.body .column th.large-4 {
+ width: 33.33333%;
+}
+
+td.large-5,
+th.large-5 {
+ width: 225.66667px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-5.first,
+th.large-5.first {
+ padding-left: 16px;
+}
+
+td.large-5.last,
+th.large-5.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-5,
+.collapse>tbody>tr>th.large-5 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 241.66667px;
+}
+
+.collapse td.large-5.first,
+.collapse th.large-5.first,
+.collapse td.large-5.last,
+.collapse th.large-5.last {
+ width: 249.66667px;
+}
+
+td.large-5 center,
+th.large-5 center {
+ min-width: 193.66667px;
+}
+
+.body .columns td.large-5,
+.body .column td.large-5,
+.body .columns th.large-5,
+.body .column th.large-5 {
+ width: 41.66667%;
+}
+
+td.large-6,
+th.large-6 {
+ width: 274px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-6.first,
+th.large-6.first {
+ padding-left: 16px;
+}
+
+td.large-6.last,
+th.large-6.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-6,
+.collapse>tbody>tr>th.large-6 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 290px;
+}
+
+.collapse td.large-6.first,
+.collapse th.large-6.first,
+.collapse td.large-6.last,
+.collapse th.large-6.last {
+ width: 298px;
+}
+
+td.large-6 center,
+th.large-6 center {
+ min-width: 242px;
+}
+
+.body .columns td.large-6,
+.body .column td.large-6,
+.body .columns th.large-6,
+.body .column th.large-6 {
+ width: 50%;
+}
+
+td.large-7,
+th.large-7 {
+ width: 322.33333px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-7.first,
+th.large-7.first {
+ padding-left: 16px;
+}
+
+td.large-7.last,
+th.large-7.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-7,
+.collapse>tbody>tr>th.large-7 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 338.33333px;
+}
+
+.collapse td.large-7.first,
+.collapse th.large-7.first,
+.collapse td.large-7.last,
+.collapse th.large-7.last {
+ width: 346.33333px;
+}
+
+td.large-7 center,
+th.large-7 center {
+ min-width: 290.33333px;
+}
+
+.body .columns td.large-7,
+.body .column td.large-7,
+.body .columns th.large-7,
+.body .column th.large-7 {
+ width: 58.33333%;
+}
+
+td.large-8,
+th.large-8 {
+ width: 370.66667px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-8.first,
+th.large-8.first {
+ padding-left: 16px;
+}
+
+td.large-8.last,
+th.large-8.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-8,
+.collapse>tbody>tr>th.large-8 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 386.66667px;
+}
+
+.collapse td.large-8.first,
+.collapse th.large-8.first,
+.collapse td.large-8.last,
+.collapse th.large-8.last {
+ width: 394.66667px;
+}
+
+td.large-8 center,
+th.large-8 center {
+ min-width: 338.66667px;
+}
+
+.body .columns td.large-8,
+.body .column td.large-8,
+.body .columns th.large-8,
+.body .column th.large-8 {
+ width: 66.66667%;
+}
+
+td.large-9,
+th.large-9 {
+ width: 419px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-9.first,
+th.large-9.first {
+ padding-left: 16px;
+}
+
+td.large-9.last,
+th.large-9.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-9,
+.collapse>tbody>tr>th.large-9 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 435px;
+}
+
+.collapse td.large-9.first,
+.collapse th.large-9.first,
+.collapse td.large-9.last,
+.collapse th.large-9.last {
+ width: 443px;
+}
+
+td.large-9 center,
+th.large-9 center {
+ min-width: 387px;
+}
+
+.body .columns td.large-9,
+.body .column td.large-9,
+.body .columns th.large-9,
+.body .column th.large-9 {
+ width: 75%;
+}
+
+td.large-10,
+th.large-10 {
+ width: 467.33333px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-10.first,
+th.large-10.first {
+ padding-left: 16px;
+}
+
+td.large-10.last,
+th.large-10.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-10,
+.collapse>tbody>tr>th.large-10 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 483.33333px;
+}
+
+.collapse td.large-10.first,
+.collapse th.large-10.first,
+.collapse td.large-10.last,
+.collapse th.large-10.last {
+ width: 491.33333px;
+}
+
+td.large-10 center,
+th.large-10 center {
+ min-width: 435.33333px;
+}
+
+.body .columns td.large-10,
+.body .column td.large-10,
+.body .columns th.large-10,
+.body .column th.large-10 {
+ width: 83.33333%;
+}
+
+td.large-11,
+th.large-11 {
+ width: 515.66667px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-11.first,
+th.large-11.first {
+ padding-left: 16px;
+}
+
+td.large-11.last,
+th.large-11.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-11,
+.collapse>tbody>tr>th.large-11 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 531.66667px;
+}
+
+.collapse td.large-11.first,
+.collapse th.large-11.first,
+.collapse td.large-11.last,
+.collapse th.large-11.last {
+ width: 539.66667px;
+}
+
+td.large-11 center,
+th.large-11 center {
+ min-width: 483.66667px;
+}
+
+.body .columns td.large-11,
+.body .column td.large-11,
+.body .columns th.large-11,
+.body .column th.large-11 {
+ width: 91.66667%;
+}
+
+td.large-12,
+th.large-12 {
+ width: 564px;
+ padding-left: 8px;
+ padding-right: 8px;
+}
+
+td.large-12.first,
+th.large-12.first {
+ padding-left: 16px;
+}
+
+td.large-12.last,
+th.large-12.last {
+ padding-right: 16px;
+}
+
+.collapse>tbody>tr>td.large-12,
+.collapse>tbody>tr>th.large-12 {
+ padding-right: 0;
+ padding-left: 0;
+ width: 580px;
+}
+
+.collapse td.large-12.first,
+.collapse th.large-12.first,
+.collapse td.large-12.last,
+.collapse th.large-12.last {
+ width: 588px;
+}
+
+td.large-12 center,
+th.large-12 center {
+ min-width: 532px;
+}
+
+.body .columns td.large-12,
+.body .column td.large-12,
+.body .columns th.large-12,
+.body .column th.large-12 {
+ width: 100%;
+}
+
+td.large-offset-1,
+td.large-offset-1.first,
+td.large-offset-1.last,
+th.large-offset-1,
+th.large-offset-1.first,
+th.large-offset-1.last {
+ padding-left: 64.33333px;
+}
+
+td.large-offset-2,
+td.large-offset-2.first,
+td.large-offset-2.last,
+th.large-offset-2,
+th.large-offset-2.first,
+th.large-offset-2.last {
+ padding-left: 112.66667px;
+}
+
+td.large-offset-3,
+td.large-offset-3.first,
+td.large-offset-3.last,
+th.large-offset-3,
+th.large-offset-3.first,
+th.large-offset-3.last {
+ padding-left: 161px;
+}
+
+td.large-offset-4,
+td.large-offset-4.first,
+td.large-offset-4.last,
+th.large-offset-4,
+th.large-offset-4.first,
+th.large-offset-4.last {
+ padding-left: 209.33333px;
+}
+
+td.large-offset-5,
+td.large-offset-5.first,
+td.large-offset-5.last,
+th.large-offset-5,
+th.large-offset-5.first,
+th.large-offset-5.last {
+ padding-left: 257.66667px;
+}
+
+td.large-offset-6,
+td.large-offset-6.first,
+td.large-offset-6.last,
+th.large-offset-6,
+th.large-offset-6.first,
+th.large-offset-6.last {
+ padding-left: 306px;
+}
+
+td.large-offset-7,
+td.large-offset-7.first,
+td.large-offset-7.last,
+th.large-offset-7,
+th.large-offset-7.first,
+th.large-offset-7.last {
+ padding-left: 354.33333px;
+}
+
+td.large-offset-8,
+td.large-offset-8.first,
+td.large-offset-8.last,
+th.large-offset-8,
+th.large-offset-8.first,
+th.large-offset-8.last {
+ padding-left: 402.66667px;
+}
+
+td.large-offset-9,
+td.large-offset-9.first,
+td.large-offset-9.last,
+th.large-offset-9,
+th.large-offset-9.first,
+th.large-offset-9.last {
+ padding-left: 451px;
+}
+
+td.large-offset-10,
+td.large-offset-10.first,
+td.large-offset-10.last,
+th.large-offset-10,
+th.large-offset-10.first,
+th.large-offset-10.last {
+ padding-left: 499.33333px;
+}
+
+td.large-offset-11,
+td.large-offset-11.first,
+td.large-offset-11.last,
+th.large-offset-11,
+th.large-offset-11.first,
+th.large-offset-11.last {
+ padding-left: 547.66667px;
+}
+
+td.expander,
+th.expander {
+ visibility: hidden;
+ width: 0;
+ padding: 0 !important;
+}
+
+table.container.radius {
+ border-radius: 0;
+ border-collapse: separate;
+}
+
+.block-grid {
+ width: 100%;
+ max-width: 580px;
+}
+
+.block-grid td {
+ display: inline-block;
+ padding: 8px;
+}
+
+.up-2 td {
+ width: 274px !important;
+}
+
+.up-3 td {
+ width: 177px !important;
+}
+
+.up-4 td {
+ width: 129px !important;
+}
+
+.up-5 td {
+ width: 100px !important;
+}
+
+.up-6 td {
+ width: 80px !important;
+}
+
+.up-7 td {
+ width: 66px !important;
+}
+
+.up-8 td {
+ width: 56px !important;
+}
+
+table.text-center,
+th.text-center,
+td.text-center,
+h1.text-center,
+h2.text-center,
+h3.text-center,
+h4.text-center,
+h5.text-center,
+h6.text-center,
+p.text-center,
+span.text-center {
+ text-align: center;
+}
+
+table.text-left,
+th.text-left,
+td.text-left,
+h1.text-left,
+h2.text-left,
+h3.text-left,
+h4.text-left,
+h5.text-left,
+h6.text-left,
+p.text-left,
+span.text-left {
+ text-align: left;
+}
+
+table.text-right,
+th.text-right,
+td.text-right,
+h1.text-right,
+h2.text-right,
+h3.text-right,
+h4.text-right,
+h5.text-right,
+h6.text-right,
+p.text-right,
+span.text-right {
+ text-align: right;
+}
+
+span.text-center {
+ display: block;
+ width: 100%;
+ text-align: center;
+}
+
+@media only screen and (max-width: 596px) {
+ .small-float-center {
+ margin: 0 auto !important;
+ float: none !important;
+ text-align: center !important;
+ }
+ .small-text-center {
+ text-align: center !important;
+ }
+ .small-text-left {
+ text-align: left !important;
+ }
+ .small-text-right {
+ text-align: right !important;
+ }
+}
+
+img.float-left {
+ float: left;
+ text-align: left;
+}
+
+img.float-right {
+ float: right;
+ text-align: right;
+}
+
+img.float-center,
+img.text-center {
+ margin: 0 auto;
+ Margin: 0 auto;
+ float: none;
+ text-align: center;
+}
+
+table.float-center,
+td.float-center,
+th.float-center {
+ margin: 0 auto;
+ Margin: 0 auto;
+ float: none;
+ text-align: center;
+}
+
+.hide-for-large {
+ display: none !important;
+ mso-hide: all;
+ overflow: hidden;
+ max-height: 0;
+ font-size: 0;
+ width: 0;
+ line-height: 0;
+}
+
+@media only screen and (max-width: 596px) {
+ .hide-for-large {
+ display: block !important;
+ width: auto !important;
+ overflow: visible !important;
+ max-height: none !important;
+ font-size: inherit !important;
+ line-height: inherit !important;
+ }
+}
+
+table.body table.container .hide-for-large * {
+ mso-hide: all;
+}
+
+@media only screen and (max-width: 596px) {
+ table.body table.container .hide-for-large,
+ table.body table.container .row.hide-for-large {
+ display: table !important;
+ width: 100% !important;
+ }
+}
+
+@media only screen and (max-width: 596px) {
+ table.body table.container .callout-inner.hide-for-large {
+ display: table-cell !important;
+ width: 100% !important;
+ }
+}
+
+@media only screen and (max-width: 596px) {
+ table.body table.container .show-for-large {
+ display: none !important;
+ width: 0;
+ mso-hide: all;
+ overflow: hidden;
+ }
+}
+
+body,
+table.body,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+td,
+th,
+a {
+ color: #0a0a0a;
+ font-family: Helvetica, Arial, sans-serif;
+ font-weight: normal;
+ padding: 0;
+ margin: 0;
+ Margin: 0;
+ text-align: left;
+ line-height: 1.3;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ color: inherit;
+ word-wrap: normal;
+ font-family: Helvetica, Arial, sans-serif;
+ font-weight: normal;
+ margin-bottom: 10px;
+ Margin-bottom: 10px;
+}
+
+h1 {
+ font-size: 34px;
+}
+
+h2 {
+ font-size: 30px;
+}
+
+h3 {
+ font-size: 28px;
+}
+
+h4 {
+ font-size: 24px;
+}
+
+h5 {
+ font-size: 20px;
+}
+
+h6 {
+ font-size: 18px;
+}
+
+body,
+table.body,
+p,
+td,
+th {
+ font-size: 16px;
+ line-height: 1.3;
+}
+
+p {
+ margin-bottom: 10px;
+ Margin-bottom: 10px;
+}
+
+p.lead {
+ font-size: 20px;
+ line-height: 1.6;
+}
+
+p.subheader {
+ margin-top: 4px;
+ margin-bottom: 8px;
+ Margin-top: 4px;
+ Margin-bottom: 8px;
+ font-weight: normal;
+ line-height: 1.4;
+ color: #8a8a8a;
+}
+
+small {
+ font-size: 80%;
+ color: #cacaca;
+}
+
+a {
+ color: #2199e8;
+ t
F438
ext-decoration: none;
+}
+
+a:hover {
+ color: #147dc2;
+}
+
+a:active {
+ color: #147dc2;
+}
+
+a:visited {
+ color: #2199e8;
+}
+
+h1 a,
+h1 a:visited,
+h2 a,
+h2 a:visited,
+h3 a,
+h3 a:visited,
+h4 a,
+h4 a:visited,
+h5 a,
+h5 a:visited,
+h6 a,
+h6 a:visited {
+ color: #2199e8;
+}
+
+pre {
+ background: #f3f3f3;
+ margin: 30px 0;
+ Margin: 30px 0;
+}
+
+pre code {
+ color: #cacaca;
+}
+
+pre code span.callout {
+ color: #8a8a8a;
+ font-weight: bold;
+}
+
+pre code span.callout-strong {
+ color: #ff6908;
+ font-weight: bold;
+}
+
+table.hr {
+ width: 100%;
+}
+
+table.hr th {
+ height: 0;
+ max-width: 580px;
+ border-top: 0;
+ border-right: 0;
+ border-bottom: 1px solid #0a0a0a;
+ border-left: 0;
+ margin: 20px auto;
+ Margin: 20px auto;
+ clear: both;
+}
+
+.stat {
+ font-size: 40px;
+ line-height: 1;
+}
+
+p+.stat {
+ margin-top: -16px;
+ Margin-top: -16px;
+}
+
+span.preheader {
+ display: none !important;
+ visibility: hidden;
+ mso-hide: all !important;
+ font-size: 1px;
+ color: #f3f3f3;
+ line-height: 1px;
+ max-height: 0px;
+ max-width: 0px;
+ opacity: 0;
+ overflow: hidden;
+}
+
+table.button {
+ width: auto;
+ margin: 0 0 16px 0;
+ Margin: 0 0 16px 0;
+}
+
+table.button table td {
+ text-align: left;
+ color: #fefefe;
+ background: #2199e8;
+ border: 2px solid #2199e8;
+}
+
+table.button table td a {
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 16px;
+ font-weight: bold;
+ color: #fefefe;
+ text-decoration: none;
+ display: inline-block;
+ padding: 8px 16px 8px 16px;
+ border: 0 solid #2199e8;
+ border-radius: 3px;
+}
+
+table.button.radius table td {
+ border-radius: 3px;
+ border: none;
+}
+
+table.button.rounded table td {
+ border-radius: 500px;
+ border: none;
+}
+
+table.button:hover table tr td a,
+table.button:active table tr td a,
+table.button table tr td a:visited,
+table.button.tiny:hover table tr td a,
+table.button.tiny:active table tr td a,
+table.button.tiny table tr td a:visited,
+table.button.small:hover table tr td a,
+table.button.small:active table tr td a,
+table.button.small table tr td a:visited,
+table.button.large:hover table tr td a,
+table.button.large:active table tr td a,
+table.button.large table tr td a:visited {
+ color: #fefefe;
+}
+
+table.button.tiny table td,
+table.button.tiny table a {
+ padding: 4px 8px 4px 8px;
+}
+
+table.button.tiny table a {
+ font-size: 10px;
+ font-weight: normal;
+}
+
+table.button.small table td,
+table.button.small table a {
+ padding: 5px 10px 5px 10px;
+ font-size: 12px;
+}
+
+table.button.large table a {
+ padding: 10px 20px 10px 20px;
+ font-size: 20px;
+}
+
+table.button.expand,
+table.button.expanded {
+ width: 100% !important;
+}
+
+table.button.expand table,
+table.button.expanded table {
+ width: 100%;
+}
+
+table.button.expand table a,
+table.button.expanded table a {
+ text-align: center;
+ width: 100%;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+table.button.expand center,
+table.button.expanded center {
+ min-width: 0;
+}
+
+table.button:hover table td,
+table.button:visited table td,
+table.button:active table td {
+ background: #147dc2;
+ color: #fefefe;
+}
+
+table.button:hover table a,
+table.button:visited table a,
+table.button:active table a {
+ border: 0 solid #147dc2;
+}
+
+table.button.secondary table td {
+ background: #777777;
+ color: #fefefe;
+ border: 0px solid #777777;
+}
+
+table.button.secondary table a {
+ color: #fefefe;
+ border: 0 solid #777777;
+}
+
+table.button.secondary:hover table td {
+ background: #919191;
+ color: #fefefe;
+}
+
+table.button.secondary:hover table a {
+ border: 0 solid #919191;
+}
+
+table.button.secondary:hover table td a {
+ color: #fefefe;
+}
+
+table.button.secondary:active table td a {
+ color: #fefefe;
+}
+
+table.button.secondary table td a:visited {
+ color: #fefefe;
+}
+
+table.button.success table td {
+ background: #3adb76;
+ border: 0px solid #3adb76;
+}
+
+table.button.success table a {
+ border: 0 solid #3adb76;
+}
+
+table.button.success:hover table td {
+ background: #23bf5d;
+}
+
+table.button.success:hover table a {
+ border: 0 solid #23bf5d;
+}
+
+table.button.alert table td {
+ background: #ec5840;
+ border: 0px solid #ec5840;
+}
+
+table.button.alert table a {
+ border: 0 solid #ec5840;
+}
+
+table.button.alert:hover table td {
+ background: #e23317;
+}
+
+table.button.alert:hover table a {
+ border: 0 solid #e23317;
+}
+
+table.button.warning table td {
+ background: #ffae00;
+ border: 0px solid #ffae00;
+}
+
+table.button.warning table a {
+ border: 0px solid #ffae00;
+}
+
+table.button.warning:hover table td {
+ background: #cc8b00;
+}
+
+table.button.warning:hover table a {
+ border: 0px solid #cc8b00;
+}
+
+table.callout {
+ margin-bottom: 16px;
+ Margin-bottom: 16px;
+}
+
+th.callout-inner {
+ width: 100%;
+ border: 1px solid #cbcbcb;
+ padding: 10px;
+ background: #fefefe;
+}
+
+th.callout-inner.primary {
+ background: #def0fc;
+ border: 1px solid #444444;
+ color: #0a0a0a;
+}
+
+th.callout-inner.secondary {
+ background: #ebebeb;
+ border: 1px solid #444444;
+ color: #0a0a0a;
+}
+
+th.callout-inner.success {
+ background: #e1faea;
+ border: 1px solid #1b9448;
+ color: #fefefe;
+}
+
+th.callout-inner.warning {
+ background: #fff3d9;
+ border: 1px solid #996800;
+ color: #fefefe;
+}
+
+th.callout-inner.alert {
+ background: #fce6e2;
+ border: 1px solid #b42912;
+ color: #fefefe;
+}
+
+.thumbnail {
+ border: solid 4px #fefefe;
+ box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2);
+ display: inline-block;
+ line-height: 0;
+ max-width: 100%;
+ transition: box-shadow 200ms ease-out;
+ border-radius: 3px;
+ margin-bottom: 16px;
+}
+
+.thumbnail:hover,
+.thumbnail:focus {
+ box-shadow: 0 0 6px 1px rgba(33, 153, 232, 0.5);
+}
+
+table.menu {
+ width: 580px;
+}
+
+table.menu td.menu-item,
+table.menu th.menu-item {
+ padding: 10px;
+ padding-right: 10px;
+}
+
+table.menu td.menu-item a,
+table.menu th.menu-item a {
+ color: #2199e8;
+}
+
+table.menu.vertical td.menu-item,
+table.menu.vertical th.menu-item {
+ padding: 10px;
+ padding-right: 0;
+ display: block;
+}
+
+table.menu.vertical td.menu-item a,
+table.menu.vertical th.menu-item a {
+ width: 100%;
+}
+
+table.menu.vertical td.menu-item table.menu.vertical td.menu-item,
+table.menu.vertical td.menu-item table.menu.vertical th.menu-item,
+table.menu.vertical th.menu-item table.menu.vertical td.menu-item,
+table.menu.vertical th.menu-item table.menu.vertical th.menu-item {
+ padding-left: 10px;
+}
+
+table.menu.text-center a {
+ text-align: center;
+}
+
+.menu[align="center"] {
+ width: auto !important;
+}
+
+body.outlook p {
+ display: inline !important;
+}
+
+@media only screen and (max-width: 596px) {
+ table.body img {
+ width: auto;
+ height: auto;
+ }
+ table.body center {
+ min-width: 0 !important;
+ }
+ table.body .container {
+ width: 95% !important;
+ }
+ table.body .columns,
+ table.body .column {
+ height: auto !important;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ padding-left: 16px !important;
+ padding-right: 16px !important;
+ }
+ table.body .columns .column,
+ table.body .columns .columns,
+ table.body .column .column,
+ table.body .column .columns {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ }
+ table.body .collapse .columns,
+ table.body .collapse .column {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ }
+ td.small-1,
+ th.small-1 {
+ display: inline-block !important;
+ width: 8.33333% !important;
+ }
+ td.small-2,
+ th.small-2 {
+ display: inline-block !important;
+ width: 16.66667% !important;
+ }
+ td.small-3,
+ th.small-3 {
+ display: inline-block !important;
+ width: 25% !important;
+ }
+ td.small-4,
+ th.small-4 {
+ display: inline-block !important;
+ width: 33.33333% !important;
+ }
+ td.small-5,
+ th.small-5 {
+ display: inline-block !important;
+ width: 41.66667% !important;
+ }
+ td.small-6,
+ th.small-6 {
+ display: inline-block !important;
+ width: 50% !important;
+ }
+ td.small-7,
+ th.small-7 {
+ display: inline-block !important;
+ width: 58.33333% !important;
+ }
+ td.small-8,
+ th.small-8 {
+ display: inline-block !important;
+ width: 66.66667% !important;
+ }
+ td.small-9,
+ th.small-9 {
+ display: inline-block !important;
+ width: 75% !important;
+ }
+ td.small-10,
+ th.small-10 {
+ display: inline-block !important;
+ width: 83.33333% !important;
+ }
+ td.small-11,
+ th.small-11 {
+ display: inline-block !important;
+ width: 91.66667% !important;
+ }
+ td.small-12,
+ th.small-12 {
+ display: inline-block !important;
+ width: 100% !important;
+ }
+ .columns td.small-12,
+ .column td.small-12,
+ .columns th.small-12,
+ .column th.small-12 {
+ display: block !important;
+ width: 100% !important;
+ }
+ table.body td.small-offset-1,
+ table.body th.small-offset-1 {
+ margin-left: 8.33333% !important;
+ Margin-left: 8.33333% !important;
+ }
+ table.body td.small-offset-2,
+ table.body th.small-offset-2 {
+ margin-left: 16.66667% !important;
+ Margin-left: 16.66667% !important;
+ }
+ table.body td.small-offset-3,
+ table.body th.small-offset-3 {
+ margin-left: 25% !important;
+ Margin-left: 25% !important;
+ }
+ table.body td.small-offset-4,
+ table.body th.small-offset-4 {
+ margin-left: 33.33333% !important;
+ Margin-left: 33.33333% !important;
+ }
+ table.body td.small-offset-5,
+ table.body th.small-offset-5 {
+ margin-left: 41.66667% !important;
+ Margin-left: 41.66667% !important;
+ }
+ table.body td.small-offset-6,
+ table.body th.small-offset-6 {
+ margin-left: 50% !important;
+ Margin-left: 50% !important;
+ }
+ table.body td.small-offset-7,
+ table.body th.small-offset-7 {
+ margin-left: 58.33333% !important;
+ Margin-left: 58.33333% !important;
+ }
+ table.body td.small-offset-8,
+ table.body th.small-offset-8 {
+ margin-left: 66.66667% !important;
+ Margin-left: 66.66667% !important;
+ }
+ table.body td.small-offset-9,
+ table.body th.small-offset-9 {
+ margin-left: 75% !important;
+ Margin-left: 75% !important;
+ }
+ table.body td.small-offset-10,
+ table.body th.small-offset-10 {
+ margin-left: 83.33333% !important;
+ Margin-left: 83.33333% !important;
+ }
+ table.body td.small-offset-11,
+ table.body th.small-offset-11 {
+ margin-left: 91.66667% !important;
+ Margin-left: 91.66667% !important;
+ }
+ table.body table.columns td.expander,
+ table.body table.columns th.expander {
+ display: none !important;
+ }
+ table.body .right-text-pad,
+ table.body .text-pad-right {
+ padding-left: 10px !important;
+ }
+ table.body .left-text-pad,
+ table.body .text-pad-left {
+ padding-right: 10px !important;
+ }
+ table.menu {
+ width: 100% !important;
+ }
+ table.menu td,
+ table.menu th {
+ width: auto !important;
+ display: inline-block !important;
+ }
+ table.menu.vertical td,
+ table.menu.vertical th,
+ table.menu.small-vertical td,
+ table.menu.small-vertical th {
+ display: block !important;
+ }
+ table.menu[align="center"] {
+ width: auto !important;
+ }
+ table.button.small-expand,
+ table.button.small-expanded {
+ width: 100% !important;
+ }
+ table.button.small-expand table,
+ table.button.small-expanded table {
+ width: 100%;
+ }
+ table.button.small-expand table a,
+ table.button.small-expanded table a {
+ text-align: center !important;
+ width: 100% !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ }
+ table.button.small-expand center,
+ table.button.small-expanded center {
+ min-width: 0;
+ }
+}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.html.twig
new file mode 100644
index 0000000000000..2f3a346df5903
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.html.twig
@@ -0,0 +1,65 @@
+{% apply inky_to_html|inline_css %}
+
+
+
+
+
+
+
+
+
+
+
+ {% block lead %}
+ {{ importance|upper }}
+
+ {{ email.subject }}
+
+ {% endblock %}
+
+ {% block content %}
+ {% if markdown %}
+ {{ include('@email/zurb_2/notification/content_markdown.html.twig') }}
+ {% else %}
+ {{ (raw ? content|raw : content)|nl2br }}
+ {% endif %}
+ {% endblock %}
+
+ {% block action %}
+ {% if action_url %}
+
+ {{ action_text }}
+ {% endif %}
+ {% endblock %}
+
+ {% block exception %}
+ {% if exception %}
+
+ Exception stack trace attached.
+ {% endif %}
+ {% endblock %}
+
+
+
+
+
+ {% block footer %}
+
+
+ {% block footer_content %}
+ Notification e-mail sent by Symfony
+ {% endblock %}
+
+
+ {% endblock %}
+
+
+
+
+
+{% endapply %}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.txt.twig b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.txt.twig
new file mode 100644
index 0000000000000..db855829703e4
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/body.txt.twig
@@ -0,0 +1,20 @@
+{% block lead %}
+{{ email.subject }}
+{% endblock %}
+
+{% block content %}
+{{ content }}
+{% endblock %}
+
+{% block action %}
+{% if action_url %}
+{{ action_url }}: {{ action_text }}
+{% endif %}
+{% endblock %}
+
+{% block exception %}
+{% if exception %}
+Exception stack trace attached.
+{{ exception }}
+{% endif %}
+{% endblock %}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/content_markdown.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/content_markdown.html.twig
new file mode 100644
index 0000000000000..120b2caad9623
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/content_markdown.html.twig
@@ -0,0 +1 @@
+{{ content|markdown_to_html }}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/local.css b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/local.css
new file mode 100644
index 0000000000000..2e68dcd3ef37d
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Resources/views/Email/zurb_2/notification/local.css
@@ -0,0 +1,19 @@
+body {
+ background: #f3f3f3;
+}
+
+.wrapper.secondary {
+ background: #f3f3f3;
+}
+
+.container.body_alert {
+ border-top: 8px solid #ec5840;
+}
+
+.container.body_warning {
+ border-top: 8px solid #ffae00;
+}
+
+.container.body_default {
+ border-top: 8px solid #aaaaaa;
+}
diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig
index b082d9236b927..49cd804398b5e 100644
--- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig
+++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig
@@ -27,7 +27,7 @@ col-sm-2
{%- if help is not empty -%}
{%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
{%- endif -%}
-