diff --git a/app/migrations/Version20231218175905.php b/app/migrations/Version20231218175905.php new file mode 100644 index 000000000..f05dcb8c1 --- /dev/null +++ b/app/migrations/Version20231218175905.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE draw_acme__user ADD preferred_locale VARCHAR(255) DEFAULT \'en\' NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE draw_acme__user DROP preferred_locale'); + } +} diff --git a/app/src/DataFixtures/AppFixtures.php b/app/src/DataFixtures/AppFixtures.php index 65994ffd4..cffaaeedb 100644 --- a/app/src/DataFixtures/AppFixtures.php +++ b/app/src/DataFixtures/AppFixtures.php @@ -78,6 +78,7 @@ public function load(ObjectManager $manager): void $user->setPlainPassword('password'); if (1 === $number) { $user->setTags([$inactiveTag]); + $user->setPreferredLocale('fr'); } $manager->persist($user); } diff --git a/app/src/Entity/User.php b/app/src/Entity/User.php index e5eb9d380..62a8f140e 100644 --- a/app/src/Entity/User.php +++ b/app/src/Entity/User.php @@ -21,6 +21,7 @@ use Draw\Bundle\UserBundle\Security\TwoFactorAuthentication\Entity\ConfigurationTrait; use Draw\Bundle\UserBundle\Security\TwoFactorAuthentication\Entity\TwoFactorAuthenticationUserInterface; use Draw\Component\EntityMigrator\MigrationTargetEntityInterface; +use Draw\Component\Mailer\Recipient\LocalizationAwareInterface; use Draw\Component\Messenger\DoctrineMessageBusHook\Entity\MessageHolderInterface; use Draw\Component\Messenger\DoctrineMessageBusHook\Entity\MessageHolderTrait; use Draw\DoctrineExtra\Common\Collections\CollectionUtil; @@ -34,7 +35,7 @@ #[ORM\Table(name: 'draw_acme__user')] #[ORM\HasLifecycleCallbacks] #[UniqueEntity(fields: ['email'])] -class User implements MessageHolderInterface, SecurityUserInterface, TwoFactorAuthenticationUserInterface, PasswordChangeUserInterface, LockableUserInterface, TwoFactorInterface, ByEmailInterface, ByTimeBaseOneTimePasswordInterface, MigrationTargetEntityInterface +class User implements MessageHolderInterface, SecurityUserInterface, TwoFactorAuthenticationUserInterface, PasswordChangeUserInterface, LockableUserInterface, TwoFactorInterface, ByEmailInterface, ByTimeBaseOneTimePasswordInterface, MigrationTargetEntityInterface, LocalizationAwareInterface { use ByEmailTrait; use ByTimeBaseOneTimePasswordTrait; @@ -148,9 +149,11 @@ class User implements MessageHolderInterface, SecurityUserInterface, TwoFactorAu private Collection $userTags; #[Assert\NotNull] - #[Serializer\ReadOnlyProperty] private string $requiredReadOnly = 'value'; + #[ORM\Column(type: 'string', nullable: false, options: ['default' => 'en'])] + private string $preferredLocale = 'en'; + public function __construct() { $this->address = new Address(); @@ -176,6 +179,18 @@ public function setId(string $id): void $this->id = $id; } + public function getPreferredLocale(): string + { + return $this->preferredLocale; + } + + public function setPreferredLocale(string $preferredLocale): static + { + $this->preferredLocale = $preferredLocale; + + return $this; + } + /** * @see UserInterface */ diff --git a/config/serializer/Entity/User.yaml b/config/serializer/Entity/User.yaml index 5d6edee3d..627b10b28 100644 --- a/config/serializer/Entity/User.yaml +++ b/config/serializer/Entity/User.yaml @@ -49,5 +49,7 @@ App\Entity\User: exclude: true emailAuthCodeGeneratedAt: exclude: true + preferredLocale: + exclude: true twoFactorAuthenticationEnabledProviders: exclude: true diff --git a/packages/framework-extra-bundle/DependencyInjection/Compiler/EmailWriterCompilerPass.php b/packages/framework-extra-bundle/DependencyInjection/Compiler/EmailWriterCompilerPass.php index 8018e6b5a..816d296a4 100644 --- a/packages/framework-extra-bundle/DependencyInjection/Compiler/EmailWriterCompilerPass.php +++ b/packages/framework-extra-bundle/DependencyInjection/Compiler/EmailWriterCompilerPass.php @@ -3,8 +3,8 @@ namespace Draw\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler; use Draw\Component\Core\Reflection\ReflectionAccessor; +use Draw\Component\Mailer\EmailComposer; use Draw\Component\Mailer\EmailWriter\EmailWriterInterface; -use Draw\Component\Mailer\EventListener\EmailWriterListener; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -16,7 +16,7 @@ class EmailWriterCompilerPass implements CompilerPassInterface public function process(ContainerBuilder $container): void { try { - $emailWriterListenerDefinition = $container->findDefinition(EmailWriterListener::class); + $emailWriterListenerDefinition = $container->findDefinition(EmailComposer::class); } catch (ServiceNotFoundException) { return; } diff --git a/packages/framework-extra-bundle/DependencyInjection/Integration/MailerIntegration.php b/packages/framework-extra-bundle/DependencyInjection/Integration/MailerIntegration.php index 3aafed7ec..5ef3d9a93 100644 --- a/packages/framework-extra-bundle/DependencyInjection/Integration/MailerIntegration.php +++ b/packages/framework-extra-bundle/DependencyInjection/Integration/MailerIntegration.php @@ -2,6 +2,8 @@ namespace Draw\Bundle\FrameworkExtraBundle\DependencyInjection\Integration; +use Draw\Component\Mailer\BodyRenderer\LocalizeBodyRenderer; +use Draw\Component\Mailer\EmailComposer; use Draw\Component\Mailer\EmailWriter\DefaultFromEmailWriter; use Draw\Component\Mailer\EmailWriter\EmailWriterInterface; use Draw\Component\Mailer\EventListener\EmailCssInlinerListener; @@ -11,6 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Mime\Address; class MailerIntegration implements IntegrationInterface, PrependIntegrationInterface @@ -28,11 +31,18 @@ public function load(array $config, PhpFileLoader $loader, ContainerBuilder $con $loader, $namespace = 'Draw\\Component\\Mailer\\', \dirname( - (new \ReflectionClass(EmailWriterInterface::class))->getFileName(), - 2 + (new \ReflectionClass(EmailComposer::class))->getFileName(), ), ); + $container + ->getDefinition(LocalizeBodyRenderer::class) + ->setDecoratedService( + 'twig.mime_body_renderer', + 'draw.mailer.body_renderer.localize_body_renderer.inner' + ) + ->setArgument('$bodyRenderer', new Reference('draw.mailer.body_renderer.localize_body_renderer.inner')); + $container ->registerForAutoconfiguration(EmailWriterInterface::class) ->addTag(EmailWriterInterface::class); diff --git a/packages/framework-extra-bundle/Tests/DependencyInjection/Integration/MailerIntegrationTest.php b/packages/framework-extra-bundle/Tests/DependencyInjection/Integration/MailerIntegrationTest.php index 1ec1cce08..5c590d073 100644 --- a/packages/framework-extra-bundle/Tests/DependencyInjection/Integration/MailerIntegrationTest.php +++ b/packages/framework-extra-bundle/Tests/DependencyInjection/Integration/MailerIntegrationTest.php @@ -4,12 +4,15 @@ use Draw\Bundle\FrameworkExtraBundle\DependencyInjection\Integration\IntegrationInterface; use Draw\Bundle\FrameworkExtraBundle\DependencyInjection\Integration\MailerIntegration; +use Draw\Component\Mailer\BodyRenderer\LocalizeBodyRenderer; use Draw\Component\Mailer\Command\SendTestEmailCommand; +use Draw\Component\Mailer\EmailComposer; +use Draw\Component\Mailer\EmailWriter\AddTemplateHeaderEmailWriter; use Draw\Component\Mailer\EmailWriter\DefaultFromEmailWriter; use Draw\Component\Mailer\EmailWriter\EmailWriterInterface; +use Draw\Component\Mailer\EventListener\EmailComposerListener; use Draw\Component\Mailer\EventListener\EmailCssInlinerListener; use Draw\Component\Mailer\EventListener\EmailSubjectFromHtmlTitleListener; -use Draw\Component\Mailer\EventListener\EmailWriterListener; use Draw\Component\Mailer\Twig\TranslationExtension; use PHPUnit\Framework\Attributes\CoversClass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; @@ -109,9 +112,27 @@ public static function provideTestLoad(): iterable ] ), new ServiceConfiguration( - 'draw.mailer.event_listener.email_writer_listener', + 'draw.mailer.email_composer', [ - EmailWriterListener::class, + EmailComposer::class, + ] + ), + new ServiceConfiguration( + 'draw.mailer.body_renderer.localize_body_renderer', + [ + LocalizeBodyRenderer::class, + ] + ), + new ServiceConfiguration( + 'draw.mailer.email_writer.add_template_header_email_writer', + [ + AddTemplateHeaderEmailWriter::class, + ] + ), + new ServiceConfiguration( + 'draw.mailer.event_listener.email_composer_listener', + [ + EmailComposerListener::class, ] ), new ServiceConfiguration( diff --git a/packages/mailer/BodyRenderer/LocalizeBodyRenderer.php b/packages/mailer/BodyRenderer/LocalizeBodyRenderer.php new file mode 100644 index 000000000..78f6d025c --- /dev/null +++ b/packages/mailer/BodyRenderer/LocalizeBodyRenderer.php @@ -0,0 +1,40 @@ +translator = $translator; + } + } + + public function render(Message $message): void + { + $currentLocale = null; + if ($this->translator && $message instanceof LocalizeEmailInterface && $message->getLocale()) { + $currentLocale = $this->translator->getLocale(); + $this->translator->setLocale($message->getLocale()); + } + + try { + $this->bodyRenderer->render($message); + } finally { + if ($currentLocale) { + $this->translator?->setLocale($currentLocale); + } + } + } +} diff --git a/packages/mailer/Email/CallToActionEmail.php b/packages/mailer/Email/CallToActionEmail.php index 16ef3a9a2..f4406945c 100644 --- a/packages/mailer/Email/CallToActionEmail.php +++ b/packages/mailer/Email/CallToActionEmail.php @@ -4,8 +4,10 @@ use Symfony\Bridge\Twig\Mime\TemplatedEmail; -class CallToActionEmail extends TemplatedEmail +class CallToActionEmail extends TemplatedEmail implements LocalizeEmailInterface { + use LocalizeEmailTrait; + private ?string $callToActionLink = null; public array $translationTokens = []; diff --git a/packages/mailer/Email/LocalizeEmailInterface.php b/packages/mailer/Email/LocalizeEmailInterface.php new file mode 100644 index 000000000..031acdff7 --- /dev/null +++ b/packages/mailer/Email/LocalizeEmailInterface.php @@ -0,0 +1,8 @@ +locale = $locale; + + return $this; + } + + public function getLocale(): ?string + { + return $this->locale; + } +} diff --git a/packages/mailer/EventListener/EmailWriterListener.php b/packages/mailer/EmailComposer.php similarity index 53% rename from packages/mailer/EventListener/EmailWriterListener.php rename to packages/mailer/EmailComposer.php index e44647f76..a04c10d87 100644 --- a/packages/mailer/EventListener/EmailWriterListener.php +++ b/packages/mailer/EmailComposer.php @@ -1,32 +1,56 @@ ['composeMessage', 200], - ]; + private ?LocaleAwareInterface $translator = null; + + public function __construct( + private ContainerInterface $serviceLocator, + TranslatorInterface $translator + ) { + if ($translator instanceof LocaleAwareInterface) { + $this->translator = $translator; + } } - public function __construct(private ContainerInterface $serviceLocator) + public function compose(Message $message, Envelope $envelope): void { + $currentLocale = null; + + if ($this->translator && $message instanceof LocalizeEmailInterface && $message->getLocale()) { + $currentLocale = $this->translator->getLocale(); + $this->translator->setLocale($message->getLocale()); + } + + try { + foreach ($this->getTypes($message) as $type) { + foreach ($this->getWriters($type) as $writerConfiguration) { + [$writer, $writerMethod] = $writerConfiguration; + $writer = $writer instanceof EmailWriterInterface ? $writer : $this->serviceLocator->get($writer); + \call_user_func([$writer, $writerMethod], $message, $envelope); + } + } + } finally { + if ($currentLocale) { + $this->translator?->setLocale($currentLocale); + } + } } public function registerEmailWriter(EmailWriterInterface $emailWriter): void @@ -78,46 +102,6 @@ private function sortWriters(string $email): void $this->sortedWriters[$email] = array_merge(...$this->writers[$email]); } - public function composeMessage(MessageEvent $event): void - { - if ($event->isQueued()) { - return; - } - - $message = $event->getMessage(); - if (!$message instanceof Message) { - return; - } - - $headers = $message->getHeaders(); - if ($headers->has('X-DrawEmail')) { - return; - } - - $headers->add(new UnstructuredHeader('X-DrawEmail', '1')); - - $envelope = $event->getEnvelope(); - - $types = $this->getTypes($message); - - foreach ($types as $type) { - foreach ($this->getWriters($type) as $writerConfiguration) { - [$writer, $writerMethod] = $writerConfiguration; - $writer = $writer instanceof EmailWriterInterface ? $writer : $this->serviceLocator->get($writer); - \call_user_func([$writer, $writerMethod], $message, $envelope); - } - } - - if ($message instanceof TemplatedEmail) { - if ($template = $message->getHtmlTemplate()) { - $headers->add(new UnstructuredHeader('X-DrawEmail-HtmlTemplate', $template)); - } - if ($template = $message->getTextTemplate()) { - $headers->add(new UnstructuredHeader('X-DrawEmail-TextTemplate', $template)); - } - } - } - private function getTypes(RawMessage $message): array { return [$message::class] diff --git a/packages/mailer/EmailWriter/AddTemplateHeaderEmailWriter.php b/packages/mailer/EmailWriter/AddTemplateHeaderEmailWriter.php new file mode 100644 index 000000000..5b1cf5243 --- /dev/null +++ b/packages/mailer/EmailWriter/AddTemplateHeaderEmailWriter.php @@ -0,0 +1,27 @@ + -255, + ]; + } + + public function addHeader(TemplatedEmail $message): void + { + $headers = $message->getHeaders(); + if ($template = $message->getHtmlTemplate()) { + $headers->add(new UnstructuredHeader('X-DrawEmail-HtmlTemplate', $template)); + } + if ($template = $message->getTextTemplate()) { + $headers->add(new UnstructuredHeader('X-DrawEmail-TextTemplate', $template)); + } + } +} diff --git a/packages/mailer/EventListener/EmailComposerListener.php b/packages/mailer/EventListener/EmailComposerListener.php new file mode 100644 index 000000000..6605006de --- /dev/null +++ b/packages/mailer/EventListener/EmailComposerListener.php @@ -0,0 +1,38 @@ +isQueued()) { + return; + } + + $message = $event->getMessage(); + if (!$message instanceof Message) { + return; + } + + $headers = $message->getHeaders(); + if ($headers->has('X-DrawEmail')) { + return; + } + + $headers->add(new UnstructuredHeader('X-DrawEmail', '1')); + + $this->emailComposer->compose($message, $event->getEnvelope()); + } +} diff --git a/packages/mailer/Recipient/LocalizationAwareInterface.php b/packages/mailer/Recipient/LocalizationAwareInterface.php new file mode 100644 index 000000000..a2b699647 --- /dev/null +++ b/packages/mailer/Recipient/LocalizationAwareInterface.php @@ -0,0 +1,8 @@ +object = new EmailWriterListener( - $this->serviceLocator = $this->createMock(ContainerInterface::class) - ); - } + private Translator&MockObject $translator; - public function testConstruct(): void + protected function setUp(): void { - static::assertInstanceOf( - EventSubscriberInterface::class, - $this->object - ); - } - - public function testGetSubscribedEvents(): void - { - static::assertSame( - [ - MessageEvent::class => ['composeMessage', 200], - ], - $this->object::getSubscribedEvents() + $this->object = new EmailComposer( + $this->serviceLocator = $this->createMock(ContainerInterface::class), + $this->translator = $this->createMock(Translator::class) ); } @@ -83,42 +65,11 @@ public function testWriterMutator(): void ); } - public function testComposeMessageNotMessage(): void - { - $this->serviceLocator - ->expects(static::never()) - ->method('get'); - - $this->object->composeMessage( - $this->createMessageEvent( - $this->createMock(RawMessage::class) - ) - ); - } - - public function testComposeMessageComposed(): void - { - $this->serviceLocator - ->expects(static::never()) - ->method('get'); - - $message = $this->createMock(Message::class); - - $message - ->expects(static::once()) - ->method('getHeaders') - ->willReturn($headers = new Headers()); - - $headers->add(new UnstructuredHeader('X-DrawEmail', '1')); - - $this->object->composeMessage($this->createMessageEvent($message)); - } - public function testComposeMessage(): void { $message = new TemplatedEmail(); - $event = $this->createMessageEvent($message); + $envelope = new Envelope(new Address('test@example.com'), [new Address('test@example.com')]); $this->object->addWriter(Message::class, $writer1 = uniqid('writer-1-'), 'method1'); $this->object->addWriter(Email::class, $writer2 = uniqid('writer-2-'), 'method2'); @@ -142,66 +93,25 @@ public function testComposeMessage(): void ->method('method1') ->with( $message, - $event->getEnvelope() + $envelope ); $emailWriter ->expects(static::once()) ->method('method2') ->with( - static::callback( - function (TemplatedEmail $templatedEmail) use ($message): bool { - static::assertSame($message, $templatedEmail); - - $templatedEmail->htmlTemplate('html-template'); - $templatedEmail->textTemplate('text-template'); - - return true; - }, - ), - $event->getEnvelope() + $message, + $envelope ); - $this->object->composeMessage($event); - - $headers = $message->getHeaders(); - - static::assertTrue($headers->has('X-DrawEmail')); - static::assertSame( - 'html-template', - $headers->get('X-DrawEmail-HtmlTemplate')->getBodyAsString() - ); - static::assertSame( - 'text-template', - $headers->get('X-DrawEmail-TextTemplate')->getBodyAsString() - ); - } - - public function testComposeMessageQueued(): void - { - $this->serviceLocator - ->expects(static::never()) - ->method('get'); - - $message = $this->createMock(Message::class); - - $message - ->expects(static::never()) - ->method('getHeaders'); - - $this->object->composeMessage($this->createMessageEvent($message, true)); + $this->object->compose($message, $envelope); } public function testRegisterEmailWriter(): void { $message = $this->createMock(Email::class); - $message - ->expects(static::once()) - ->method('getHeaders') - ->willReturn(new Headers()); - - $event = $this->createMessageEvent($message); + $envelope = new Envelope(new Address('test@example.com'), [new Address('test@example.com')]); $emailWriter = new class() implements EmailWriterInterface { public int $compose1CallCounter = 0; @@ -247,19 +157,39 @@ public function compose2(Message $email): void ->expects(static::never()) ->method('get'); - $this->object->composeMessage($event); + $this->object->compose($message, $envelope); static::assertSame(1, $emailWriter->compose1CallCounter); static::assertSame(1, $emailWriter->compose2CallCounter); } - private function createMessageEvent(RawMessage $message, bool $queue = false): MessageEvent + public function testComposeLocalizeEmail(): void { - return new MessageEvent( + $message = new class() extends Email implements LocalizeEmailInterface { + public function getLocale(): ?string + { + return 'fr'; + } + }; + + $this->translator + ->expects(static::once()) + ->method('getLocale') + ->willReturn('en'); + + $this->translator + ->expects(static::exactly(2)) + ->method('setLocale') + ->with( + ...static::withConsecutive( + ['fr'], + ['en'] + ) + ); + + $this->object->compose( $message, - new Envelope(new Address('test@example.com'), [new Address('test@example.com')]), - uniqid('transport-'), - $queue + new Envelope(new Address('test@example.com'), [new Address('test@example.com')]) ); } } diff --git a/packages/mailer/Tests/EmailWriter/AddTemplateHeaderEmailWriterTest.php b/packages/mailer/Tests/EmailWriter/AddTemplateHeaderEmailWriterTest.php new file mode 100644 index 000000000..bff7df320 --- /dev/null +++ b/packages/mailer/Tests/EmailWriter/AddTemplateHeaderEmailWriterTest.php @@ -0,0 +1,56 @@ +object = new AddTemplateHeaderEmailWriter(); + } + + public function testConstruct(): void + { + static::assertInstanceOf( + EmailWriterInterface::class, + $this->object + ); + } + + public function testGetForEmails(): void + { + static::assertSame( + ['addHeader' => -255], + $this->object::getForEmails() + ); + } + + public function testAddHeader(): void + { + $message = new TemplatedEmail(); + + $message->htmlTemplate('html-template'); + $message->textTemplate('text-template'); + + $this->object->addHeader($message); + + static::assertSame( + 'html-template', + $message->getHeaders()->get('X-DrawEmail-HtmlTemplate')->getBodyAsString() + ); + + static::assertSame( + 'text-template', + $message->getHeaders()->get('X-DrawEmail-TextTemplate')->getBodyAsString() + ); + } +} diff --git a/packages/mailer/Tests/EventListener/EmailComposerListenerTest.php b/packages/mailer/Tests/EventListener/EmailComposerListenerTest.php new file mode 100644 index 000000000..a482132f5 --- /dev/null +++ b/packages/mailer/Tests/EventListener/EmailComposerListenerTest.php @@ -0,0 +1,104 @@ +object = new EmailComposerListener( + $this->emailComposer = $this->createMock(EmailComposer::class) + ); + } + + public function testComposeMessageNotMessage(): void + { + $this->emailComposer + ->expects(static::never()) + ->method('compose'); + + $this->object->composeMessage( + $this->createMessageEvent( + $this->createMock(RawMessage::class) + ) + ); + } + + public function testComposeMessageComposed(): void + { + $this->emailComposer + ->expects(static::never()) + ->method('compose'); + + $message = $this->createMock(Message::class); + + $message + ->expects(static::once()) + ->method('getHeaders') + ->willReturn($headers = new Headers()); + + $headers->add(new UnstructuredHeader('X-DrawEmail', '1')); + + $this->object->composeMessage($this->createMessageEvent($message)); + } + + public function testComposeMessage(): void + { + $message = new TemplatedEmail(); + + $event = $this->createMessageEvent($message); + + $this->emailComposer + ->expects(static::once()) + ->method('compose'); + + $this->object->composeMessage($event); + + $headers = $message->getHeaders(); + + static::assertTrue($headers->has('X-DrawEmail')); + } + + public function testComposeMessageQueued(): void + { + $this->emailComposer + ->expects(static::never()) + ->method('compose'); + + $message = $this->createMock(Message::class); + + $this->object->composeMessage($this->createMessageEvent($message, true)); + } + + private function createMessageEvent(RawMessage $message, bool $queue = false): MessageEvent + { + return new MessageEvent( + $message, + new Envelope(new Address('test@example.com'), [new Address('test@example.com')]), + uniqid('transport-'), + $queue + ); + } +} diff --git a/packages/sonata-integration-bundle/User/Controller/LoginController.php b/packages/sonata-integration-bundle/User/Controller/LoginController.php index e9fc5a7ad..f8d11889f 100644 --- a/packages/sonata-integration-bundle/User/Controller/LoginController.php +++ b/packages/sonata-integration-bundle/User/Controller/LoginController.php @@ -38,7 +38,10 @@ public function forgotPasswordAction( $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $mailer->send(new ForgotPasswordEmail($form->get('email')->getData())); + $mailer->send( + (new ForgotPasswordEmail($form->get('email')->getData())) + ->setLocale($request->getLocale()) + ); return new RedirectResponse($this->generateUrl('admin_check_email')); } diff --git a/packages/tester-bundle/EventDispatcher/EventListenerTestTrait.php b/packages/tester-bundle/EventDispatcher/EventListenerTestTrait.php new file mode 100644 index 000000000..1bd381027 --- /dev/null +++ b/packages/tester-bundle/EventDispatcher/EventListenerTestTrait.php @@ -0,0 +1,27 @@ +get('event_dispatcher')->getListeners(); + + $events = []; + foreach ($listeners as $eventName => $eventListeners) { + foreach ($eventListeners as $eventListener) { + if ($eventListener[0] instanceof $className) { + $events[$eventName][] = $eventListener[1]; + } + } + } + + ksort($events); + ksort($expectedEvents); + + TestCase::assertSame($expectedEvents, $events); + } +} diff --git a/packages/user-bundle/Email/ToUserEmailTrait.php b/packages/user-bundle/Email/ToUserEmailTrait.php index aab30815b..a499d7247 100644 --- a/packages/user-bundle/Email/ToUserEmailTrait.php +++ b/packages/user-bundle/Email/ToUserEmailTrait.php @@ -2,10 +2,27 @@ namespace Draw\Bundle\UserBundle\Email; +use Draw\Bundle\UserBundle\Entity\SecurityUserInterface; +use Draw\Component\Mailer\Email\LocalizeEmailTrait; +use Draw\Component\Mailer\Recipient\LocalizationAwareInterface; + trait ToUserEmailTrait { + use LocalizeEmailTrait; + private string|int|null $userId = null; + public function toUser(SecurityUserInterface $user): self + { + $this->userId = $user->getId(); + + if ($user instanceof LocalizationAwareInterface) { + $this->setLocale($user->getPreferredLocale()); + } + + return $this; + } + public function setUserId(string|int $userId): self { $this->userId = $userId; diff --git a/packages/user-bundle/MessageHandler/NewUserSendEmailMessageHandler.php b/packages/user-bundle/MessageHandler/NewUserSendEmailMessageHandler.php index 2ed125b89..6a00d1d92 100644 --- a/packages/user-bundle/MessageHandler/NewUserSendEmailMessageHandler.php +++ b/packages/user-bundle/MessageHandler/NewUserSendEmailMessageHandler.php @@ -5,6 +5,7 @@ use Doctrine\ORM\EntityRepository; use Draw\Bundle\UserBundle\Email\UserOnboardingEmail; use Draw\Bundle\UserBundle\Message\NewUserMessage; +use Draw\Component\Mailer\Recipient\LocalizationAwareInterface; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -29,14 +30,18 @@ public function __invoke(NewUserMessage $message): void { $user = $this->drawUserEntityRepository->find($message->getUserId()); - if (null === $user) { - return; - } - if (!method_exists($user, 'getEmail') || empty($user->getEmail())) { return; } - $this->mailer->send((new UserOnboardingEmail())->setUserId($message->getUserId())); + $this->mailer->send( + (new UserOnboardingEmail()) + ->setUserId($message->getUserId()) + ->setLocale( + $user instanceof LocalizationAwareInterface ? + $user->getPreferredLocale() : + null + ) + ); } } diff --git a/packages/user-bundle/MessageHandler/PasswordChangeRequestedSendEmailMessageHandler.php b/packages/user-bundle/MessageHandler/PasswordChangeRequestedSendEmailMessageHandler.php index 80844a210..a75a5d5f2 100644 --- a/packages/user-bundle/MessageHandler/PasswordChangeRequestedSendEmailMessageHandler.php +++ b/packages/user-bundle/MessageHandler/PasswordChangeRequestedSendEmailMessageHandler.php @@ -6,6 +6,7 @@ use Draw\Bundle\UserBundle\Email\PasswordChangeRequestedEmail; use Draw\Bundle\UserBundle\Entity\PasswordChangeUserInterface; use Draw\Bundle\UserBundle\Message\PasswordChangeRequestedMessage; +use Draw\Component\Mailer\Recipient\LocalizationAwareInterface; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -38,6 +39,14 @@ public function __invoke(PasswordChangeRequestedMessage $message): void return; } - $this->mailer->send((new PasswordChangeRequestedEmail())->setUserId($user->getId())); + $this->mailer->send( + (new PasswordChangeRequestedEmail()) + ->setUserId($user->getId()) + ->setLocale( + $user instanceof LocalizationAwareInterface + ? $user->getPreferredLocale() + : null + ) + ); } } diff --git a/packages/user-bundle/Resources/translations/DrawEmail.en.yaml b/packages/user-bundle/Resources/translations/DrawEmail.en.yaml index 1bd013b5a..e1d656c3a 100644 --- a/packages/user-bundle/Resources/translations/DrawEmail.en.yaml +++ b/packages/user-bundle/Resources/translations/DrawEmail.en.yaml @@ -32,7 +32,7 @@ email: subject: 'Welcome' top_section: |

