Browse Source

add mailerHub service, send mail after subdomain activation

Olivier Massot 3 years ago
parent
commit
b0c703c6e5

+ 4 - 1
config/services.yaml

@@ -46,6 +46,10 @@ services:
 
     Gaufrette\Filesystem: '@knp_gaufrette.filesystem_map'
 
+    App\Service\MailHub:
+        bind:
+            $opentalentNoReplyEmailAddress: 'noreply@opentalent.fr'
+
     #########################################
     ##  TAG Services ##
     _instanceof:
@@ -67,7 +71,6 @@ services:
     App\Service\ServiceIterator\EncoderIterator:
         - !tagged_iterator app.encoder
 
-
     App\Service\Dolibarr\DolibarrSyncService:
         tags:
             - { name: monolog.logger, channel: dolibarrsync }

+ 2 - 2
src/DataPersister/Access/AdminAccessDataPersister.php

@@ -41,7 +41,7 @@ class AdminAccessDataPersister implements ContextAwareDataPersisterInterface
         /** @var Access $access */
         $access = $this->security->getUser();
 
-        $administrator = $this->accessUtils->getAdminAccess($access->getOrganization());
+        $administrator = $this->accessUtils->findAdminFor($access->getOrganization());
         if(!$administrator){
             throw new Exception('administrator_not_found', 404);
         }
@@ -59,4 +59,4 @@ class AdminAccessDataPersister implements ContextAwareDataPersisterInterface
     {
         throw new Exception('not supported', 500);
     }
-}
+}

+ 12 - 3
src/DataPersister/Organization/SubdomainDataPersister.php

@@ -6,9 +6,10 @@ namespace App\DataPersister\Organization;
 use App\DataPersister\BaseDataPersister;
 use App\Entity\Organization\Subdomain;
 use App\Message\Command\Typo3\Typo3UpdateCommand;
+use App\Service\MailHub;
+use App\Service\OnChange\Organization\OnSubdomainChange;
 use App\Service\Organization\Utils;
 use App\Service\Typo3\BindFileService;
