Vincent GUFFON пре 3 година
родитељ
комит
c1b63d7c84

+ 1 - 0
src/Service/Core/ContactPointUtils.php

@@ -5,6 +5,7 @@ namespace App\Service\Core;
 
 use App\Entity\Access\Access;
 use App\Entity\Core\ContactPoint;
+use App\Entity\Organization\Organization;
 use App\Enum\Core\ContactPointTypeEnum;
 use App\Repository\Core\ContactPointRepository;
 use App\Test\Service\Access\ContactPointUtilsTest;

+ 125 - 0
src/Service/Mailer/Builder/AbstractBuilder.php

@@ -0,0 +1,125 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer\Builder;
+
+use App\Entity\Access\Access;
+use App\Entity\Core\ContactPoint;
+use App\Entity\Organization\Organization;
+use App\Enum\Message\ReportMessageSatusEnum;
+use App\Repository\Core\ContactPointRepository;
+use App\Service\Mailer\Email;
+use App\Entity\Message\Email as EmailEntity;
+use App\Service\Mailer\EmailRecipient;
+use Symfony\Contracts\Service\Attribute\Required;
+use Twig\Environment;
+
+/**
+ * Classe AbstractBuilder qui définit les fonctions de bases de chaque builder
+ */
+class AbstractBuilder implements AbstractBuilderInterface
+{
+    protected Environment $twig;
+    protected ContactPointRepository $contactPointRepository;
+
+    #[Required]
+    public function setTwig(Environment $twig): void
+    { $this->twig = $twig; }
+
+    #[Required]
+    public function setContactPointRepository(ContactPointRepository $contactPointRepository): void
+    { $this->contactPointRepository = $contactPointRepository; }
+
+
+    /**
+     * @param string $subject
+     * @param Access $author
+     * @param string $content
+     * @return EmailEntity
+     */
+    public function buildEmailEntity(string $subject, Access $author, string $content): EmailEntity{
+        return (new EmailEntity())
+            ->setUniqueSendId(uniqid())
+            ->setAuthor($author)
+            ->setAbout($subject)
+            ->setIsSystem(true)
+            ->setDateSent(new \DateTime('now'))
+            ->setText($content)
+            ;
+    }
+
+    /**
+     * @param string $template
+     * @param array $context
+     * @return string
+     * @throws \Twig\Error\LoaderError
+     * @throws \Twig\Error\RuntimeError
+     * @throws \Twig\Error\SyntaxError
+     * @throws \Twig_Error_Loader
+     * @throws \Twig_Error_Runtime
+     * @throws \Twig_Error_Syntax
+     */
+    public function renderer(string $template, array $context)
+    {
+        return $this->twig->render(sprintf('@templates/emails/%s.html.twig', $template), $context);
+    }
+
+    /**
+     * @param Email $email
+     * @param Access|Organization|string $target
+     * @param string $sendType
+     * @param string|null $contactPointType
+     */
+    public function addRecipient(Email $email, Access|Organization|string $target, string $sendType, string $contactPointType = null): void{
+        $emailRecipient = (new EmailRecipient())
+            ->setSendType($sendType);
+
+        if($target instanceof Access){
+            $emailRecipient->setAccess($target);
+            $emailRecipient->setName($target->getPerson()->getFullName());
+            $this->setMailToRecipient($this->contactPointRepository->getByTypeAndPerson($contactPointType, $target->getPerson()), $emailRecipient);
+        }else if ($target instanceof Organization){
+            $emailRecipient->setOrganization($target);
+            $emailRecipient->setName($target->getName());
+            $this->setMailToRecipient($this->contactPointRepository->getByTypeAndOrganization($contactPointType, $target), $emailRecipient);
+        }else{
+            $emailRecipient->setSendStatus(ReportMessageSatusEnum::DELIVERED()->getValue());
+            $emailRecipient->setEmailAddress($target);
+        }
+
+        $email->addEmailRecipient($emailRecipient);
+    }
+
+    /**
+     * @param array $contactPoints
+     * @param EmailRecipient $recipient
+     */
+    public function setMailToRecipient(array $contactPoints, EmailRecipient $recipient): void {
+        if($contactPoint = $this->getFirstContactPointWithEmail($contactPoints)){
+            $recipient->setSendStatus(ReportMessageSatusEnum::DELIVERED()->getValue());
+            $recipient->setEmailAddress($contactPoint->getEmail());
+        }else{
+            $recipient->setSendStatus(ReportMessageSatusEnum::MISSING()->getValue());
+        }
+    }
+
+    /**
+     * @param $contactPoints
+     * @return ContactPoint
+     */
+    public function getFirstContactPointWithEmail($contactPoints): ContactPoint{
+        $find = false;
+        $cmpt = 0;
+        $contactPoint = null;
+
+        while(count($contactPoints) > $cmpt && !$find){
+            if($contactPoints[$cmpt]->getEmail()){
+                $find = true;
+                $contactPoint = $contactPoints[$cmpt];
+            }
+            $cmpt++;
+        }
+
+        return $contactPoint;
+    }
+}