Your account have been created.

-

Please click on the button below to finally your account creation.

+

Please click on the button below to finalise your account creation.

call_to_action_text: 'Finalise my account creation' bottom_section: |

Follow the instruction on the site to finalise your account creation.

diff --git a/packages/user-bundle/Resources/translations/DrawEmail.fr.yaml b/packages/user-bundle/Resources/translations/DrawEmail.fr.yaml index 9a9dfe882..84c366be0 100644 --- a/packages/user-bundle/Resources/translations/DrawEmail.fr.yaml +++ b/packages/user-bundle/Resources/translations/DrawEmail.fr.yaml @@ -20,3 +20,12 @@ email: bottom_section: |

Si vous n'êtes pas l'auteur de cette demande, peut-être que quelqu'un à fait une erreur.

Peut-être seriez vous intéressé par notre site web ? Cliquer sur le lien plus haut pour en savoir plus.

+ + user_onboarding: + subject: 'Bienvenue' + top_section: | +

Votre compte a été créé.

+

Veuillez cliquer sur le bouton ci-dessous pour finaliser la création de votre compte.

+ call_to_action_text: 'Finaliser la création de mon compte' + bottom_section: | +

Suivez les instructions sur le site pour finaliser la création de votre compte.

diff --git a/tests/Mailer/EventListener/EmailComposerListenerTest.php b/tests/Mailer/EventListener/EmailComposerListenerTest.php new file mode 100644 index 000000000..eb71c7151 --- /dev/null +++ b/tests/Mailer/EventListener/EmailComposerListenerTest.php @@ -0,0 +1,30 @@ +object = static::getContainer()->get(EmailComposerListener::class); + } + + public function testEventListenersRegistered(): void + { + static::assertEventListenersRegistered( + $this->object::class, + [ + MessageEvent::class => ['composeMessage'], + ] + ); + } +}