diff --git a/ecs.php b/ecs.php index 9dc643e..0d09b97 100644 --- a/ecs.php +++ b/ecs.php @@ -2,6 +2,7 @@ use PhpCsFixer\Fixer\ClassNotation\VisibilityRequiredFixer; use PhpCsFixer\Fixer\Comment\HeaderCommentFixer; +use PhpCsFixer\Fixer\Phpdoc\NoSuperfluousPhpdocTagsFixer; use PhpCsFixer\Fixer\Phpdoc\PhpdocTagTypeFixer; use SlevomatCodingStandard\Sniffs\Commenting\InlineDocCommentDeclarationSniff; use Symplify\EasyCodingStandard\Config\ECSConfig; @@ -25,6 +26,7 @@ PhpdocTagTypeFixer::class, InlineDocCommentDeclarationSniff::class . '.MissingVariable', VisibilityRequiredFixer::class => ['*Spec.php'], + NoSuperfluousPhpdocTagsFixer::class => ['src/Component/Sender/SenderInterface.php'], '**/var/*', 'src/Bundle/test/app/AppKernel.php', ]); diff --git a/phpstan.neon b/phpstan.neon index c6517c5..5e1f7a9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -26,3 +26,5 @@ parameters: - '/Method Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface::dispatch\(\) invoked with 2 parameters, 1 required\./' - '/Cannot call method has\(\) on object\|null/' - '/Property Sylius\\Component\\Mailer\\Model\\Email\:\:\$id is never written\, only read\./' + - '/PHPDoc tag \@param references unknown parameter\: \$bccRecipients/' + - '/PHPDoc tag \@param references unknown parameter\: \$ccRecipients/' diff --git a/psalm.xml b/psalm.xml index 47272cf..380a208 100644 --- a/psalm.xml +++ b/psalm.xml @@ -17,6 +17,12 @@ + + + + + + diff --git a/src/Bundle/Sender/Adapter/DefaultAdapter.php b/src/Bundle/Sender/Adapter/DefaultAdapter.php index 3e73f14..6337115 100644 --- a/src/Bundle/Sender/Adapter/DefaultAdapter.php +++ b/src/Bundle/Sender/Adapter/DefaultAdapter.php @@ -43,4 +43,22 @@ public function send( SymfonyMailerAdapter::class, )); } + + public function sendWithCC( + array $recipients, + string $senderAddress, + string $senderName, + RenderedEmail $renderedEmail, + EmailInterface $email, + array $data, + array $attachments = [], + array $replyTo = [], + array $ccRecipients = [], + array $bccRecipients = [], + ): void { + throw new \RuntimeException(sprintf( + 'You need to configure an adapter to send the email. Take a look at %s (requires "symfony/mailer" library).', + SymfonyMailerAdapter::class, + )); + } } diff --git a/src/Bundle/Sender/Adapter/SwiftMailerAdapter.php b/src/Bundle/Sender/Adapter/SwiftMailerAdapter.php index 8dbc727..b1f70f5 100644 --- a/src/Bundle/Sender/Adapter/SwiftMailerAdapter.php +++ b/src/Bundle/Sender/Adapter/SwiftMailerAdapter.php @@ -17,13 +17,14 @@ use Sylius\Component\Mailer\Model\EmailInterface; use Sylius\Component\Mailer\Renderer\RenderedEmail; use Sylius\Component\Mailer\Sender\Adapter\AbstractAdapter; +use Sylius\Component\Mailer\Sender\Adapter\CcAwareAdapterInterface; use Sylius\Component\Mailer\SyliusMailerEvents; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * @deprecated The Swift Mailer integration is deprecated since sylius/mailer-bundle 1.8. Use the Symfony Mailer integration instead. */ -class SwiftMailerAdapter extends AbstractAdapter +class SwiftMailerAdapter extends AbstractAdapter implements CcAwareAdapterInterface { /** @var \Swift_Mailer */ protected $mailer; @@ -55,6 +56,56 @@ public function send( array $data, array $attachments = [], array $replyTo = [], + ): void { + $this->sendMessage( + $renderedEmail, + $senderAddress, + $senderName, + $recipients, + $replyTo, + $attachments, + $email, + $data, + ); + } + + public function sendWithCC( + array $recipients, + string $senderAddress, + string $senderName, + RenderedEmail $renderedEmail, + EmailInterface $email, + array $data, + array $attachments = [], + array $replyTo = [], + array $ccRecipients = [], + array $bccRecipients = [], + ): void { + $this->sendMessage( + $renderedEmail, + $senderAddress, + $senderName, + $recipients, + $replyTo, + $attachments, + $email, + $data, + $ccRecipients, + $bccRecipients, + ); + } + + private function sendMessage( + RenderedEmail $renderedEmail, + string $senderAddress, + string $senderName, + array $recipients, + array $replyTo, + array $attachments, + EmailInterface $email, + array $data, + array $ccRecipients = [], + array $bccRecipients = [], ): void { $message = (new \Swift_Message()) ->setSubject($renderedEmail->getSubject()) @@ -63,6 +114,13 @@ public function send( ->setReplyTo($replyTo) ; + if (!empty($ccRecipients)) { + $message->setCc($ccRecipients); + } + if (!empty($bccRecipients)) { + $message->setBcc($bccRecipients); + } + $message->setBody($renderedEmail->getBody(), 'text/html'); foreach ($attachments as $attachment) { diff --git a/src/Bundle/Sender/Adapter/SymfonyMailerAdapter.php b/src/Bundle/Sender/Adapter/SymfonyMailerAdapter.php index a64dfde..eb256c1 100644 --- a/src/Bundle/Sender/Adapter/SymfonyMailerAdapter.php +++ b/src/Bundle/Sender/Adapter/SymfonyMailerAdapter.php @@ -17,12 +17,13 @@ use Sylius\Component\Mailer\Model\EmailInterface; use Sylius\Component\Mailer\Renderer\RenderedEmail; use Sylius\Component\Mailer\Sender\Adapter\AbstractAdapter; +use Sylius\Component\Mailer\Sender\Adapter\CcAwareAdapterInterface; use Sylius\Component\Mailer\SyliusMailerEvents; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; -final class SymfonyMailerAdapter extends AbstractAdapter +final class SymfonyMailerAdapter extends AbstractAdapter implements CcAwareAdapterInterface { private MailerInterface $mailer; @@ -43,6 +44,56 @@ public function send( array $data, array $attachments = [], array $replyTo = [], + ): void { + $this->sendMessage( + $renderedEmail, + $senderAddress, + $senderName, + $recipients, + $replyTo, + $attachments, + $email, + $data, + ); + } + + public function sendWithCC( + array $recipients, + string $senderAddress, + string $senderName, + RenderedEmail $renderedEmail, + EmailInterface $email, + array $data, + array $attachments = [], + array $replyTo = [], + array $ccRecipients = [], + array $bccRecipients = [], + ): void { + $this->sendMessage( + $renderedEmail, + $senderAddress, + $senderName, + $recipients, + $replyTo, + $attachments, + $email, + $data, + $ccRecipients, + $bccRecipients, + ); + } + + private function sendMessage( + RenderedEmail $renderedEmail, + string $senderAddress, + string $senderName, + array $recipients, + array $replyTo, + array $attachments, + EmailInterface $email, + array $data, + array $ccRecipients = [], + array $bccRecipients = [], ): void { $message = (new Email()) ->subject($renderedEmail->getSubject()) @@ -52,6 +103,9 @@ public function send( ->html($renderedEmail->getBody()) ; + $message->addCc(...$ccRecipients); + $message->addBcc(...$bccRecipients); + foreach ($attachments as $attachment) { $message->attachFromPath($attachment); } diff --git a/src/Bundle/spec/Sender/Adapter/DefaultAdapterSpec.php b/src/Bundle/spec/Sender/Adapter/DefaultAdapterSpec.php index 931346b..3b06fe8 100644 --- a/src/Bundle/spec/Sender/Adapter/DefaultAdapterSpec.php +++ b/src/Bundle/spec/Sender/Adapter/DefaultAdapterSpec.php @@ -37,5 +37,13 @@ function it_throws_an_exception_about_not_configured_email_sender_adapter( ))) ->during('send', [['pawel@sylius.com'], 'arnaud@sylius.com', 'arnaud', $renderedEmail, $email, []]) ; + + $this + ->shouldThrow(new \RuntimeException(sprintf( + 'You need to configure an adapter to send the email. Take a look at %s (requires "symfony/mailer" library).', + SymfonyMailerAdapter::class, + ))) + ->during('sendWithCc', [['pawel@sylius.com'], 'arnaud@sylius.com', 'arnaud', $renderedEmail, $email, [], [], [], ['cc@example.com'], ['bcc@example.com']]) + ; } } diff --git a/src/Bundle/spec/Sender/Adapter/SwiftMailerAdapterSpec.php b/src/Bundle/spec/Sender/Adapter/SwiftMailerAdapterSpec.php index 7962d0b..1760171 100644 --- a/src/Bundle/spec/Sender/Adapter/SwiftMailerAdapterSpec.php +++ b/src/Bundle/spec/Sender/Adapter/SwiftMailerAdapterSpec.php @@ -50,7 +50,14 @@ function it_sends_an_email( ->shouldBeCalled() ; - $mailer->send(Argument::type('\Swift_Message'))->shouldBeCalled(); + $mailer->send(Argument::that(function (\Swift_Message $message): bool { + return + $message->getSubject() === 'subject' && + $message->getBody() === 'body' && + $message->getFrom() === ['arnaud@sylius.com' => 'arnaud'] && + $message->getTo() === ['pawel@sylius.com' => null] + ; + }))->shouldBeCalled(); $dispatcher ->dispatch(Argument::type(EmailSendEvent::class), SyliusMailerEvents::EMAIL_POST_SEND) @@ -66,4 +73,50 @@ function it_sends_an_email( [], ); } + + function it_sends_an_email_with_cc_and_bcc_emails( + \Swift_Mailer $mailer, + EmailInterface $email, + EventDispatcherInterface $dispatcher, + RenderedEmail $renderedEmail, + ): void { + $this->setEventDispatcher($dispatcher); + + $renderedEmail->getSubject()->shouldBeCalled()->willReturn('subject'); + $renderedEmail->getBody()->shouldBeCalled()->willReturn('body'); + + $dispatcher + ->dispatch(Argument::type(EmailSendEvent::class), SyliusMailerEvents::EMAIL_PRE_SEND) + ->shouldBeCalled() + ; + + $mailer->send(Argument::that(function (\Swift_Message $message): bool { + return + $message->getSubject() === 'subject' && + $message->getBody() === 'body' && + $message->getFrom() === ['arnaud@sylius.com' => 'arnaud'] && + $message->getTo() === ['pawel@sylius.com' => null] && + $message->getCc() === ['cc@example.com' => null] && + $message->getBcc() === ['bcc@example.com' => null] + ; + }))->shouldBeCalled(); + + $dispatcher + ->dispatch(Argument::type(EmailSendEvent::class), SyliusMailerEvents::EMAIL_POST_SEND) + ->shouldBeCalled() + ; + + $this->sendWithCC( + ['pawel@sylius.com'], + 'arnaud@sylius.com', + 'arnaud', + $renderedEmail, + $email, + [], + [], + [], + ['cc@example.com'], + ['bcc@example.com'], + ); + } } diff --git a/src/Bundle/spec/Sender/Adapter/SymfonyMailerAdapterSpec.php b/src/Bundle/spec/Sender/Adapter/SymfonyMailerAdapterSpec.php index 61c882f..80ac6dc 100644 --- a/src/Bundle/spec/Sender/Adapter/SymfonyMailerAdapterSpec.php +++ b/src/Bundle/spec/Sender/Adapter/SymfonyMailerAdapterSpec.php @@ -22,6 +22,7 @@ use Sylius\Component\Mailer\SyliusMailerEvents; use Symfony\Component\Mailer\Exception\TransportException; use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -53,7 +54,14 @@ function it_sends_an_email_with_events( ->shouldBeCalled() ; - $mailer->send(Argument::type(Email::class))->shouldBeCalled(); + $mailer->send(Argument::that(function (Email $message): bool { + return + $message->getSubject() === 'subject' && + $message->getBody()->bodyToString() === 'body' && + $message->getFrom()[0] == new Address('arnaud@sylius.com', 'arnaud') && + $message->getTo()[0] == new Address('pawel@sylius.com') + ; + }))->shouldBeCalled(); $dispatcher ->dispatch(Argument::type(EmailSendEvent::class), SyliusMailerEvents::EMAIL_POST_SEND) @@ -70,6 +78,39 @@ function it_sends_an_email_with_events( ); } + function it_sends_an_email_with_cc_and_bcc( + MailerInterface $mailer, + EmailInterface $email, + RenderedEmail $renderedEmail, + ): void { + $renderedEmail->getSubject()->willReturn('subject'); + $renderedEmail->getBody()->willReturn('body'); + + $mailer->send(Argument::that(function (Email $message): bool { + return + $message->getSubject() === 'subject' && + $message->getBody()->bodyToString() === 'body' && + $message->getFrom()[0] == new Address('arnaud@sylius.com', 'arnaud') && + $message->getTo()[0] == new Address('pawel@sylius.com') && + $message->getCc()[0] == new Address('cc@example.com') && + $message->getBcc()[0] == new Address('bcc@example.com') + ; + }))->shouldBeCalled(); + + $this->sendWithCC( + ['pawel@sylius.com'], + 'arnaud@sylius.com', + 'arnaud', + $renderedEmail, + $email, + [], + [], + [], + ['cc@example.com'], + ['bcc@example.com'], + ); + } + function it_sends_an_email_with_attachments( MailerInterface $mailer, EmailInterface $email, diff --git a/src/Component/Sender/Adapter/CcAwareAdapterInterface.php b/src/Component/Sender/Adapter/CcAwareAdapterInterface.php new file mode 100644 index 0000000..0b9042d --- /dev/null +++ b/src/Component/Sender/Adapter/CcAwareAdapterInterface.php @@ -0,0 +1,40 @@ +provider->getEmail($code); @@ -59,6 +67,23 @@ public function send(string $code, array $recipients, array $data = [], array $a $renderedEmail = $this->rendererAdapter->render($email, $data); + if (count($arguments) > 5 && $this->senderAdapter instanceof CcAwareAdapterInterface) { + $this->senderAdapter->sendWithCC( + $recipients, + $senderAddress, + $senderName, + $renderedEmail, + $email, + $data, + $attachments, + $replyTo, + $arguments[5] ?? [], + $arguments[6] ?? [], + ); + + return; + } + $this->senderAdapter->send( $recipients, $senderAddress, diff --git a/src/Component/Sender/SenderInterface.php b/src/Component/Sender/SenderInterface.php index c118007..62f29d4 100644 --- a/src/Component/Sender/SenderInterface.php +++ b/src/Component/Sender/SenderInterface.php @@ -16,9 +16,19 @@ interface SenderInterface { /** + * @deprecated using this method without 2 last arguments ($ccRecipients and $bccRecipients) is deprecated since 1.8 and won't be possible since 2.0 + * * @param string[]|null[] $recipients A list of email addresses to receive the message. Providing null or empty string in the list of recipients is deprecated and will be removed in SyliusMailerBundle 2.0 * @param string[] $attachments A list of file paths to attach to the message. * @param string[] $replyTo A list of email addresses to set as the Reply-To address for the message. + * @param string[] $ccRecipients A list of email addresses set as carbon copy + * @param string[] $bccRecipients A list of email addresses set as blind carbon copy */ - public function send(string $code, array $recipients, array $data = [], array $attachments = [], array $replyTo = []): void; + public function send( + string $code, + array $recipients, + array $data = [], + array $attachments = [], + array $replyTo = [], + ): void; } diff --git a/src/Component/spec/Sender/SenderSpec.php b/src/Component/spec/Sender/SenderSpec.php index d897057..8fa17a4 100644 --- a/src/Component/spec/Sender/SenderSpec.php +++ b/src/Component/spec/Sender/SenderSpec.php @@ -20,7 +20,7 @@ use Sylius\Component\Mailer\Provider\EmailProviderInterface; use Sylius\Component\Mailer\Renderer\Adapter\AdapterInterface as RendererAdapterInterface; use Sylius\Component\Mailer\Renderer\RenderedEmail; -use Sylius\Component\Mailer\Sender\Adapter\AdapterInterface as SenderAdapterInterface; +use Sylius\Component\Mailer\Sender\Adapter\CcAwareAdapterInterface as SenderAdapterInterface; final class SenderSpec extends ObjectBehavior { @@ -48,9 +48,49 @@ function it_sends_an_email_through_the_adapter( $data = ['foo' => 2]; $rendererAdapter->render($email, ['foo' => 2])->willReturn($renderedEmail); - $senderAdapter->send(['john@example.com'], 'sender@example.com', 'Sender', $renderedEmail, $email, $data, [], [])->shouldBeCalled(); + $senderAdapter->send( + ['john@example.com'], + 'sender@example.com', + 'Sender', + $renderedEmail, + $email, + $data, + [], + [], + )->shouldBeCalled(); + + $this->send('bar', ['john@example.com'], $data, [], []); + } - $this->send('bar', ['john@example.com'], $data, []); + function it_sends_an_email_with_cc_and_bcc_through_the_adapter( + EmailInterface $email, + EmailProviderInterface $provider, + RenderedEmail $renderedEmail, + RendererAdapterInterface $rendererAdapter, + SenderAdapterInterface $senderAdapter, + ): void { + $provider->getEmail('bar')->willReturn($email); + $email->isEnabled()->willReturn(true); + $email->getSenderAddress()->willReturn('sender@example.com'); + $email->getSenderName()->willReturn('Sender'); + + $data = ['foo' => 2]; + + $rendererAdapter->render($email, ['foo' => 2])->willReturn($renderedEmail); + $senderAdapter->sendWithCC( + ['john@example.com'], + 'sender@example.com', + 'Sender', + $renderedEmail, + $email, + $data, + [], + [], + ['cc@example.com'], + ['bcc@example.com'], + )->shouldBeCalled(); + + $this->send('bar', ['john@example.com'], $data, [], [], ['cc@example.com'], ['bcc@example.com']); } function it_does_not_send_disabled_emails(