+ 17 - 0
src/Service/Mailer/Builder/AbstractBuilderInterface.php

@@ -0,0 +1,17 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer\Builder;
+
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use App\Service\Mailer\Email;
+
+interface AbstractBuilderInterface {
+
+    public function renderer(string $template, array $context);
+
+    public function buildEmailEntity(string $subject, Access $author, string $content);
+
+    public function addRecipient(Email $email, Access|Organization|string $target, string $sendType, string $contactPointType = null);
+}

+ 12 - 0
src/Service/Mailer/Builder/BuilderInterface.php

@@ -0,0 +1,12 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer\Builder;
+
+use App\Service\Mailer\Model\MailerModelInterface;
+
+interface BuilderInterface extends AbstractBuilderInterface {
+    public function support(MailerModelInterface $mailerModel): bool;
+
+    public function build(MailerModelInterface $mailerModel);
+}

+ 61 - 0
src/Service/Mailer/Builder/TestBuilder.php

@@ -0,0 +1,61 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer\Builder;
+
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use App\Enum\Core\ContactPointTypeEnum;
+use App\Enum\Core\EmailSendingTypeEnum;
+use App\Service\Mailer\Email;
+use App\Service\Mailer\Model\MailerModelInterface;
+use App\Service\Mailer\Model\TestModel;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\ORM\EntityManagerInterface;
+use function Symfony\Component\DependencyInjection\Loader\Configurator\param;
+
+/**
+ * Classe ... qui ...
+ */
+class TestBuilder extends AbstractBuilder implements BuilderInterface
+{
+    public function __construct(
+        private EntityManagerInterface $entityManager
+    )
+    {
+    }
+
+    public function support(MailerModelInterface $mailerModel): bool
+    {
+        return $mailerModel instanceof TestModel;
+    }
+
+    /**
+     * @param TestModel $mailerModel
+     * @return ArrayCollection
+     */
+    public function build(MailerModelInterface $mailerModel): ArrayCollection
+    {
+        $organization = $this->entityManager->getRepository(Organization::class)->find($mailerModel->getOrganizationId());
+        $access = $this->entityManager->getRepository(Access::class)->find($mailerModel->getAccessId());
+
+        $emails = new ArrayCollection();
+
+        $context = ['organization' => $organization];
+        $content = $this->renderer('test', $context);
+
+        $email= (new Email())
+            ->setEmailEntity($this->buildEmailEntity('Mon sujet', $access, $content))
+            ->setContent($content)
+            ->setFrom('vincent.guffon@2iopenservice.fr')
+        ;
+
+        $this->addRecipient($email, $access, EmailSendingTypeEnum::TO()->getValue(), ContactPointTypeEnum::PRINCIPAL()->getValue());
+        $this->addRecipient($email, $organization, EmailSendingTypeEnum::TO()->getValue(), ContactPointTypeEnum::PRINCIPAL()->getValue());
+        $this->addRecipient($email, "vincent.guffon@gmail.com", EmailSendingTypeEnum::TO()->getValue());
+
+        $emails->add($email);
+
+        return $emails;
+    }
+}

+ 67 - 0
src/Service/Mailer/Email.php

