Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/Illuminate/Notifications/Channels/MailChannel.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public function send($notifiable, Notification $notification)
}

if ($message instanceof Mailable) {
return $message->send($this->mailer);
return $this->populateMailableRecipients($message, $notifiable, $notification)
->send($this->mailer);
}

return $this->mailer->mailer($message->mailer ?? null)->send(
Expand Down Expand Up @@ -213,7 +214,7 @@ protected function addressMessage($mailMessage, $notifiable, $notification, $mes
{
$this->addSender($mailMessage, $message);

$mailMessage->to($this->getRecipients($notifiable, $notification, $message));
$mailMessage->to($this->getRecipients($notifiable, $notification));

if (! empty($message->cc)) {
foreach ($message->cc as $cc) {
Expand Down Expand Up @@ -248,15 +249,35 @@ protected function addSender($mailMessage, $message)
}
}

/**
* Populate the mailable recipients using the notifiable's mail route.
*
* @param \Illuminate\Contracts\Mail\Mailable $mailable
* @param mixed $notifiable
* @param \Illuminate\Notifications\Notification $notification
* @return \Illuminate\Contracts\Mail\Mailable
*/
protected function populateMailableRecipients($mailable, $notifiable, $notification)
{
if (! empty($mailable->to)) {
return $mailable;
}

if (method_exists($mailable, 'envelope') && ! empty($mailable->envelope()->to)) {
return $mailable;
}

return $mailable->to($this->getRecipients($notifiable, $notification));
}

/**
* Get the recipients of the given message.
*
* @param mixed $notifiable
* @param \Illuminate\Notifications\Notification $notification
* @param \Illuminate\Notifications\Messages\MailMessage $message
* @return mixed
*/
protected function getRecipients($notifiable, $notification, $message)
protected function getRecipients($notifiable, $notification)
{
if (is_string($recipients = $notifiable->routeNotificationFor('mail', $notification))) {
$recipients = [$recipients];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,38 @@ public function testMailableIsSentWithSelectedLocale()
);
}

public function testMailableWithoutToIsAddressedToNotifiable()
{
$user = NotifiableLocalizedUser::forceCreate([
'email' => 'taylor@laravel.com',
]);

$user->notify(new GreetingMailNotificationWithMailableWithoutTo);

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

$this->assertSame(
'taylor@laravel.com',
$message->getTo()[0]->getAddress()
);
}

public function testMailableWithoutToIsAddressedToMultipleNotifiableRoutes()
{
$user = NotifiableLocalizedUserWithMultipleMailRoutes::forceCreate([
'email' => 'taylor@laravel.com',
]);

$user->notify(new GreetingMailNotificationWithMailableWithoutTo);

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

$this->assertEqualsCanonicalizing(
['foo_taylor@laravel.com', 'bar_taylor@laravel.com'],
array_map(fn ($address) => $address->getAddress(), $message->getTo())
);
}

public function testMailIsSentWithLocaleUpdatedListenersCalled()
{
Carbon::setTestNow('2018-07-25');
Expand Down Expand Up @@ -247,6 +279,17 @@ public function preferredLocale()
}
}

class NotifiableLocalizedUserWithMultipleMailRoutes extends NotifiableLocalizedUser
{
public function routeNotificationForMail($notification)
{
return [
'foo_'.$this->email,
'bar_'.$this->email,
];
}
}

class GreetingMailNotification extends Notification
{
public function via($notifiable)
Expand Down Expand Up @@ -276,6 +319,19 @@ public function toMail($notifiable)
}
}

class GreetingMailNotificationWithMailableWithoutTo extends Notification
{
public function via($notifiable)
{
return [MailChannel::class];
}

public function toMail($notifiable)
{
return new GreetingMailable;
}
}

class GreetingMailable extends Mailable
{
public function build()
Expand Down
153 changes: 153 additions & 0 deletions tests/Notifications/MailChannelTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Illuminate\Tests\Notifications;

use Illuminate\Contracts\Mail\Factory as MailFactory;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Markdown;
use Illuminate\Notifications\Channels\MailChannel;
use Illuminate\Notifications\Notification;
use Mockery as m;
use PHPUnit\Framework\TestCase;

class MailChannelTest extends TestCase
{
protected function tearDown(): void
{
m::close();

parent::tearDown();
}

public function testItPopulatesMailableRecipientsWhenToIsEmpty()
{
$mailable = new MailChannelTestMailable;

$mailable = $this->channel()->populateMailableRecipients(
$mailable,
new MailChannelNotifiable('taylor@laravel.com'),
new MailChannelTestNotification,
);

$this->assertSame('taylor@laravel.com', $mailable->to[0]['address']);
}

public function testItDoesNotOverrideExistingMailableRecipients()
{
$mailable = (new MailChannelTestMailable)->to('other@laravel.com');

$mailable = $this->channel()->populateMailableRecipients(
$mailable,
new MailChannelNotifiable('taylor@laravel.com'),
new MailChannelTestNotification,
);

$this->assertSame('other@laravel.com', $mailable->to[0]['address']);
}

public function testItDoesNotOverrideMailableRecipientsDefinedOnEnvelope()
{
$mailable = new MailChannelTestMailableWithEnvelopeRecipient;

$mailable = $this->channel()->populateMailableRecipients(
$mailable,
new MailChannelNotifiable('taylor@laravel.com'),
new MailChannelTestNotification,
);

$this->assertSame([], $mailable->to);
}

public function testItPopulatesMultipleMailableRecipientsFromRoute()
{
$mailable = new MailChannelTestMailable;

$mailable = $this->channel()->populateMailableRecipients(
$mailable,
new MailChannelNotifiableWithMultipleRoutes('taylor@laravel.com'),
new MailChannelTestNotification,
);

$this->assertEqualsCanonicalizing(
['foo_taylor@laravel.com', 'bar_taylor@laravel.com'],
array_column($mailable->to, 'address')
);
}

protected function channel(): MailChannelForTesting
{
return new MailChannelForTesting(
m::mock(MailFactory::class),
m::mock(Markdown::class),
);
}
}

class MailChannelForTesting extends MailChannel
{
public function populateMailableRecipients($mailable, $notifiable, $notification)
{
return parent::populateMailableRecipients($mailable, $notifiable, $notification);
}
}

class MailChannelNotifiable
{
public function __construct(public string $email)
{
}

public function routeNotificationFor($driver, $notification = null)
{
return $driver === 'mail'
? $this->routeNotificationForMail($notification)
: null;
}

public function routeNotificationForMail($notification)
{
return $this->email;
}
}

class MailChannelNotifiableWithMultipleRoutes extends MailChannelNotifiable
{
public function routeNotificationForMail($notification)
{
return [
'foo_'.$this->email,
'bar_'.$this->email,
];
}
}

class MailChannelTestMailable extends Mailable
{
public function build()
{
return $this->html('Test');
}
}

class MailChannelTestMailableWithEnvelopeRecipient extends Mailable
{
public function envelope()
{
return new \Illuminate\Mail\Mailables\Envelope(
to: [new \Illuminate\Mail\Mailables\Address('envelope@laravel.com')],
subject: 'Test',
);
}

public function content()
{
return new \Illuminate\Mail\Mailables\Content(
htmlString: 'Test',
);
}
}

class MailChannelTestNotification extends Notification
{
//
}
Loading