Skip to content

Commit

Permalink
Merge pull request #47603 from cosmastech/fix/markdown-mailable
Browse files Browse the repository at this point in the history
[10.x] Support Inline Attachments within Markdown Mailable
  • Loading branch information
nunomaduro committed Jul 3, 2023
2 parents 7c707a1 + a82d8b0 commit 969c9fe
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 78 deletions.
14 changes: 9 additions & 5 deletions src/Illuminate/Notifications/Channels/MailChannel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Illuminate\Notifications\Channels;

use Illuminate\Container\Container;
use Illuminate\Contracts\Mail\Factory as MailFactory;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Contracts\Queue\ShouldQueue;
Expand All @@ -11,6 +12,7 @@
use Illuminate\Support\Str;
use Symfony\Component\Mailer\Header\MetadataHeader;
use Symfony\Component\Mailer\Header\TagHeader;
use Illuminate\Config\Repository as ConfigRepository;

class MailChannel
{
Expand Down Expand Up @@ -95,13 +97,15 @@ protected function buildView($message)
return $message->view;
}

if (property_exists($message, 'theme') && ! is_null($message->theme)) {
$this->markdown->theme($message->theme);
}
$configRepository = Container::getInstance()->get(ConfigRepository::class);

return [
'html' => $this->markdown->render($message->markdown, $message->data()),
'text' => $this->markdown->renderText($message->markdown, $message->data()),
'html' => fn ($messageData = []) => $this->markdown->theme(
$message->theme ?: $configRepository->get('mail.markdown.theme', 'default')
)->render($message->markdown, array_merge($message->data(), $messageData)),
'text' => fn ($messageData = []) => $this->markdown->theme(
$message->theme ?: $configRepository->get('mail.markdown.theme', 'default')
)->renderText($message->markdown, array_merge($message->data(), $messageData)),
];
}

Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
body{color: test;}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Embed content: {{ $message->embed(__FILE__) }}
154 changes: 81 additions & 73 deletions tests/Integration/Notifications/SendingMailNotificationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use Mockery as m;
use Mockery\MockInterface;
use Orchestra\Testbench\TestCase;