@@ -0,0 +1,67 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer;
+
+use App\Entity\Message\Email as EmailEntity;
+use Doctrine\Common\Collections\ArrayCollection;
+
+/**
+ * Classe Email qui contient les informations nécessaire pour assurer l'envoie d'un mail
+ */
+class Email implements EmailInterface
+{
+    private string $from;
+    private EmailEntity $emailEntity;
+    private string $content;
+    private ArrayCollection $emailRecipients;
+
+    public function __construct()
+    {
+        $this->emailRecipients = new ArrayCollection();
+    }
+
+    public function getFrom(): string
+    {
+        return $this->from;
+    }
+
+    public function setFrom(string $from): self
+    {
+        $this->from = $from;
+        return $this;
+    }
+
+    public function getEmailEntity(): EmailEntity
+    {
+        return $this->emailEntity;
+    }
+
+    public function setEmailEntity(EmailEntity $emailEntity): self
+    {
+        $this->emailEntity = $emailEntity;
+        return $this;
+    }
+
+    public function getContent(): string
+    {
+        return $this->content;
+    }
+
+    public function setContent(string $content): self
+    {
+        $this->content = $content;
+        return $this;
+    }
+
+    public function addEmailRecipient(EmailRecipient $emailRecipients): self{
+        $this->emailRecipients->add($emailRecipients);
+        return $this;
+    }
+
+    public function getEmailRecipients(): ArrayCollection
+    {
+        return $this->emailRecipients;
+    }
+
+}

+ 7 - 0
src/Service/Mailer/EmailInterface.php

@@ -0,0 +1,7 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer;
+
+interface EmailInterface{
+}

+ 87 - 0
src/Service/Mailer/EmailRecipient.php

@@ -0,0 +1,87 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer;
+
+use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+
+/**
+ * Classe AbstractRecipient qui contient les informations des destinataires
+ */
+class EmailRecipient
+{
+    private string $sendStatus;
+    private string $name = '';
+    private string $emailAddress;
+    private string $sendType;
+    private ?Access $access = null;
+    private ?Organization $organization = null;
+
+
+    public function getSendType(): string
+    {
+        return $this->sendType;
+    }
+
+    public function setSendType(string $sendType): self
+    {
+        $this->sendType = $sendType;
+        return $this;
+    }
+
+    public function getSendStatus(): string
+    {
+        return $this->sendStatus;
+    }
+
+    public function setSendStatus(string $sendStatus): self
+    {
+        $this->sendStatus = $sendStatus;
+        return $this;
+    }
+
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    public function setName(string $name): self
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    public function getEmailAddress(): string
+    {
+        return $this->emailAddress;
+    }
+
+    public function setEmailAddress(string $emailAddress): self
+    {
+        $this->emailAddress = $emailAddress;
+        return $this;
+    }
+
+    public function getAccess(): ?Access
+    {
+        return $this->access;
+    }
+
+    public function setAccess(?Access $access): self
+    {
+        $this->access = $access;
+        return $this;
+    }
+
+    public function getOrganization(): ?Organization
+    {
+        return $this->organization;
+    }
+
+    public function setOrganization(?Organization $organization): self
+    {
+        $this->organization = $organization;
+        return $this;
+    }
+}

+ 210 - 0
src/Service/Mailer/Mailer.php