-use Exception;
 use Symfony\Component\Messenger\MessageBusInterface;
 
 /**
@@ -19,6 +20,7 @@ class SubdomainDataPersister extends BaseDataPersister
     public function __construct(
         private BindFileService $bindFileService,
         private MessageBusInterface $messageBus,
+        private OnSubdomainChange $onSubdomainChange
     ) {}
 
     public function supports($data, array $context = []): bool
@@ -42,7 +44,7 @@ class SubdomainDataPersister extends BaseDataPersister
     {
         $shallPersist = false;
 
-        // Ensure it is the only active subdomain of this organization
+        // Ensure it is the only active subdomain of this organization by disabling other organization subdomains
         if ($subdomain->isActive()) {
             foreach ($subdomain->getOrganization()->getSubdomains() as $other) {
                 if ($other !== $subdomain && $other->isActive()) {
@@ -60,7 +62,7 @@ class SubdomainDataPersister extends BaseDataPersister
         // Update the admin username
         if ($this->isPutRequest() && $this->previousData()) {
             if ($subdomain->isActive() && !$this->previousData()->isActive()) {
-                Utils::updateAdminUsername($subdomain->getOrganization());
+                $this->onSubdomainChange->updateAdminUsername($subdomain->getOrganization());
                 $shallPersist = true;
             }
         }
@@ -78,5 +80,12 @@ class SubdomainDataPersister extends BaseDataPersister
                 );
             }
         }
+
+        // Envoi d'un email
+        if ($this->isPutRequest() && $this->previousData()) {
+            if ($subdomain->isActive() && !$this->previousData()->isActive()) {
+                $this->onSubdomainChange->sendEmailAfterSubdomainChange($subdomain);
+            }
+        }
     }
 }

+ 7 - 0
src/Entity/Person/Person.php

@@ -158,6 +158,13 @@ class Person implements UserInterface
         return $this;
     }
 
+    public function getFullName(): ?string {
+        if (!$this->getName() || !$this->getGivenName()) {
+            return null;
+        }
+        return "{$this->getName()} {$this->getGivenName()}";
+    }
+
     public function setGender(?string $gender): self
     {
         $this->gender = $gender;

+ 167 - 0
src/Message/Command/SendEmail.php

@@ -0,0 +1,167 @@
+<?php
+
+namespace App\Message\Command;
+
+use Symfony\Component\Mime\Email;
+
+/**
+ * Envoi d'un email
+ */
+class SendEmail
+{
+    public function __construct(
+        private array $from,
+        private array $to,
+        private string $subject,
+        private ?string $text = null,
+        private ?String $html = null,
+        private ?array $cc = null,
+        private ?array $bcc = null,
+        private ?array $replyTo = null,
+        private int $priority = Email::PRIORITY_NORMAL
+    ) {}
+
+    /**
+     * @return array
+     */
+    public function getFrom(): array
+    {
+        return $this->from;
+    }
+
+    /**
+     * @param array $from
+     */
+    public function setFrom(array $from): void
+    {
+        $this->from = $from;
+    }
+
+    /**
+     * @return array
+     */
+    public function getTo(): array
+    {
+        return $this->to;
+    }
+
+    /**
+     * @param array $to
+     */
+    public function setTo(array $to): void
+    {
+        $this->to = $to;
+    }
+
+    /**
+     * @return string
+     */
+    public function getSubject(): string
+    {
+        return $this->subject;
+    }
+
+    /**
+     * @param string $subject
+     */
+    public function setSubject(string $subject): void
+    {
+        $this->subject = $subject;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getText(): ?string
+    {
+        return $this->text;
+    }
+
+    /**
+     * @param string|null $text
+     */
+    public function setText(?string $text): void
+    {
+        $this->text = $text;
+    }
+
+    /**
+     * @return String|null
+     */
+    public function getHtml(): ?string
+    {
+        return $this->html;
+    }
+
+    /**
+     * @param String|null $html
+     */
+    public function setHtml(?string $html): void
+    {
+        $this->html = $html;
+    }
+
+    /**
+     * @return array|null
+     */
+    public function getCc(): ?array
+    {
+        return $this->cc;
+    }
+
+    /**
+     * @param array|null $cc
+     */
+    public function setCc(?array $cc): void
+    {
+        $this->cc = $cc;
+    }
+
+    /**
+     * @return array|null
+     */
+    public function getBcc(): ?array
+    {
+        return $this->bcc;
+    }
+
+    /**
+     * @param array|null $bcc
+     */
+    public function setBcc(?array $bcc): void
+    {
+        $this->bcc = $bcc;
+    }
+
+    /**
+     * @return array|null
+     */
+    public function getReplyTo(): ?array
+    {
+        return $this->replyTo;
+    }
+
+    /**
+     * @param array|null $replyTo
+     */
+    public function setReplyTo(?array $replyTo): void
+    {
+        $this->replyTo = $replyTo;
+    }
+
+    /**
+     * @return int
+     */
+    public function getPriority(): int
+    {
+        return $this->priority;
+    }
+
+    /**
+     * @param int $priority
+     */
+    public function setPriority(int $priority): void
+    {
+        $this->priority = $priority;
+    }
+}

+ 50 - 0
src/Message/Handler/SendEmailHandler.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Message\Handler;
+
+use App\Message\Command\SendEmail;
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mailer\MailerInterface;
+use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
+use Symfony\Component\Mime\Email;
+
+/**
+ * Handler for App\Message\Command\SendEmail
+ */
+class SendEmailHandler implements MessageHandlerInterface
+{
+    public function __construct(
+        private MailerInterface $mailer
+    ) {}
+
+    /**
+     * @throws TransportExceptionInterface
+     */
+    public function __invoke(SendEmail $sendEmail) {
+        $email = (new Email())
+            ->from(...$sendEmail->getFrom())
+            ->to(...$sendEmail->getTo())
+            ->subject($sendEmail->getSubject());
+
+        if ($sendEmail->getText() !== null) {
+            $email->text($sendEmail->getText());
+        }
+        if ($sendEmail->getHtml() !== null) {
+            $email->html($sendEmail->getHtml());
+        }
+        if ($sendEmail->getCc() !== null) {
+            $email->cc(...$sendEmail->getCc());
+        }
+        if ($sendEmail->getBcc() !== null) {
+            $email->bcc(...$sendEmail->getBcc());
+        }
+        if ($sendEmail->getReplyTo() !== null) {
+            $email->replyTo(...$sendEmail->getReplyTo());
+        }
+        if ($sendEmail->getPriority() !== null) {
+            $email->priority($sendEmail->getPriority());
+        }
+
+        $this->mailer->send($email);
+    }
+}

+ 5 - 3
src/Service/Access/AdminAccessUtils.php

@@ -21,13 +21,15 @@ class AdminAccessUtils
     { }
 
     /**
-     * Renvoi l'objet AdminAccess initialisé par rapport à l'organization passée en paramètre
+     * Renvoie l'objet AdminAccess initialisé par rapport à l'organization passée en paramètre
+     *
      * @param Organization $organization
      * @return AdminAccess|null
+     * @throws \Exception
      * @see AdminAccessUtilsTest::testGetAdminAccess()
      */
     public function getAdminAccess(Organization $organization): ?AdminAccess{
-        $administrator = $this->accessUtils->getAdminAccess($organization);
+        $administrator = $this->accessUtils->findAdminFor($organization);
         if(!$administrator) return null;
 
         $contactPoint = $this->contactPointUtils->getPersonContactPointPrincipal($administrator);
@@ -41,4 +43,4 @@ class AdminAccessUtils
         ;
         return $adminAccess;
     }
-}
+}

