From a0b5c208eb05bb81b8385664e61dbb19e2c977c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 4 Oct 2024 13:40:50 +0200 Subject: [PATCH] Send an email when a workflow is send to an external - create an event subscriber to catch the workflow which arrive to a "sentExternal" step; - add a messenger's message to handle the generation of the email; - add a simple message, and a simple controller for viewing the document - add dedicated tests --- .../WorkflowViewSendPublicController.php | 25 ++++ .../Entity/Workflow/EntityWorkflowSend.php | 10 ++ .../Entity/Workflow/EntityWorkflowStep.php | 8 + ..._send_external_email_to_destinee.html.twig | 6 + ...EmailOnSendExternalEventSubscriberTest.php | 140 ++++++++++++++++++ .../PostSendExternalMessageHandlerTest.php | 77 ++++++++++ ...pareEmailOnSendExternalEventSubscriber.php | 68 +++++++++ .../Messenger/PostSendExternalMessage.php | 20 +++ .../PostSendExternalMessageHandler.php | 57 +++++++ .../ChillMainBundle/config/services.yaml | 8 +- .../translations/messages+intl-icu.fr.yaml | 2 + 11 files changed, 417 insertions(+), 4 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Controller/WorkflowViewSendPublicController.php create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_external_email_to_destinee.html.twig create mode 100644 src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowPrepareEmailOnSendExternalEventSubscriberTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Workflow/Messenger/PostSendExternalMessageHandlerTest.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowPrepareEmailOnSendExternalEventSubscriber.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Messenger/PostSendExternalMessage.php create mode 100644 src/Bundle/ChillMainBundle/Workflow/Messenger/PostSendExternalMessageHandler.php diff --git a/src/Bundle/ChillMainBundle/Controller/WorkflowViewSendPublicController.php b/src/Bundle/ChillMainBundle/Controller/WorkflowViewSendPublicController.php new file mode 100644 index 000000000..88269d5bb --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/WorkflowViewSendPublicController.php @@ -0,0 +1,25 @@ +uuid; } + public function getExpireAt(): \DateTimeImmutable + { + return $this->expireAt; + } + + public function getViews(): Collection + { + return $this->views; + } + public function increaseErrorTrials(): void { $this->numberOfErrorTrials = $this->numberOfErrorTrials + 1; diff --git a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php index b0f4d4bf5..cd60d78d1 100644 --- a/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php +++ b/src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflowStep.php @@ -303,6 +303,14 @@ class EntityWorkflowStep return $this->signatures; } + /** + * @return Collection + */ + public function getSends(): Collection + { + return $this->sends; + } + public function getId(): ?int { return $this->id; diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_external_email_to_destinee.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_external_email_to_destinee.html.twig new file mode 100644 index 000000000..3dc31f1a6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/workflow_send_external_email_to_destinee.html.twig @@ -0,0 +1,6 @@ + +Un message vous a été envoyé. Vous pouvez le consulter à cette adresse + +{{ absolute_url(path('chill_main_workflow_send_view_public', {'uuid': send.uuid, 'verificationKey': send.privateToken})) }} + +{{ 'workflow.send_external_message.document_available_until'|trans({ 'expiration': send.expireAt}, null, lang) }} diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowPrepareEmailOnSendExternalEventSubscriberTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowPrepareEmailOnSendExternalEventSubscriberTest.php new file mode 100644 index 000000000..a72990af6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowPrepareEmailOnSendExternalEventSubscriberTest.php @@ -0,0 +1,140 @@ +prophesize(MessageBusInterface::class); + $messageBus->dispatch(Argument::type(PostSendExternalMessage::class)) + ->will(fn ($args) => new Envelope($args[0])) + ->shouldBeCalled(); + + $registry = $this->buildRegistry($messageBus->reveal()); + + $entityWorkflow = $this->buildEntityWorkflow(); + $dto = new WorkflowTransitionContextDTO($entityWorkflow); + + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $workflow->apply( + $entityWorkflow, + $this->transitionSendExternal->getName(), + ['context' => $dto, 'byUser' => new User(), 'transition' => $this->transitionSendExternal->getName(), + 'transitionAt' => new \DateTimeImmutable()] + ); + + // at this step, prophecy should check that the dispatch method has been called + } + + public function testToRegularDoNotGenerateMessage(): void + { + $messageBus = $this->prophesize(MessageBusInterface::class); + $messageBus->dispatch(Argument::type(PostSendExternalMessage::class)) + ->shouldNotBeCalled(); + + $registry = $this->buildRegistry($messageBus->reveal()); + + $entityWorkflow = $this->buildEntityWorkflow(); + $dto = new WorkflowTransitionContextDTO($entityWorkflow); + + $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $workflow->apply( + $entityWorkflow, + $this->transitionRegular->getName(), + ['context' => $dto, 'byUser' => new User(), 'transition' => $this->transitionRegular->getName(), + 'transitionAt' => new \DateTimeImmutable()] + ); + + // at this step, prophecy should check that the dispatch method has been called + } + + private function buildEntityWorkflow(): EntityWorkflow + { + $entityWorkflow = new EntityWorkflow(); + $entityWorkflow->setWorkflowName('dummy'); + + // set an id + $reflectionClass = new \ReflectionClass($entityWorkflow); + $idProperty = $reflectionClass->getProperty('id'); + $idProperty->setValue($entityWorkflow, 1); + + return $entityWorkflow; + } + + private function buildRegistry(MessageBusInterface $messageBus): Registry + { + $builder = new DefinitionBuilder( + ['initial', 'sendExternal', 'regular'], + [ + $this->transitionSendExternal = new Transition('toSendExternal', 'initial', 'sendExternal'), + $this->transitionRegular = new Transition('toRegular', 'initial', 'regular'), + ] + ); + + $builder + ->setInitialPlaces('initial') + ->setMetadataStore(new InMemoryMetadataStore( + placesMetadata: [ + 'sendExternal' => ['isSentExternal' => true], + ] + )); + + $entityMarkingStore = new EntityWorkflowMarkingStore(); + $registry = new Registry(); + + $eventSubscriber = new EntityWorkflowPrepareEmailOnSendExternalEventSubscriber($registry, $messageBus); + $eventSubscriber->setLocale('fr'); + $eventDispatcher = new EventDispatcher(); + $eventDispatcher->addSubscriber($eventSubscriber); + + $workflow = new Workflow($builder->build(), $entityMarkingStore, $eventDispatcher, 'dummy'); + $registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface { + public function supports(WorkflowInterface $workflow, object $subject): bool + { + return true; + } + }); + + return $registry; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/Messenger/PostSendExternalMessageHandlerTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/Messenger/PostSendExternalMessageHandlerTest.php new file mode 100644 index 000000000..a6f3172a7 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/Messenger/PostSendExternalMessageHandlerTest.php @@ -0,0 +1,77 @@ +buildEntityWorkflow(); + $dto = new WorkflowTransitionContextDTO($entityWorkflow); + $dto->futureDestineeEmails = ['external@example.com']; + $dto->futureDestineeThirdParties = [(new ThirdParty())->setEmail('3party@example.com')]; + $entityWorkflow->setStep('send_external', $dto, 'to_send_external', new \DateTimeImmutable(), new User()); + + $repository = $this->prophesize(EntityWorkflowRepository::class); + $repository->find(1)->willReturn($entityWorkflow); + + $mailer = $this->prophesize(MailerInterface::class); + $mailer->send(Argument::that($this->buildCheckAddressCallback('3party@example.com')))->shouldBeCalledOnce(); + $mailer->send(Argument::that($this->buildCheckAddressCallback('external@example.com')))->shouldBeCalledOnce(); + + $bodyRenderer = $this->prophesize(BodyRendererInterface::class); + $bodyRenderer->render(Argument::type(TemplatedEmail::class))->shouldBeCalledTimes(2); + + $handler = new PostSendExternalMessageHandler($repository->reveal(), $mailer->reveal(), $bodyRenderer->reveal()); + + $handler(new PostSendExternalMessage(1, 'fr')); + + // prophecy should do the check at the end of this test + } + + private function buildCheckAddressCallback(string $emailToCheck): callable + { + return fn(TemplatedEmail $email): bool => in_array($emailToCheck, array_map(fn (Address $addr) => $addr->getAddress(), $email->getTo()), true); + } + + private function buildEntityWorkflow(): EntityWorkflow + { + $entityWorkflow = new EntityWorkflow(); + $reflection = new \ReflectionClass($entityWorkflow); + $idProperty = $reflection->getProperty('id'); + $idProperty->setValue($entityWorkflow, 1); + + return $entityWorkflow; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowPrepareEmailOnSendExternalEventSubscriber.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowPrepareEmailOnSendExternalEventSubscriber.php new file mode 100644 index 000000000..9eeaaeaa5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/EntityWorkflowPrepareEmailOnSendExternalEventSubscriber.php @@ -0,0 +1,68 @@ + 'onWorkflowCompleted', + ]; + } + + public function onWorkflowCompleted(CompletedEvent $event): void + { + $entityWorkflow = $event->getSubject(); + + if (!$entityWorkflow instanceof EntityWorkflow) { + return; + } + + $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); + $store = $workflow->getMetadataStore(); + + $mustSend = false; + foreach ($event->getTransition()->getTos() as $to) { + $metadata = $store->getPlaceMetadata($to); + if ($metadata['isSentExternal'] ?? false) { + $mustSend = true; + } + } + + if ($mustSend) { + $this->messageBus->dispatch(new PostSendExternalMessage($entityWorkflow->getId(), $this->getLocale())); + } + } + + public function setLocale(string $locale): void + { + $this->locale = $locale; + } + + public function getLocale(): string + { + return $this->locale; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSendExternalMessage.php b/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSendExternalMessage.php new file mode 100644 index 000000000..40ee49bbc --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Messenger/PostSendExternalMessage.php @@ -0,0 +1,20 @@ +entityWorkflowRepository->find($message->entityWorkflowId); + + if (null === $entityWorkflow) { + throw new UnrecoverableMessageHandlingException(sprintf('Entity workflow with id %d not found', $message->entityWorkflowId)); + } + + foreach ($entityWorkflow->getCurrentStep()->getSends() as $send) { + $this->sendEmailToDestinee($send, $message); + } + } + + private function sendEmailToDestinee(EntityWorkflowSend $send, PostSendExternalMessage $message): void + { + $email = new TemplatedEmail(); + $email + ->to($send->getDestineeThirdParty()?->getEmail() ?? $send->getDestineeEmail()) + ->htmlTemplate('@ChillMain/Workflow/workflow_send_external_email_to_destinee.html.twig') + ->context([ + 'send' => $send, + 'lang' => $message->lang, + ]); + + $this->bodyRenderer->render($email); + $this->mailer->send($email); + } +} diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index 8b4aaa7bf..86f15097b 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -33,15 +33,15 @@ services: # workflow related Chill\MainBundle\Workflow\: resource: '../Workflow/' - autowire: true - autoconfigure: true Chill\MainBundle\Workflow\EntityWorkflowManager: - autoconfigure: true - autowire: true arguments: $handlers: !tagged_iterator chill_main.workflow_handler + # seems to have no alias on symfony 5.4 + Symfony\Component\Mime\BodyRendererInterface: + alias: 'twig.mime_body_renderer' + # other stuffes chill.main.helper.translatable_string: diff --git a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml index 9100f8ab8..59374ca57 100644 --- a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml @@ -67,6 +67,8 @@ workflow: one {Signature demandée} other {Signatures demandées} } + send_external_message: + document_available_until: Le lien sera valable jusqu'au {expiration, date, long} à {expiration, time, short}. duration: minute: >-