@@ -0,0 +1,210 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer;
+
+use App\Entity\Message\ReportEmail;
+use App\Enum\Core\EmailSendingTypeEnum;
+use App\Enum\Message\MessageStatusEnum;
+use App\Service\Mailer\Model\MailerModelInterface;
+use App\Service\ServiceIterator\Mailer\BuilderIterator;
+use App\Service\Utils\StringsUtils;
+use App\Enum\Message\ReportMessageSatusEnum;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\ORM\EntityManagerInterface;
+use Symfony\Bridge\Twig\Mime\TemplatedEmail;
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+use Symfony\Component\Mailer\MailerInterface;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Email as SymfonyEmail;
+
+/**
+ * Classe Mailer : Service assurant l'envoie d'un mail à un destinataire
+ */
+class Mailer
+{
+    public function __construct(
+        private MailerInterface $symfonyMailer,
+        private string $opentalentNoReplyEmailAddress,
+        private BuilderIterator $builderIterator,
+        private StringsUtils $stringsUtils,
+        private EntityManagerInterface $entityManager
+    )
+    {
+    }
+
+    /**
+     * Main fonction qui itère les différentes étapes nécessaires à l'envoie d'un email
+     *  - Le Build
+     *  - Le Send
+     *  - Le Reporting
+     *
+     * @param MailerModelInterface $mailerModel
+     * @throws \Exception
+     */
+    public function main(MailerModelInterface $mailerModel): void{
+        $builderService = $this->builderIterator->getBuilderFor($mailerModel);
+        $emailsCollection = $builderService->build($mailerModel);
+
+        // @todo
+        //$emailsCollection = $this->reduceEmailsCollectionInPreproduction($emailsCollection);
+
+        foreach ($emailsCollection as $email){
+            $this->send($email);
+
+            $this->persistEmailEntity($email);
+        }
+
+        $this->report($emailsCollection);
+
+        $this->entityManager->flush();
+    }
+
+    /**
+     * Fonction d'envoie
+     * @param EmailInterface $email
+     */
+    public function send(EmailInterface $email): void{
+        //On créer le mail
+        $symfonyMail = $this->createSymfonyEmail($email);
+
+        $this->addRecipients($symfonyMail, $email);
+
+        $this->addHeaders($symfonyMail, $email);
+
+        // @todo
+        //$this->addAttachments();
+
+        //On tente d'envoyer
+        try {
+            $this->symfonyMailer->send($symfonyMail);
+            $email->getEmailEntity()->setStatus(MessageStatusEnum::SEND()->getValue());
+            $email->getEmailEntity()->setDateSent(new \DateTime('now'));
+        } catch (\Exception $e) {
+            $email->getEmailEntity()->setStatus(MessageStatusEnum::FAILED()->getValue());
+        }
+    }
+
+    /**
+     * Fonction de rapport
+     * @param ArrayCollection $emails
+     */
+    public function report(ArrayCollection $emails): void{
+        $delivered = [];
+        $unDelivered = [];
+
+        foreach ($emails as $email){
+            $emailRecipients = $email->getEmailRecipients();
+            /** @var EmailRecipient $emailRecipient */
+            foreach ($emailRecipients as $emailRecipient){
+                if($emailRecipient->getSendStatus() === ReportMessageSatusEnum::MISSING()->getValue()){
+                    $unDelivered[] = $emailRecipient;
+                }else if($emailRecipient->getSendStatus() === ReportMessageSatusEnum::DELIVERED()->getValue()){
+                    $delivered[] = $emailRecipient;
+                }
+            }
+        }
+
+        $templatedMail = (new TemplatedEmail())
+            ->from($this->opentalentNoReplyEmailAddress)
+            ->subject(sprintf('Rapport d\'envoie du message : %s', $emails->first()->getEmailEntity()->getAbout()))
+            ->htmlTemplate('@templates/emails/report.html.twig')
+            ->context(
+                [
+                    'email_example' => $emails->first()->getEmailEntity(),
+                    'delivered' => $delivered,
+                    'unDelivered' => $unDelivered
+                ]
+            )
+            ->addTo(new Address($this->opentalentNoReplyEmailAddress))
+        ;
+
+        try {
+            $this->symfonyMailer->send($templatedMail);
+        } catch (TransportExceptionInterface $e) {
+
+        }
+    }
+
+    /**
+     * Persist l'Email et les ReportEmail
+     * @param Email $email
+     */
+    public function persistEmailEntity(Email $email){
+        $emailEntity = $email->getEmailEntity();
+
+        /** @var EmailRecipient $emailRecipient */
+        foreach ($email->getEmailRecipients() as $emailRecipient){
+            $report = (new ReportEmail())
+                ->setAddressEmail($emailRecipient->getEmailAddress())
+                ->setAccess($emailRecipient->getAccess())
+                ->setOrganization($emailRecipient->getOrganization())
+                ->setDateSend(new \DateTime('now'))
+                ->setStatus($emailRecipient->getSendStatus())
+            ;
+            $emailEntity->addReport($report);
+        }
+        $this->entityManager->persist($emailEntity);
+    }
+
+    /**
+     * Reduit le nombre d'emails a envoyer si on ne se trouve pas en prod
+     * @param ArrayCollection $emailsCollection
+     */
+    public function reduceEmailsCollectionInPreproduction(ArrayCollection $emailsCollection): ArrayCollection {
+        if($_ENV['env'] === 'prod') return $emailsCollection;
+
+        $startEmails = $emailsCollection->slice(0, 10);
+        $endEmails = $emailsCollection->slice($emailsCollection->count() - 11, 10);
+
+        return new ArrayCollection([...$startEmails, ...$endEmails]);
+    }
+
+    /**
+     * @param SymfonyEmail $symfonyMail
+     * @param Email $email
+     */
+    public function addHeaders(SymfonyEmail $symfonyMail, Email $email){
+       // $symfonyMail->getHeaders()->addTextHeader('List-Unsubscribe','mailto:'.$email->getOriginator().'?subject=désabonnement');
+
+        $symfonyMail->getHeaders()->addTextHeader('X-ID-OT', $email->getEmailEntity()->getUniqueSendId());
+    }
+
+
+    /**
+     * Création du Mail qui sera envoyé via le Mailer de Symfony
+     * @param Email $email
+     * @return SymfonyEmail
+     */
+    public function createSymfonyEmail(Email $email): SymfonyEmail{
+        return (new SymfonyEmail())
+            ->from($email->getFrom())
+            ->subject($email->getEmailEntity()->getAbout())
+            ->html($email->getContent())
+            ->text($this->stringsUtils->convertHtmlToText($email->getContent()))
+            ;
+    }
+
+    /**
+     * On ajoute les destinataires suivant le type d'envoie souhaité
+     * @param SymfonyEmail $symfonyMail
+     * @param Address $addressesMail
+     */
+    public function addRecipients(SymfonyEmail $symfonyMail, Email $email): void{
+        foreach ($email->getEmailRecipients() as $emailRecipient){
+            $addressMail = new Address($emailRecipient->getEmailAddress(), $emailRecipient->getName());
+
+            switch($emailRecipient->getSendType()){
+                case EmailSendingTypeEnum::TO()->getValue():
+                    $symfonyMail->addTo($addressMail);
+                    break;
+                case EmailSendingTypeEnum::BBC()->getValue():
+                    $symfonyMail->addBcc($addressMail);
+                    break;
+                case EmailSendingTypeEnum::CC()->getValue():
+                    $symfonyMail->addCc($addressMail);
+                    break;
+            }
+        }
+    }
+}

+ 6 - 0
src/Service/Mailer/Model/MailerModelInterface.php

@@ -0,0 +1,6 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer\Model;
+
+interface MailerModelInterface {}

+ 34 - 0
src/Service/Mailer/Model/TestModel.php

@@ -0,0 +1,34 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Service\Mailer\Model;
+
+/**
+ * Classe ... qui ...
+ */
+class TestModel implements MailerModelInterface
+{
+    public function __construct(
+        private int $accessId,
+        private int $organizationId
+    )
+    {}
+
+    public function setAccessId(int $accessId) : self{
+        $this->accessId = $accessId;
+        return $this;
+    }
+
+    public function getAccessId(): int {
+        return $this->accessId;
+    }
+
+    public function setOrganizationId(int $organizationId): self {
+        $this->organizationId = $organizationId;
+        return $this;
+    }
+
+    public function getOrganizationId(): int{
+        return $this->organizationId;
+    }
+}

+ 41 - 0
src/Service/ServiceIterator/Mailer/BuilderIterator.php

@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Service\ServiceIterator\Mailer;
+
+use App\Service\Mailer\Model\MailerModelInterface;
+use App\Service\Mailer\Builder\BuilderInterface;
+use Exception;
+
+/**
+ * Permet d'itérer sur les services de build Mailer
+ */
+class BuilderIterator
+{
+    /**
+     * Pour l'injection des services, voir config/services.yaml, section 'TAG Services'
+     * @param iterable $builderServices
+     */
+    public function __construct(
+        private iterable $builderServices,
+    ) {}
+
+    /**
+     * Itère sur les services de build disponibles et
+     * retourne le premier qui supporte ce type de requête.
+     *
+     * @param string $type
+     * @return BuilderInterface
+     * @throws Exception
+     */
+    public function getBuilderFor(MailerModelInterface $mailerModel): BuilderInterface
+    {
+        /** @var BuilderInterface $builderServices */
+        foreach ($this->builderServices as $builderService){
+            if($builderService->support($mailerModel))
+                return $builderService;
+        }
+        throw new Exception('no builder service found for this operation');
+    }
+}

+ 9 - 0
src/Service/Utils/StringsUtils.php

@@ -38,4 +38,13 @@ class StringsUtils
             )
         );
     }
+
+    /**
+     * @param string $html
+     * @return string
+     */
+    public function convertHtmlToText(string $html): string
+    {
+        return strip_tags(preg_replace('{<(head|style)\b.*?</\1>}is', '', $html));
+    }
 }