From 27a4d206fce3e3a920c370e1ee327fb641ec3741 Mon Sep 17 00:00:00 2001 From: doobry Date: Thu, 21 Feb 2019 18:19:35 +0100 Subject: [PATCH] Send mail to user at alias creation (Fixes: #108) --- config/services.yaml | 9 +++ default_translations/de/messages.de.yml | 45 ++++++++--- default_translations/en/messages.en.yml | 56 +++++++------ src/Builder/AliasCreatedMessageBuilder.php | 79 +++++++++++++++++++ src/Builder/RecoveryProcessMessageBuilder.php | 7 +- src/Builder/WelcomeMessageBuilder.php | 8 +- src/Creator/AliasCreator.php | 2 + src/Event/AliasCreatedEvent.php | 27 +++++++ src/EventListener/AliasCreationListener.php | 76 ++++++++++++++++++ src/Sender/AliasCreatedMessageSender.php | 59 ++++++++++++++ src/Sender/WelcomeMessageSender.php | 2 +- .../AliasCreatedMessageBuilderTest.php | 62 +++++++++++++++ .../RecoveryProcessMessageBuilderTest.php | 2 +- tests/Builder/WelcomeMessageBuilderTest.php | 4 +- 14 files changed, 395 insertions(+), 43 deletions(-) create mode 100644 src/Builder/AliasCreatedMessageBuilder.php create mode 100644 src/Event/AliasCreatedEvent.php create mode 100644 src/EventListener/AliasCreationListener.php create mode 100644 src/Sender/AliasCreatedMessageSender.php create mode 100644 tests/Builder/AliasCreatedMessageBuilderTest.php diff --git a/config/services.yaml b/config/services.yaml index bf3e9e9c..4a4df419 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -32,6 +32,11 @@ services: resource: '../src/Controller' tags: ['controller.service_arguments'] + App\Builder\AliasCreatedMessageBuilder: + arguments: + $appUrl: "%env(APP_URL)%" + $projectName: "%env(PROJECT_NAME)%" + App\Builder\RecoveryProcessMessageBuilder: arguments: $appUrl: "%env(APP_URL)%" @@ -109,6 +114,10 @@ services: App\Helper\PasswordUpdater: public: true + App\EventListener\AliasCreationListener: + arguments: + $sendMail: "%env(SEND_MAIL)%" + App\EventListener\RecoveryProcessListener: arguments: $sendMail: "%env(SEND_MAIL)%" diff --git a/default_translations/de/messages.de.yml b/default_translations/de/messages.de.yml index d88881f5..b0852e4f 100644 --- a/default_translations/de/messages.de.yml +++ b/default_translations/de/messages.de.yml @@ -97,17 +97,6 @@ recovery: waiting-info: Die Wartezeit dient deiner eigenen Sicherheit und kann nicht verkürzt oder umgangen werden. waiting-time: Der zweite Schritt ist möglich ab dem %time%. reset-lead: Du kannst nun dein Passwort zurücksetzen - email-subject: Das Passwort zu deinem E-Mail-Account %email% wird zurückgesetzt - email-body: | - Hallo %email%, - - Jemand (hoffentlich du selbst) hat versucht, dein Passwort zurückzusetzen. Dazu wurden deine E-Mail-Addresse und der zugehörige Wiederherstellungscode unter %app_url%/recovery eingegeben. - - Nun startet die 48-stündige Wartezeit. Diese geht bis zum %time%. Anschließend kann in einem zweiten Schritt dein Passwort neu gesetzt werden. - - Du hast das nicht selbst ausgelöst? Jemand anderes mit Zugang zu deinem Wiederherstellungscode versucht, Kontrolle über deinen Account zu bekommen. Gehe schnellstmöglich zu %app_url%/user/recovery_token und erstelle dir einen neuen Wiederherstellungscode. Dadurch wird der alte ungültig. - - Weiterhin viel Spaß mit deinem Mailaccount, wünscht dir dein %project_name% Admin Team. navbar_left: about-us: @@ -212,3 +201,37 @@ Bad credentials.: Fehlerhafte Zugangsdaten custom: gewählten random: zufälligen + +mail: + welcome-subject: Willkommen bei %domain% + welcome-body: | + Lieber User, liebe Userin, willkommen an Bord. + + Im Web findest du Anleitungen zur Einrichtung deines E-Mail Accounts mit verschiedenen Client Programmen. + + Dein Postfachgröße beträgt 1GB. Sollte dein Postfach diese Größe überschreiten, kann es zu Einschränkungen der Funktionalität deines E-Mail Accounts kommen. + + In einer Woche bekommst du drei Einladungscodes gutgeschrieben, die du unter %app_url% abrufen kannst. Diese kannst du an Freund*innen weiterleiten und sie so zu %project_name% einladen. + + Viel Spaß mit deinem Mailaccount, wünscht dir dein %project_name% Admin Team. + recovery-subject: Das Passwort zu deinem E-Mail-Account %email% wird zurückgesetzt + recovery-body: | + Hallo %email%, + + Jemand (hoffentlich du selbst) hat versucht, dein Passwort zurückzusetzen. Dazu wurden deine E-Mail-Addresse und der zugehörige Wiederherstellungscode unter %app_url%/recovery eingegeben. + + Nun startet die 48-stündige Wartezeit. Diese geht bis zum %time%. Anschließend kann in einem zweiten Schritt dein Passwort neu gesetzt werden. + + Du hast das nicht selbst ausgelöst? Jemand anderes mit Zugang zu deinem Wiederherstellungscode versucht, Kontrolle über deinen Account zu bekommen. Gehe schnellstmöglich zu %app_url%/user/recovery_token und erstelle dir einen neuen Wiederherstellungscode. Dadurch wird der alte ungültig. + + Weiterhin viel Spaß mit deinem Mailaccount, wünscht dir dein %project_name% Admin Team. + alias-created-subject: Neue Alias-Adresse erstellt + alias-created-body: | + Hallo %email%, + + Für deine E-Mail-Adresse %email% wurde ein neues Alias angelegt: + %alias% + + Du kannst deine E-Mail-Aliase hier verwalten: %app_url%/alias + + Weiterhin viel Spaß mit deinem Mailaccount, wünscht dir dein %project_name% Admin Team. diff --git a/default_translations/en/messages.en.yml b/default_translations/en/messages.en.yml index 3f353854..12cbf58b 100644 --- a/default_translations/en/messages.en.yml +++ b/default_translations/en/messages.en.yml @@ -94,17 +94,6 @@ recovery: waiting-info: The waiting period is for your own security and cannot be shortened or skipped. waiting-time: Second step starts at %time%. reset-lead: You're now allowed to reset your password - email-subject: The password for your email account %email% will be reset - email-body: | - Hello %email%, - - Somebody (let's hope it was you) tried to reset your account password. To do so, they authenticated with your email address and corresponding recovery code at %app_url%/recovery. - - The 48 hours waiting period started now and will end at %time%. Afterwards, in a second step, a new password can be set for your account. - - You didn't trigger this yourself? Then somebody else with access to your recovery token tries to get hold of your account. You should go to %app_url%/user/recovery_token immediately and create a new recovery token. This will invalidate the current recovery token. - - We hope you continue to enjoy your email account. Your %project_name% admin team. navbar_left: about-us: @@ -134,17 +123,6 @@ welcome:

Ready to go

In the web you can find guides for settting up your email-account with several client programs.

In one week you will receive three invite codes, which you can collect at %app_url%. You can pass these on to friends and invite them to register with %project_name%.

- email: | - Hello new user, welcome on board. - - In the web you can find guides for setting up your email-account with several client programs. - - You get a mailbox with 1GB quota. If you exceed your quota limit, your email-account may not function properly. - - In one week you will receive three invite codes, which you can collect at %app_url%. You can pass these on to friends and invite them to register with %project_name%. - - - Sincerly your %project_name% admin team closed: title: Registration closed @@ -206,3 +184,37 @@ Bad credentials.: Wrong login details custom: custom random: random + +mail: + welcome-subject: Welcome to %domain% + welcome-body: | + Hello new user, welcome on board. + + In the web you can find guides for setting up your email-account with several client programs. + + You get a mailbox with 1GB quota. If you exceed your quota limit, your email-account may not function properly. + + In one week you will receive three invite codes, which you can collect at %app_url%. You can pass these on to friends and invite them to register with %project_name%. + + Sincerly, your %project_name% admin team. + recovery-subject: The password for your email account %email% will be reset + recovery-body: | + Hello %email%, + + Somebody (let's hope it was you) tried to reset your account password. To do so, they authenticated with your email address and corresponding recovery code at %app_url%/recovery. + + The 48 hours waiting period started now and will end at %time%. Afterwards, in a second step, a new password can be set for your account. + + You didn't trigger this yourself? Then somebody else with access to your recovery token tries to get hold of your account. You should go to %app_url%/user/recovery_token immediately and create a new recovery token. This will invalidate the current recovery token. + + We hope you continue to enjoy your email account. Your %project_name% admin team. + alias-created-subject: New alias address created + alias-created:body: | + Hello %email%, + + For your email address %email% a new alias has been added: + %alias% + + Manage your email aliases here: %app_url%/alias + + We hope you continue to enjoy your email account. Your %project_name% admin team. diff --git a/src/Builder/AliasCreatedMessageBuilder.php b/src/Builder/AliasCreatedMessageBuilder.php new file mode 100644 index 00000000..820eecac --- /dev/null +++ b/src/Builder/AliasCreatedMessageBuilder.php @@ -0,0 +1,79 @@ + + */ +class AliasCreatedMessageBuilder +{ + /** + * @var TranslatorInterface + */ + private $translator; + /** + * @var string + */ + private $appUrl; + /** + * @var string + */ + private $projectName; + + /** + * AliasCreatedMessageBuilder constructor. + * + * @param TranslatorInterface $translator + * @param string $appUrl + * @param string $projectName + */ + public function __construct(TranslatorInterface $translator, $appUrl, $projectName) + { + $this->translator = $translator; + $this->appUrl = $appUrl; + $this->projectName = $projectName; + } + + /** + * @param string $locale + * @param string $email + * @param string $alias + * + * @return string + */ + public function buildBody(string $locale, string $email, string $alias) + { + $body = $this->translator->trans( + 'mail.alias-created-body', + [ + '%app_url%' => $this->appUrl, + '%project_name%' => $this->projectName, + '%email%' => $email, + '%alias%' => $alias, + ], + null, + $locale + ); + + return $body; + } + + /** + * @param string $locale + * @param string $email + * + * @return string + */ + public function buildSubject(string $locale, string $email) + { + $subject = $this->translator->trans( + 'mail.alias-created-subject', ['%email%' => $email], null, $locale + ); + + return $subject; + } +} diff --git a/src/Builder/RecoveryProcessMessageBuilder.php b/src/Builder/RecoveryProcessMessageBuilder.php index 4cc60658..2f080965 100644 --- a/src/Builder/RecoveryProcessMessageBuilder.php +++ b/src/Builder/RecoveryProcessMessageBuilder.php @@ -48,11 +48,12 @@ public function __construct(TranslatorInterface $translator, $appUrl, $projectNa public function buildBody(string $locale, string $email, string $time) { $body = $this->translator->trans( - 'recovery.email-body', + 'mail.recovery-body', [ '%app_url%' => $this->appUrl, '%project_name%' => $this->projectName, - '%email%' => $email, '%time%' => $time, + '%email%' => $email, + '%time%' => $time, ], null, $locale @@ -70,7 +71,7 @@ public function buildBody(string $locale, string $email, string $time) public function buildSubject(string $locale, string $email) { $subject = $this->translator->trans( - 'recovery.email-subject', ['%email%' => $email], null, $locale + 'mail.recovery-subject', ['%email%' => $email], null, $locale ); return $subject; diff --git a/src/Builder/WelcomeMessageBuilder.php b/src/Builder/WelcomeMessageBuilder.php index 0e01cc19..de53fedc 100644 --- a/src/Builder/WelcomeMessageBuilder.php +++ b/src/Builder/WelcomeMessageBuilder.php @@ -51,17 +51,19 @@ public function __construct(TranslatorInterface $translator, $domain, $appUrl, $ */ public function buildBody($locale) { - $body = $this->translator->trans('welcome.email', ['%app_url%' => $this->appUrl, '%project_name%' => $this->projectName], null, $locale); + $body = $this->translator->trans('mail.welcome-body', ['%app_url%' => $this->appUrl, '%project_name%' => $this->projectName], null, $locale); return $body; } /** + * @param $locale + * * @return string */ - public function buildSubject() + public function buildSubject($locale) { - $subject = sprintf('Welcome to %s!', $this->domain); + $subject = $this->translator->trans('mail.welcome-subject', ['%domain%' => $this->domain], null, $locale); return $subject; } diff --git a/src/Creator/AliasCreator.php b/src/Creator/AliasCreator.php index e2265cb0..471c2476 100644 --- a/src/Creator/AliasCreator.php +++ b/src/Creator/AliasCreator.php @@ -4,6 +4,7 @@ use App\Entity\Alias; use App\Entity\User; +use App\Event\AliasCreatedEvent; use App\Event\RandomAliasCreatedEvent; use App\Factory\AliasFactory; @@ -25,6 +26,7 @@ public function create(User $user, ?string $localPart): Alias $localPart = (isset($localPart)) ? strtolower($localPart) : null; $alias = AliasFactory::create($user, $localPart); + $this->eventDispatcher->dispatch(AliasCreatedEvent::NAME, new AliasCreatedEvent($alias)); if (null === $localPart) { $this->eventDispatcher->dispatch(RandomAliasCreatedEvent::NAME, new RandomAliasCreatedEvent($alias)); } diff --git a/src/Event/AliasCreatedEvent.php b/src/Event/AliasCreatedEvent.php new file mode 100644 index 00000000..a9792fe9 --- /dev/null +++ b/src/Event/AliasCreatedEvent.php @@ -0,0 +1,27 @@ +alias = $alias; + } +} diff --git a/src/EventListener/AliasCreationListener.php b/src/EventListener/AliasCreationListener.php new file mode 100644 index 00000000..ac4a5324 --- /dev/null +++ b/src/EventListener/AliasCreationListener.php @@ -0,0 +1,76 @@ + + */ +class AliasCreationListener implements EventSubscriberInterface +{ + /** + * @var RequestStack + */ + private $request; + /** + * @var AliasCreatedMessageSender + */ + private $sender; + /** + * @var bool + */ + private $sendMail; + + /** + * AliasCreationListener constructor. + * + * @param RequestStack $request + * @param AliasCreatedMessageSender $sender + * @param bool $sendMail + */ + public function __construct( + RequestStack $request, + AliasCreatedMessageSender $sender, + bool $sendMail + ) { + $this->request = $request; + $this->sender = $sender; + $this->sendMail = $sendMail; + } + + /** + * @param AliasCreatedEvent $event + * + * @throws \Exception + */ + public function onAliasCreated(AliasCreatedEvent $event) + { + if (!$this->sendMail) { + return; + } + + if (null === $alias = $event->getAlias()) { + throw new \Exception('User should not be null'); + } + + if (null === $user = $alias->getUser()) { + throw new \Exception('User should not be null'); + } + $locale = $this->request->getCurrentRequest()->getLocale(); + $this->sender->send($user, $alias, $locale); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + AliasCreatedEvent::NAME => 'onAliasCreated', + ]; + } +} diff --git a/src/Sender/AliasCreatedMessageSender.php b/src/Sender/AliasCreatedMessageSender.php new file mode 100644 index 00000000..eb75ff66 --- /dev/null +++ b/src/Sender/AliasCreatedMessageSender.php @@ -0,0 +1,59 @@ + + */ +class AliasCreatedMessageSender +{ + /** + * @var MailHandler + */ + private $handler; + /** + * @var AliasCreatedMessageBuilder + */ + private $builder; + + /** + * AliasCreatedMessageSender constructor. + * + * @param MailHandler $handler + * @param AliasCreatedMessageBuilder $builder + */ + public function __construct(MailHandler $handler, AliasCreatedMessageBuilder $builder) + { + $this->handler = $handler; + $this->builder = $builder; + } + + /** + * @param User $user + * @param Alias $alias + * @param string $locale + * + * @throws \Exception + */ + public function send(User $user, Alias $alias, string $locale) + { + if (null === $email = $user->getEmail()) { + throw new \Exception('Email should not be null'); + } + + if (null === $aliasSource = $alias->getSource()) { + throw new \Exception('Alias should not be null'); + } + + $body = $this->builder->buildBody($locale, $email, $aliasSource); + $subject = $this->builder->buildSubject($locale, $email); + $this->handler->send($email, $body, $subject); + } +} diff --git a/src/Sender/WelcomeMessageSender.php b/src/Sender/WelcomeMessageSender.php index 443ba24c..c2b8b79a 100644 --- a/src/Sender/WelcomeMessageSender.php +++ b/src/Sender/WelcomeMessageSender.php @@ -57,7 +57,7 @@ public function send(User $user, string $locale) } $body = $this->builder->buildBody($locale); - $subject = $this->builder->buildSubject(); + $subject = $this->builder->buildSubject($locale); $this->handler->send($email, $body, $subject); } } diff --git a/tests/Builder/AliasCreatedMessageBuilderTest.php b/tests/Builder/AliasCreatedMessageBuilderTest.php new file mode 100644 index 00000000..3fecf55e --- /dev/null +++ b/tests/Builder/AliasCreatedMessageBuilderTest.php @@ -0,0 +1,62 @@ + + */ +class AliasCreatedMessageBuilderTest extends TestCase +{ + const BODY_TEMPLATE = 'APP URL: %s'.PHP_EOL.'Project Name: %s'; + + private $email = 'user@example.org'; + private $alias = 'alias@example.org'; + private $appUrl = 'https://www.example.org'; + private $projectName = 'example.org'; + + public function testBuildBody() + { + $builder = $this->getBuilder($this->appUrl, $this->projectName); + $expected = sprintf(self::BODY_TEMPLATE, $this->appUrl, $this->projectName); + + self::assertEquals($expected, $builder->buildBody('de', $this->email, $this->alias)); + } + + public function testBuildSubject() + { + $builder = $this->getBuilder($this->appUrl, $this->projectName); + $expected = sprintf(self::BODY_TEMPLATE, $this->appUrl, $this->projectName); + + self::assertEquals($expected, $builder->buildSubject('de', $this->email)); + } + + /** + * @param $appUrl + * @param $projectName + * + * @return AliasCreatedMessageBuilder + */ + private function getBuilder($appUrl, $projectName) + { + /** + * @var TranslatorInterface|PHPUnit_Framework_MockObject_MockObject + */ + $translator = $this->getMockBuilder(TranslatorInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $message = sprintf(self::BODY_TEMPLATE, $appUrl, $projectName); + + $translator->expects($this->any())->method('trans')->willReturn($message); + + $builder = new AliasCreatedMessageBuilder($translator, $appUrl, $projectName); + + return $builder; + } +} diff --git a/tests/Builder/RecoveryProcessMessageBuilderTest.php b/tests/Builder/RecoveryProcessMessageBuilderTest.php index 06cba621..4d3d6069 100644 --- a/tests/Builder/RecoveryProcessMessageBuilderTest.php +++ b/tests/Builder/RecoveryProcessMessageBuilderTest.php @@ -16,7 +16,7 @@ class RecoveryProcessMessageBuilderTest extends TestCase const BODY_TEMPLATE = 'APP URL: %s'.PHP_EOL.'Project Name: %s'; private $email = 'user@example.org'; - private $appUrl = 'https://www.example.org/'; + private $appUrl = 'https://www.example.org'; private $projectName = 'example.org'; public function testBuildBody() diff --git a/tests/Builder/WelcomeMessageBuilderTest.php b/tests/Builder/WelcomeMessageBuilderTest.php index e92080da..396517fd 100644 --- a/tests/Builder/WelcomeMessageBuilderTest.php +++ b/tests/Builder/WelcomeMessageBuilderTest.php @@ -34,9 +34,9 @@ public function testBuildSubject() $projectUrl = 'https://users.example.com'; $builder = $this->getBuilder($domain, $appUrl, $projectUrl); - $expected = sprintf('Welcome to %s!', $domain); + $expected = sprintf(self::BODY_TEMPLATE, $domain, $appUrl, $projectUrl); - self::assertEquals($expected, $builder->buildSubject()); + self::assertEquals($expected, $builder->buildSubject('en')); } /**