+ 5 - 3
src/Service/Access/Utils.php

@@ -8,6 +8,7 @@ use App\Entity\Organization\Organization;
 use App\Repository\Access\AccessRepository;
 use App\Service\ServiceIterator\OptionalsRolesIterator;
 use App\Test\Service\Access\UtilsTest;
+use Doctrine\Common\Collections\Criteria;
 use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
 
 /**
@@ -60,15 +61,16 @@ class Utils
     }
 
     /**
-     * Renvoi l'access de l'organization qui est le "super admin"
+     * Renvoie l'access de l'organization qui est le "super admin"
+     *
      * @param Organization $organization
      * @return Access|null
      * @see UtilsTest::testGetAdminAccess()
      */
-    public function getAdminAccess(Organization $organization): Access|null{
+    public function findAdminFor(Organization $organization): Access|null{
         return $this->accessRepository->findOneBy([
             'adminAccess' => true,
             'organization' => $organization
-        ]) ?? null;
+        ]);
     }
 }

+ 17 - 8
src/Service/Core/ContactPointUtils.php

@@ -19,17 +19,26 @@ class ContactPointUtils
     }
 
     /**
-     * Renvoie le point de contact principal de l'Access passé en paramètre
+     * Renvoie le point de contact principal de l'Access, ou null si aucun trouvé.
+     *
      * @param Access $access
      * @return ContactPoint|null
+     * @throws \Exception
      * @see ContactPointUtilsTest::testGetPersonContactPointPrincipal()
      */
-    public function getPersonContactPointPrincipal(Access $access): ?ContactPoint{
-        $contactPoint = $this->contactPointRepository->getByTypeAndPerson(ContactPointTypeEnum::PRINCIPAL()->getValue(), $access->getPerson());
-        if(count($contactPoint) === 0) return null;
-        if(count($contactPoint) !== 1){
-            throw new \Exception('more_than_one_result');
+    public function getPersonContactPointPrincipal(Access $access): ?ContactPoint {
+        $contactPoints = $this->contactPointRepository->getByTypeAndPerson(
+            ContactPointTypeEnum::PRINCIPAL()->getValue(),
+            $access->getPerson()
+        );
+
+        if (count($contactPoints) === 0) {
+            return null;
+        }
+
+        if (count($contactPoints) !== 1){
+            throw new \RuntimeException('more_than_one_result');
         }
-        return $contactPoint[0];
+        return $contactPoints[0];
     }
-}
+}

+ 75 - 0
src/Service/MailHub.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace App\Service;
+
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use App\Message\Command\SendEmail;
+use App\Service\Core\ContactPointUtils;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Mime\Address;
+use Twig\Environment;
+use Twig\Error\LoaderError;
+use Twig\Error\RuntimeError;
+use Twig\Error\SyntaxError;
+
+class MailHub
+{
+    public function __construct(
+        private MessageBusInterface $messageBus,
+        private Environment $twig,
+        private string $opentalentNoReplyEmailAddress,
+        private ContactPointUtils $contactPointUtils,
+        private \App\Service\Access\Utils $accessUtils
+    ) {}
+
+    /**
+     * Sends an automatic 'do-not-reply'-type email to the user
+     *
+     * NB: These emails are not registered in the DB
+     *
+     * @param Access $access
+     * @param string $subject
+     * @param string $template
+     * @param array $data
+     * @throws \Exception
+     */
+    public function sendAutomaticEmailTo(Access $access, string $subject, string $template, array $data): void
+    {
+        $contactPoint = $this->contactPointUtils->getPersonContactPointPrincipal($access);
+        if ($contactPoint === null || empty($contactPoint->getEmail()) || $contactPoint->getEmailInvalid()) {
+            throw new \RuntimeException('Access has no principal email address, abort');
+        }
+        $to = new Address($contactPoint->getEmail(), $access->getPerson()->getFullName());
+        $data['_address'] = $to;
+
+        try {
+            $content = $this->twig->render('@templates/mail/' . $template . '.html.twig', $data);
+        } catch (LoaderError | RuntimeError | SyntaxError $e) {
+            throw new \RuntimeException('Error while rendering the email template ', 0, $e);
+        }
+
+        $this->messageBus->dispatch(
+            new SendEmail(
+                [$this->opentalentNoReplyEmailAddress],
+                [$to],
+                $subject,
+                null,
+                $content
+            )
+        );
+    }
+
+    /**
+     * Sends an automatic 'do-not-reply'-type email to the admin of the organization
+     * @throws \Exception
+     */
+    public function sendAutomaticEmailToAdmin(Organization $organization, string $subject, string $template, array $data): void
+    {
+        $admin = $this->accessUtils->findAdminFor($organization);
+        if ($admin === null) {
+            throw new \RuntimeException('No admin found for organization ' . $organization->getId());
+        }
+        $this->sendAutomaticEmailTo($admin, $subject, $template, $data);
+    }
+}