class SendingMailNotificationsTest extends TestCase
{
public $mailFactory;
public $mailer;
public $markdown;
public MailFactory&MockInterface $mailFactory;
public Mailer&MockInterface $mailer;
public Markdown&MockInterface $markdown;

protected function tearDown(): void
{
Expand Down Expand Up @@ -77,39 +78,67 @@ public function testMailIsSent()
$this->markdown->shouldReceive('render')->once()->andReturn('htmlContent');
$this->markdown->shouldReceive('renderText')->once()->andReturn('textContent');

$this->mailer->shouldReceive('send')->once()->with(
['html' => 'htmlContent', 'text' => 'textContent'],
array_merge($notification->toMail($user)->toArray(), [
'__laravel_notification_id' => $notification->id,
'__laravel_notification' => get_class($notification),
'__laravel_notification_queued' => false,
]),
m::on(function ($closure) {
$message = m::mock(Message::class);
$this->setMailerSendAssertions($notification, $user, function($closure) {
$message = m::mock(Message::class);

$message->shouldReceive('to')->once()->with(['taylor@laravel.com']);
$message->shouldReceive('to')->once()->with(['taylor@laravel.com']);

$message->shouldReceive('cc')->once()->with('cc@deepblue.com', 'cc');
$message->shouldReceive('cc')->once()->with('cc@deepblue.com', 'cc');

$message->shouldReceive('bcc')->once()->with('bcc@deepblue.com', 'bcc');
$message->shouldReceive('bcc')->once()->with('bcc@deepblue.com', 'bcc');

$message->shouldReceive('from')->once()->with('jack@deepblue.com', 'Jacques Mayol');
$message->shouldReceive('from')->once()->with('jack@deepblue.com', 'Jacques Mayol');

$message->shouldReceive('replyTo')->once()->with('jack@deepblue.com', 'Jacques Mayol');
$message->shouldReceive('replyTo')->once()->with('jack@deepblue.com', 'Jacques Mayol');

$message->shouldReceive('subject')->once()->with('Test Mail Notification');
$message->shouldReceive('subject')->once()->with('Test Mail Notification');

$message->shouldReceive('priority')->once()->with(1);
$message->shouldReceive('priority')->once()->with(1);

$closure($message);
$closure($message);

return true;
});

return true;
})
);

$user->notify($notification);
}

private function setMailerSendAssertions(
Notification $notification,
NotifiableUser $user,
callable $callbackExpectationClosure
) {
$this->mailer->shouldReceive('send')->once()->withArgs(function(...$args) use ($notification, $user, $callbackExpectationClosure){
$viewArray = $args[0];

if (!m::on(fn($closure) => $closure() === 'htmlContent')->match($viewArray['html'])) {
return false;
}

if (!m::on(fn($closure) => $closure() === 'textContent')->match($viewArray['text'])) {
return false;
}

$data = $args[1];

$expected = array_merge($notification->toMail($user)->toArray(), [
'__laravel_notification_id' => $notification->id,
'__laravel_notification' => get_class($notification),
'__laravel_notification_queued' => false,
]);

if (array_keys($data) !== array_keys($expected)) {
return false;
}
if (array_values($data) !== array_values($expected)) {
return false;
}

return m::on($callbackExpectationClosure)->match($args[2]);
});
}

public function testMailIsSentToNamedAddress()
{
$notification = new TestMailNotification;
Expand All @@ -123,35 +152,27 @@ public function testMailIsSentToNamedAddress()
$this->markdown->shouldReceive('render')->once()->andReturn('htmlContent');
$this->markdown->shouldReceive('renderText')->once()->andReturn('textContent');

$this->mailer->shouldReceive('send')->once()->with(
['html' => 'htmlContent', 'text' => 'textContent'],
array_merge($notification->toMail($user)->toArray(), [
'__laravel_notification_id' => $notification->id,
'__laravel_notification' => get_class($notification),
'__laravel_notification_queued' => false,
]),
m::on(function ($closure) {
$message = m::mock(Message::class);
$this->setMailerSendAssertions($notification, $user, function($closure) {
$message = m::mock(Message::class);

$message->shouldReceive('to')->once()->with(['taylor@laravel.com' => 'Taylor Otwell', 'foo_taylor@laravel.com']);
$message->shouldReceive('to')->once()->with(['taylor@laravel.com' => 'Taylor Otwell', 'foo_taylor@laravel.com']);

$message->shouldReceive('cc')->once()->with('cc@deepblue.com', 'cc');
$message->shouldReceive('cc')->once()->with('cc@deepblue.com', 'cc');

$message->shouldReceive('bcc')->once()->with('bcc@deepblue.com', 'bcc');
$message->shouldReceive('bcc')->once()->with('bcc@deepblue.com', 'bcc');

$message->shouldReceive('from')->once()->with('jack@deepblue.com', 'Jacques Mayol');
$message->shouldReceive('from')->once()->with('jack@deepblue.com', 'Jacques Mayol');

$message->shouldReceive('replyTo')->once()->with('jack@deepblue.com', 'Jacques Mayol');
$message->shouldReceive('replyTo')->once()->with('jack@deepblue.com', 'Jacques Mayol');

$message->shouldReceive('subject')->once()->with('Test Mail Notification');
$message->shouldReceive('subject')->once()->with('Test Mail Notification');

$message->shouldReceive('priority')->once()->with(1);
$message->shouldReceive('priority')->once()->with(1);

$closure($message);
$closure($message);

return true;
})
);
return true;
});

$user->notify($notification);
}
Expand All @@ -168,25 +189,17 @@ public function testMailIsSentWithSubject()
$this->markdown->shouldReceive('render')->once()->andReturn('htmlContent');
$this->markdown->shouldReceive('renderText')->once()->andReturn('textContent');

$this->mailer->shouldReceive('send')->once()->with(
['html' => 'htmlContent', 'text' => 'textContent'],
array_merge($notification->toMail($user)->toArray(), [
'__laravel_notification_id' => $notification->id,
'__laravel_notification' => get_class($notification),
'__laravel_notification_queued' => false,
]),
m::on(function ($closure) {
$message = m::mock(Message::class);
$this->setMailerSendAssertions($notification, $user, function($closure) {
$message = m::mock(Message::class);

$message->shouldReceive('to')->once()->with(['taylor@laravel.com']);
$message->shouldReceive('to')->once()->with(['taylor@laravel.com']);

$message->shouldReceive('subject')->once()->with('mail custom subject');
$message->shouldReceive('subject')->once()->with('mail custom subject');

$closure($message);
$closure($message);

return true;
})
);
return true;
});

$user->notify($notification);
}
Expand All @@ -203,25 +216,18 @@ public function testMailIsSentToMultipleAddresses()
$this->markdown->shouldReceive('render')->once()->andReturn('htmlContent');
$this->markdown->shouldReceive('renderText')->once()->andReturn('textContent');

