From 48122f154c74f162c9480c4ffc77c259a2a03479 Mon Sep 17 00:00:00 2001 From: Mathieu Lemoine Date: Thu, 17 Mar 2016 13:58:58 -0400 Subject: [PATCH] Initial commit --- DependencyInjection/Configuration.php | 17 +++ .../WMCSwiftmailerTwigExtension.php | 31 ++++ LICENSE | 21 +++ Mailer/FOSUserMailer.php | 63 ++++++++ Mailer/TwigSwiftHelper.php | 112 ++++++++++++++ README.md | 141 ++++++++++++++++++ Resources/config/services.xml | 28 ++++ WMCSwiftmailerTwigBundle.php | 17 +++ composer.json | 36 +++++ 9 files changed, 466 insertions(+) create mode 100644 DependencyInjection/Configuration.php create mode 100644 DependencyInjection/WMCSwiftmailerTwigExtension.php create mode 100644 LICENSE create mode 100644 Mailer/FOSUserMailer.php create mode 100644 Mailer/TwigSwiftHelper.php create mode 100644 README.md create mode 100644 Resources/config/services.xml create mode 100644 WMCSwiftmailerTwigBundle.php create mode 100644 composer.json diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php new file mode 100644 index 0000000..20b88f8 --- /dev/null +++ b/DependencyInjection/Configuration.php @@ -0,0 +1,17 @@ +root('wmc_swiftmailer_twig'); + + return $treeBuilder; + } +} diff --git a/DependencyInjection/WMCSwiftmailerTwigExtension.php b/DependencyInjection/WMCSwiftmailerTwigExtension.php new file mode 100644 index 0000000..9fe4e79 --- /dev/null +++ b/DependencyInjection/WMCSwiftmailerTwigExtension.php @@ -0,0 +1,31 @@ +load('services.xml'); + } + + /** + * {@inheritDoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasExtension('fos_user')) { + $container->removeDefinition('wmc.swiftmailer_twig.fosub'); + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd385c6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 WeMakeCustom + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Mailer/FOSUserMailer.php b/Mailer/FOSUserMailer.php new file mode 100644 index 0000000..da4b221 --- /dev/null +++ b/Mailer/FOSUserMailer.php @@ -0,0 +1,63 @@ +mailer = $mailer; + $this->router = $router; + $this->helper = $helper; + $this->parameters = $parameters; + } + + public function sendConfirmationEmailMessage(UserInterface $user) + { + $template = $this->parameters['template']['confirmation']; + $url = $this->router->generate('fos_user_registration_confirm', array('token' => $user->getConfirmationToken()), true); + $context = array( + 'user' => $user, + 'confirmationUrl' => $url + ); + + $this->sendMessage($template, $context, $this->parameters['from_email']['confirmation'], $user->getEmail()); + } + + public function sendResettingEmailMessage(UserInterface $user) + { + $template = $this->parameters['template']['resetting']; + $url = $this->router->generate('fos_user_resetting_reset', array('token' => $user->getConfirmationToken()), true); + $context = array( + 'user' => $user, + 'confirmationUrl' => $url + ); + $this->sendMessage($template, $context, $this->parameters['from_email']['resetting'], $user->getEmail()); + } + + /** + * @param string $templateName + * @param array $context + */ + protected function sendMessage($templateName, $context, $fromEmail, $toEmail) + { + $message = Swift_Message::newInstance() + ->setFrom($fromEmail) + ->setTo($toEmail); + + $this->helper->populateMessage($message, $templateName, $context); + $this->mailer->send($message); + } +} diff --git a/Mailer/TwigSwiftHelper.php b/Mailer/TwigSwiftHelper.php new file mode 100644 index 0000000..8276dae --- /dev/null +++ b/Mailer/TwigSwiftHelper.php @@ -0,0 +1,112 @@ +twig = $twig; + $this->web_directory = $web_directory; + } + + protected function extractMessageContent($template_name, $context, $parts = array('subject', 'content' => 'body_text')) + { + $template = $this->twig->loadTemplate($template_name); + $context = $this->twig->mergeGlobals($context); + + $data = array(); + + foreach ($parts as $k => $part) { + $key = is_numeric($k) ? $part : $k; + $data[$key] = $template->renderBlock($part, $context); + } + + return $data; + } + + /** + * Replace all src of img.inline-image with an embedded image + * + * @param Swift_Message $message + */ + protected function inlineImages(Swift_Message $message) + { + $html = $message->getBody(); + + $crawler = new Crawler(); + $crawler->addHtmlContent($html); + + $imgs = array(); + $replaces = array(); + + foreach ($crawler->filterXPath("//img[contains(concat(' ',normalize-space(@class), ' '), ' inline-image ')]") as $img) { + $normalized_src = $src = $img->getAttribute('src'); + + if (isset($replaces['src="'.$src.'"'])) { + continue; + } + + // if starting with one slash, use local file + if (preg_match('#^/[^/]#', $normalized_src)) { + $normalized_src = $this->web_directory . parse_url($src, PHP_URL_PATH); + } + + if (!isset($imgs[$normalized_src])) { + $swift_image = Swift_Image::fromPath($normalized_src); + $imgs[$normalized_src] = $message->embed($swift_image); + } + + $replaces['src=\''.$src.'\''] = 'src="'.$imgs[$normalized_src].'"'; + $replaces['src="' .$src.'"' ] = 'src="'.$imgs[$normalized_src].'"'; + } + + if (count($replaces)) { + $html = str_replace(array_keys($replaces), array_values($replaces), $html); + $message->setBody($html); + } + } + + public function populateMessage(Swift_Message $message, $template_name, $context) + { + $data = $this->extractMessageContent($template_name, $context, array('subject', 'text' => 'body_text', 'html' => 'body_html')); + + if (empty($data['subject']) || empty($data['text'])) { + throw new \InvalidArgumentException('The mail template must have (non-empty) subject ('.$data['subject'].') and body_text ('.$data['text'].') blocks'); + } + + $message->setSubject($data['subject']); + + if (!empty($data['html'])) { + $message->setBody($data['html'], 'text/html') + ->addPart($data['text'], 'text/plain'); + + $this->inlineImages($message); + } else { + $message->setBody($data['text']); + } + + return $message; + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c509a76 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# WMC SwiftMailer Twig bridge + +This bundle provides an easy way to create email templates using Twig for the +SwiftMailer library. + +This helper is inspired from +[FOSUB TwigSwiftMailer](https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Mailer/TwigSwiftMailer.php). + +If you're using FOS User Bundle, we also provide a mailer service drop-in +replacement to support our additional features. + +## Installation + +### With Symfony +The best way to install this extension is through composer: + +First, require the bundle: + +```sh +composer require wemakecustom/swiftmailer-twig-bundle "^1.0" +``` + +Second, enable it: + +```php + 'Jonh Smith', 'email' => 'recipient@example.com']; + +$message = $mailer->createMessage()->setTo(['recipient@example.com' => 'John Smith']); +$swiftMailerTemplateHelper->populateMessage($message, 'AppBundle:Mail:my_email.mail.twig', $data); +$mailer->send($message); +``` + +```twig +{# my_email.mail.twig #} + +{% block subject -%} + My email Subject +{%- endblock %} + +{% block body_text %} + Hello {{ recipient.name }}, + + {# ... Awesome plain text email ... #} + + Best Regards, + Keep being awesome! +{% endblock %} + +{% block body_html %} +

Hello {{ recipient.name }},

+ +

+ {# ... Awesome HTML email ... #} +

+ +

+ Best Regards,
+ Keep being awesome! +

+{% endblock %} +``` diff --git a/Resources/config/services.xml b/Resources/config/services.xml new file mode 100644 index 0000000..825f836 --- /dev/null +++ b/Resources/config/services.xml @@ -0,0 +1,28 @@ + + + + + + + + %web_directory% + + + + + + + + %fos_user.registration.confirmation.template% + %fos_user.resetting.email.template% + + + %fos_user.registration.confirmation.from_email% + %fos_user.resetting.email.from_email% + + + + + diff --git a/WMCSwiftmailerTwigBundle.php b/WMCSwiftmailerTwigBundle.php new file mode 100644 index 0000000..ac580f7 --- /dev/null +++ b/WMCSwiftmailerTwigBundle.php @@ -0,0 +1,17 @@ +addCompilerPass($this->getContainerExtension()); + } + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c70ec76 --- /dev/null +++ b/composer.json @@ -0,0 +1,36 @@ +{ + "name": "wemakecustom/swiftmailer-twig-bundle", + "type": "symfony-bundle", + "license": "MIT", + "keywords": ["SwiftMailer", "Twig", "Bridge", "FOSUB", "EMail Templating"], + "authors": [ + { + "name": "Sébastin Lavoie", + "email": "github@lavoie.sl" + }, + { + "name": "Mathieu Lemoine", + "email": "mathieu@wemakecustom.com" + } + ], + "description": "WeMakeCustom Bridge between SwiftMailer Twig", + "autoload": { + "psr-4": { "WMC\\SwiftmailerTwigBundle\\": "" } + }, + "require": { + "php": ">=5.3.3", + "swiftmailer/swiftmailer": ">=4.2.0,~5.0", + "twig/twig": "~1.23|~2.0", + "symfony/dom-crawler": "~2.3|~3.0" + }, + "suggests": { + "symfony/swiftmailer-bundle": "With symfony/twig-bundle to have the complete Symfony integration", + "symfony/twig-bundle": "With symfony/swiftmailer-bundle to have the complete Symfony integration", + "friendsofsymfony/user-bundle": "FOSUB integration" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +}