Skip to content

Commit

Permalink
Markdown Emails + Notifications (#16768)
Browse files Browse the repository at this point in the history
This PR adds support for writing mailables and notifications in simple Markdown syntax using Blade and rendering the contents in a responsive HTML temlate along with plain text counterpart. Customizable themes and exportable views.
  • Loading branch information
taylorotwell authored Dec 12, 2016
1 parent b3780d7 commit 44880df
Show file tree
Hide file tree
Showing 41 changed files with 1,182 additions and 273 deletions.
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"ext-openssl": "*",
"classpreloader/classpreloader": "~3.0",
"doctrine/inflector": "~1.0",
"erusev/parsedown": "~1.6",
"jeremeamia/superclosure": "~2.2",
"league/flysystem": "~1.0",
"monolog/monolog": "~1.11",
Expand All @@ -37,6 +38,7 @@
"symfony/process": "~3.2",
"symfony/routing": "~3.2",
"symfony/var-dumper": "~3.2",
"tijsverkoyen/css-to-inline-styles": "~2.2",
"vlucas/phpdotenv": "~2.2"
},
"replace": {
Expand Down
11 changes: 10 additions & 1 deletion src/Illuminate/Contracts/View/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,16 @@ public function creator($views, $callback);
*
* @param string $namespace
* @param string|array $hints
* @return void
* @return $this
*/
public function addNamespace($namespace, $hints);

/**
* Replace the namespace hints for the given namespace.
*
* @param string $namespace
* @param string|array $hints
* @return $this
*/
public function replaceNamespace($namespace, $hints);
}
48 changes: 43 additions & 5 deletions src/Illuminate/Mail/MailServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ public function register()
{
$this->registerSwiftMailer();

$this->registerIlluminateMailer();

$this->registerMarkdownRenderer();
}

/**
* Register the Illuminate mailer instance.
*
* @return void
*/
protected function registerIlluminateMailer()
{
$this->app->singleton('mailer', function ($app) {
// Once we have create the mailer instance, we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
Expand All @@ -36,13 +48,16 @@ public function register()
// If a "from" address is set, we will set it on the mailer so that all mail
// messages sent by the applications will utilize the same "from" address
// on each one, which makes the developer's life a lot more convenient.
$from = $app['config']['mail.from'];
$from = $app->make('config')->get('mail.from');

if (is_array($from) && isset($from['address'])) {
$mailer->alwaysFrom($from['address'], $from['name']);
}

$to = $app['config']['mail.to'];
// If a "to" address is set we will set all mail messages to be delivered to
// this single address. This is primarily for easier viewing of emails if
// the application developer is still experimenting with the emails UI.
$to = $app->make('config')->get('mail.to');

if (is_array($to) && isset($to['address'])) {
$mailer->alwaysTo($to['address'], $to['name']);
Expand Down Expand Up @@ -80,7 +95,7 @@ public function registerSwiftMailer()
// Once we have the transporter registered, we will register the actual Swift
// mailer instance, passing in the transport instances, which allows us to
// override this transporter instances during app start-up if necessary.
$this->app['swift.mailer'] = $this->app->share(function ($app) {
$this->app->singleton('swift.mailer', function ($app) {
return new Swift_Mailer($app['swift.transport']->driver());
});
}
Expand All @@ -92,18 +107,41 @@ public function registerSwiftMailer()
*/
protected function registerSwiftTransport()
{
$this->app['swift.transport'] = $this->app->share(function ($app) {
$this->app->singleton('swift.transport', function ($app) {
return new TransportManager($app);
});
}

/**
* Register the Markdown renderer instance.
*
* @return void
*/
protected function registerMarkdownRenderer()
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/resources/views' => resource_path('views/vendor/mail'),
], 'laravel-mail');
}

$this->app->singleton(Markdown::class, function () {
return new Markdown($this->app->make('view'), [
'theme' => config('mail.markdown.theme', 'default'),
'paths' => config('mail.markdown.paths', []),
]);
});
}

/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['mailer', 'swift.mailer', 'swift.transport'];
return [
'mailer', 'swift.mailer', 'swift.transport', Markdown::class,
];
}
}
43 changes: 43 additions & 0 deletions src/Illuminate/Mail/Mailable.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ class Mailable implements MailableContract
*/
protected $subject;

/**
* The Markdown template for the message (if applicable).
*
* @var string
*/
protected $markdown;

/**
* The view to use for the message.
*
Expand Down Expand Up @@ -159,6 +166,10 @@ public function later($delay, Queue $queue)
*/
protected function buildView()
{
if (isset($this->markdown)) {
return $this->buildMarkdownView();
}

if (isset($this->view, $this->textView)) {
return [$this->view, $this->textView];
} elseif (isset($this->textView)) {
Expand All @@ -168,6 +179,23 @@ protected function buildView()
return $this->view;
}

/**
* Build the Markdown view for the message.
*
* @return array
*/
protected function buildMarkdownView()
{
$markdown = Container::getInstance()->make(Markdown::class);

$data = $this->buildViewData();

return [
'html' => $markdown->render($this->markdown, $data),
'text' => $markdown->renderText($this->markdown, $data),
];
}

/**
* Build the view data for the message.
*
Expand Down Expand Up @@ -403,6 +431,21 @@ public function subject($subject)
return $this;
}

/**
* Set the Markdown template for the message.
*
* @param string $view
* @param array $data
* @return $this
*/
public function markdown($view, array $data = [])
{
$this->markdown = $view;
$this->viewData = $data;

return $this;
}

/**
* Set the view and view data for the message.
*
Expand Down
144 changes: 144 additions & 0 deletions src/Illuminate/Mail/Markdown.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace Illuminate\Mail;

use Parsedown;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\View\Factory as ViewFactory;
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;

class Markdown
{
/**
* The view factory implementation.
*
* @var \Illuminate\View\Factory
*/
protected $view;

/**
* The current theme being used when generating emails.
*
* @var string
*/
protected $theme = 'default';

/**
* The registered component paths.
*
* @var array
*/
protected $componentPaths = [];

/**
* Create a new Markdown renderer instance.
*
* @param \Illuminate\View\Factory $view
* @param array $options
* @return void
*/
public function __construct(ViewFactory $view, array $options = [])
{
$this->view = $view;
$this->theme = Arr::get($options, 'theme', 'default');
$this->loadComponentsFrom(Arr::get($options, 'paths', []));
}

/**
* Render the Markdown template into HTML.
*
* @param string $view
* @param array $data
* @param \TijsVerkoyen\CssToInlineStyles\CssToInlineStyles|null $inliner
* @return \Illuminate\Support\HtmlString
*/
public function render($view, array $data = [], $inliner = null)
{
$this->view->flushFinderCache();

$contents = $this->view->replaceNamespace(
'mail', $this->htmlComponentPaths()
)->make($view, $data)->render();

return new HtmlString(with($inliner ?: new CssToInlineStyles)->convert(
$contents, $this->view->make('mail::themes.'.$this->theme)->render()
));
}

/**
* Render the Markdown template into HTML.
*
* @param string $view
* @param array $data
* @return \Illuminate\Support\HtmlString
*/
public function renderText($view, array $data = [])
{
$this->view->flushFinderCache();

return new HtmlString(preg_replace("/[\r\n]{2,}/", "\n\n", $this->view->replaceNamespace(
'mail', $this->markdownComponentPaths()
)->make($view, $data)->render()));
}

/**
* Parse the given Markdown text into HTML.
*
* @param string $text
* @return string
*/
public static function parse($text)
{
$parsedown = new Parsedown;

return new HtmlString($parsedown->text($text));
}

/**
* Get the HTML component paths.
*
* @return array
*/
public function htmlComponentPaths()
{
return array_map(function ($path) {
return $path.'/html';
}, $this->componentPaths());
}

/**
* Get the Markdown component paths.
*
* @return array
*/
public function markdownComponentPaths()
{
return array_map(function ($path) {
return $path.'/markdown';
}, $this->componentPaths());
}

/**
* Get the component paths.
*
* @return array
*/
protected function componentPaths()
{
return array_unique(array_merge($this->componentPaths, [
__DIR__.'/resources/views',
]));
}

/**
* Register new mail component paths.
*
* @param array $paths
* @return void
*/
public function loadComponentsFrom(array $paths = [])
{
$this->componentPaths = $paths;
}
}
4 changes: 3 additions & 1 deletion src/Illuminate/Mail/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
],
"require": {
"php": ">=5.6.4",
"erusev/parsedown": "~1.6",
"illuminate/container": "5.4.*",
"illuminate/contracts": "5.4.*",
"illuminate/support": "5.4.*",
"psr/log": "~1.0",
"swiftmailer/swiftmailer": "~5.1"
"swiftmailer/swiftmailer": "~5.1",
"tijsverkoyen/css-to-inline-styles": "~2.2"
},
"autoload": {
"psr-4": {
Expand Down
19 changes: 19 additions & 0 deletions src/Illuminate/Mail/resources/views/html/button.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<a href="{{ $url }}" class="button button-{{ $color or 'blue' }}" target="_blank">{{ $slot }}</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
11 changes: 11 additions & 0 deletions src/Illuminate/Mail/resources/views/html/footer.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<tr>
<td>
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0">
<tr>
<td class="content-cell" align="center">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</td>
</tr>
</table>
</td>
</tr>
7 changes: 7 additions & 0 deletions src/Illuminate/Mail/resources/views/html/header.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<tr>
<td class="header">
<a href="{{ $url }}">
{{ $slot }}
</a>
</td>
</tr>
Loading

0 comments on commit 44880df

Please sign in to comment.