+ 58 - 0
src/Service/OnChange/Organization/OnSubdomainChange.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Service\OnChange\Organization;
+
+use App\Entity\Organization\Organization;
+use App\Entity\Organization\Subdomain;
+use App\Service\MailHub;
+
+class OnSubdomainChange
+{
+    public function __construct(
+        private \App\Service\Organization\Utils $organizationUtils,
+        private \App\Service\Access\Utils $accessUtils,
+        private MailHub $mailHub
+    ) {}
+
+    /**
+     * Update the admin username to match the pattern 'admin{subdomain}'
+     *
+     * @param Organization $organization
+     */
+    public function updateAdminUsername(Organization $organization): void
+    {
+        $subdomain = \App\Service\Organization\Utils::getOrganizationActiveSubdomain($organization);
+        if (!$subdomain) {
+            throw new \RuntimeException('Organization has no active subdomain : ' . $organization->getId());
+        }
+
+        $admin = $this->accessUtils->findAdminFor($organization);
+        if (!$admin) {
+            throw new \RuntimeException('No admin found for organization ' . $organization->getId());
+        }
+
+        $newUsername = 'admin' . $subdomain;
+        /** @noinspection NullPointerExceptionInspection */
+        $admin->getPerson()->setUsername($newUsername);
+    }
+
+    /**
+     * @throws \Exception
+     */
+    public function sendEmailAfterSubdomainChange(Subdomain $subdomain): void
+    {
+
+        $admin = $this->accessUtils->findAdminFor($subdomain->getOrganization());
+
+        $this->mailHub->sendAutomaticEmailToAdmin(
+            $subdomain->getOrganization(),
+            'Nouveau sous domaine: ' . $subdomain->getSubdomain(),
+            'subdomain',
+            [
+                'access' => $admin,
+                'subdomain' => $subdomain,
+                'url' => $this->organizationUtils::getOrganizationWebsite($subdomain->getOrganization())
+            ]
+        );
+    }
+}

+ 0 - 21
src/Service/Organization/Utils.php

@@ -190,25 +190,4 @@ class Utils
 
         return 'https://' . $subdomain . '.opentalent.fr';
     }
-
-    /**
-     * Update the admin username to match the pattern 'admin{subdomain}'
-     *
-     * @param Organization $organization
-     */
-    public static function updateAdminUsername(Organization $organization): void {
-        $subdomain = self::getOrganizationActiveSubdomain($organization);
-        if (!$subdomain) {
-            throw new \RuntimeException('Organization has no active subdomain : ' . $organization->getId());
-        }
-
-        $criteria = Criteria::create()
-            ->andWhere(Criteria::expr()->eq('adminAccess', 1));
-        $admin = $organization->getAccesses()->matching($criteria)->first();
-        if (!$admin) {
-            throw new \RuntimeException('no admin found for organization ' . $organization->getId());
-        }
-
-        $admin->getPerson()->setUsername('admin' . $subdomain);
-    }
 }

+ 17 - 0
templates/mail/subdomain.html.twig

@@ -0,0 +1,17 @@
+{% if  access.adminAccess %}
+Cher administrateur de {{access.organization.name}},
+{% else %}
+Cher {{access.person.givenName}} {{access.person.name}},
+{% endif %}
+votre demande d'activation du sous-domaine '{{ subdomain.subdomain }}' a été prise en compte et sera effective d'ici quelques minutes.
+
+Votre site sera dè lors accessible à l'adresse suivante :
+
+<a href="{{url}}">{{url}}</a>
+
+{% if  access.adminAccess %}
+Notez que votre identifiant est désormais : {{access.person.username}}
+Votre mot de passe reste inchangé.
+{% endif %}
+
+{% include 'signature_opentalent.html.twig' with {'name': _address.fullname, 'email': _address.email } only %}

+ 4 - 4
tests/Service/Access/UtilsTest.php

@@ -95,24 +95,24 @@ class UtilsTest extends TestCase
     }
 
     /**
-     * @see Utils::getAdminAccess()
+     * @see Utils::findAdminFor()
      */
     public function testGetAdminAccess(){
         $this->accessRepositoryMock
             ->method('findOneBy')
             ->willReturn(new Access());
 
-        $this->assertNotEmpty($this->utils->getAdminAccess(new Organization()));
+        $this->assertNotEmpty($this->utils->findAdminFor(new Organization()));
     }
 
     /**
-     * @see Utils::getAdminAccess()
+     * @see Utils::findAdminFor()
      */
     public function testGetAdminAccessNotFound(){
         $this->accessRepositoryMock
             ->method('findOneBy')
             ->willReturn(null);
 
-        $this->assertNull($this->utils->getAdminAccess(new Organization()));
+        $this->assertNull($this->utils->findAdminFor(new Organization()));
     }
 }