$this->mailer->shouldReceive('send')->once()->with(
['html' => 'htmlContent', 'text' => 'textContent'],
array_merge($notification->toMail($user)->toArray(), [
'__laravel_notification_id' => $notification->id,
'__laravel_notification' => get_class($notification),
'__laravel_notification_queued' => false,
]),
m::on(function ($closure) {
$message = m::mock(Message::class);
$this->setMailerSendAssertions($notification, $user, function($closure) {
$message = m::mock(Message::class);

$message->shouldReceive('to')->once()->with(['foo_taylor@laravel.com', 'bar_taylor@laravel.com']);
$message->shouldReceive('to')->once()->with(['foo_taylor@laravel.com', 'bar_taylor@laravel.com']);

$message->shouldReceive('subject')->once()->with('mail custom subject');
$message->shouldReceive('subject')->once()->with('mail custom subject');

$closure($message);
$closure($message);

return true;
});

return true;
})
);

$user->notify($notification);
}
Expand Down Expand Up @@ -332,6 +338,8 @@ public function testMailIsSentUsingMailMessageWithPlainOnly()

$user->notify($notification);
}


}

class NotifiableUser extends Model
Expand Down
118 changes: 118 additions & 0 deletions tests/Integration/Notifications/SendingMailableNotificationsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace Illuminate\Tests\Integration\Notifications;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\View;
use Orchestra\Testbench\TestCase;

class SendingMailableNotificationsTest extends TestCase
{
public $mailer;

protected function getEnvironmentSetUp($app)
{
$app['config']->set('mail.driver', 'array');

$app['config']->set('app.locale', 'en');

$app['config']->set('mail.markdown.theme', 'blank');

View::addLocation(__DIR__.'/Fixtures');
}

protected function setUp(): void
{
parent::setUp();

Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('email');
$table->string('name')->nullable();
});
}

public function testMarkdownNotification()
{
$user = MailableNotificationUser::forceCreate([
'email' => 'nuno@laravel.com',
]);

$user->notify(new MarkdownNotification());

$email = app('mailer')->getSymfonyTransport()->messages()[0]->getOriginalMessage()->toString();

$cid = explode(' cid:', str($email)->explode("\r\n")
->filter(fn ($line) => str_contains($line, 'Embed content: cid:'))
->first())[1];

$this->assertStringContainsString(<<<EOT
Content-Type: application/x-php; name=$cid\r
Content-Transfer-Encoding: base64\r
Content-Disposition: inline; name=$cid; filename=$cid\r
EOT, $email);
}

public function testCanSetTheme()
{
$user = MailableNotificationUser::forceCreate([
'email' => 'nuno@laravel.com',
]);

$user->notify(new MarkdownNotification('color-test'));
$mailTransport = app('mailer')->getSymfonyTransport();
$email = $mailTransport->messages()[0]->getOriginalMessage()->toString();

$bodyStyleLine =str($email)->explode("\r\n")
->filter(fn ($line) => str_contains($line, '<body style='))
->first();

$this->assertStringContainsString('color: test', $bodyStyleLine);

// confirm passing no theme resets to the app's default theme
$user->notify(new MarkdownNotification());

$email = $mailTransport->messages()[1]->getOriginalMessage()->toString();

$this->assertNull(str($email)->explode("\r\n")
->filter(fn ($line) => str_contains($line, '<body style='))
->first());
}
}


class MailableNotificationUser extends Model
{
use Notifiable;

public $table = 'users';
public $timestamps = false;
}


class MarkdownNotification extends Notification
{
public function __construct(
private readonly ?string $theme = null
){}
public function via($notifiable): array
{
return ['mail'];
}

public function toMail($notifiable): MailMessage
{
$message = (new MailMessage)->markdown('markdown');

if (! is_null($this->theme)) {
$message->theme($this->theme);
}

return $message;
}
}

0 comments on commit 969c9fe

Please sign in to comment.