Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.x] Support Inline Attachments within Markdown Mailable #47603

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Illuminate/Notifications/Channels/MailChannel.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ protected function buildView($message)
}

return [
'html' => $this->markdown->render($message->markdown, $message->data()),
'text' => $this->markdown->renderText($message->markdown, $message->data()),
'html' => fn ($messageData = []) => $this->markdown->render($message->markdown, $messageData),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my pull request, we've moved the "markdown->theme($message->theme)" to be executed within the given closure. Shouldn't we do the same here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like theme is respected if I write return (new MailMessage)->markdown('markdown')->theme('abba-zabba'); within the toMail() method of the notification.

That said, the instance level $markdown should be cloned, since we are changing the theme. Though Markdown is not bound as a singleton in the container, subsequent calls would use the same theme, even tho the user would anticipate it to be the result.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But can you adjust the code, so the "set" of theme happens exactly before the rendering? Just like the code I've done for the other PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nunomaduro take a look at how I have implemented it with the latest 2 commits. I can move it into the closure if that's better, but I don't see any way that the message's theme property would be overridden. (Also, I just think it would look uglier).

Could we remove the Markdown instance variable and just create it within the closures (or buildView() method)? It would mean a signature change to the constructor, but that instance level variable is more of a hinderance than a help, IMO.

'text' => fn ($messageData = []) => $this->markdown->renderText($message->markdown, $messageData),
];
}

Expand Down
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace Illuminate\Tests\Integration\Notifications;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Mail\Transport\ArrayTransport;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\View;
use Orchestra\Testbench\TestCase;
use Symfony\Component\Mailer\SentMessage;

class SendingMailableNotificationsTest extends TestCase
{
public $mailer;

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

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

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);
}
}


class MailableNotificationUser extends Model
{
use Notifiable;

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


class MarkdownNotification extends Notification
{
public function via($notifiable): array
{
return ['mail'];
}

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