src/Controller/EmailingClientCampaignController.php line 106

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Datatable\EmailingClientCampaignTable;
  4. use App\Entity\Collaborator;
  5. use App\Entity\EmailingClientCampaign;
  6. use App\Entity\EmailingClientList;
  7. use App\Entity\EmailingClientListEmail;
  8. use App\Entity\MailingTask;
  9. use App\Form\EmailingClientCampaignDateType;
  10. use App\Form\EmailingClientCampaignMailType;
  11. use App\Form\EmailingClientCampaignNameType;
  12. use App\Form\EmailingClientCampaignSelectListType;
  13. use App\Form\EmailingClientCampaignSelectTagType;
  14. use App\Message\MailingProcessMessage;
  15. use App\Repository\AccountingFirmRepository;
  16. use App\Repository\CollaboratorRepository;
  17. use App\Repository\EmailingClientCampaignRepository;
  18. use App\Repository\EmailingClientListRepository;
  19. use App\Repository\MailingCommunicationRepository;
  20. use App\Repository\SatisfactionClientSurveyRepository;
  21. use App\Services\EmailingClientTokenManage;
  22. use App\Services\EmailingClientTokenManageService;
  23. use App\Services\Mailing;
  24. use App\Services\MailjetService;
  25. use DateTime;
  26. use DateTimeImmutable;
  27. use DateTimeZone;
  28. use Doctrine\Common\Collections\ArrayCollection;
  29. use Doctrine\ORM\EntityManagerInterface;
  30. use Exception;
  31. use Qferrer\Mjml\Twig\MjmlExtension;
  32. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  33. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  34. use Symfony\Component\HttpFoundation\JsonResponse;
  35. use Symfony\Component\HttpFoundation\RedirectResponse;
  36. use Symfony\Component\HttpFoundation\Request;
  37. use Symfony\Component\HttpFoundation\Response;
  38. use Symfony\Component\HttpKernel\KernelInterface;
  39. use Symfony\Component\Messenger\MessageBusInterface;
  40. use Symfony\Component\Routing\Annotation\Route;
  41. use Symfony\Contracts\Cache\CacheInterface;
  42. use Symfony\Contracts\Cache\ItemInterface;
  43. use Vich\UploaderBundle\Storage\StorageInterface;
  44. #[Route('/emailing-client/campagnes')]
  45. class EmailingClientCampaignController extends AbstractController
  46. {
  47.   public function __construct(
  48.     protected readonly MessageBusInterface $messageBus,
  49.     private readonly EntityManagerInterface $em,
  50.     private readonly MailjetService $mailjetService,
  51.     private readonly SatisfactionClientSurveyRepository $surveyRepository,
  52.     private readonly EmailingClientCampaignRepository $emailingClientCampaignRepository
  53.   ) {
  54.   }
  55.   private function checkValidCampaign($idEmailingClientCampaignRepository $emailingClientCampaignRepository$strict true): EmailingClientCampaign|\Symfony\Component\HttpFoundation\RedirectResponse
  56.   {
  57.     $campaign $emailingClientCampaignRepository->findOneBy(['id' => $id]);
  58.     if ($campaign == null) {
  59.       $this->addFlash('error''Aucune campagne n\'a été trouvé avec cet identifiant.');
  60.       return $this->redirectToRoute("app_emailing_client_campaign_index");
  61.     }
  62.     if ($campaign->getAccountingFirm() != $this->getUser()->getAccountingFirm()) {
  63.       $this->addFlash('error''Vous n\'avez pas les droits pour accéder à cette campagne.');
  64.       return $this->redirectToRoute("app_emailing_client_campaign_index");
  65.     }
  66.     if ($strict) {
  67.         if ($campaign->getStatus() != "ON_CREATE" && $campaign->getStatus() != "PROGRAMMED") {
  68.           $this->addFlash('error''Votre campagne a déjà été traité, vous ne pouvez plus la modifier.');
  69.           return $this->redirectToRoute("app_emailing_client_campaign_index");
  70.         }
  71.     }
  72.     return $campaign;
  73.   }
  74.     #[Route('/'name'app_emailing_client_campaign_index')]
  75.     #[IsGranted('ROLE_USER')]
  76.     public function index(Request $requestEmailingClientCampaignTable $datatable): Response
  77.     {
  78.         if (!$this->isGranted('access_emailing')) {
  79.             return $this->redirectToRoute('app_emailing_client_sender_edit');
  80.         }
  81.         $table $datatable->buildTable();
  82.         $table->handleRequest($request);
  83.         if ($table->isCallback()) {
  84.             return $table->getResponse();
  85.         }
  86.         return $this->render('emailing_client_campaign/index.html.twig', [
  87.             'datatable' => $table
  88.         ]);
  89.     }
  90.     #[Route(path'/create/name/{id}'name'app_emailing_client_campaign_create_name'defaults: ['id' => null])]
  91.     #[IsGranted('ROLE_USER')]
  92.     public function campagnesCreateName(Request $requestEntityManagerInterface $em, ?int $id nullEmailingClientCampaignRepository $emailingClientCampaignRepository): Response
  93.     {
  94.         $campaign null;
  95.         if ($id != null) {
  96.             $campaign $emailingClientCampaignRepository->findOneBy(['id' => $id]);
  97.         }
  98.         if (!$campaign) {
  99.             $campaign = new EmailingClientCampaign();
  100.         }
  101.         $form $this->createForm(EmailingClientCampaignNameType::class, $campaign);
  102.         $form->handleRequest($request);
  103.         if ($form->isSubmitted() && $form->isValid()) {
  104.             $masquerTemplate $request->query->get('masquertemplate'false);
  105.             $campaign->setStatus("ON_CREATE");
  106.             $campaign->setAccountingFirm($this->getUser()->getAccountingFirm());
  107.             $em->persist($campaign);
  108.             $em->flush();
  109.             if ($masquerTemplate) {
  110.                 return $this->redirectToRoute('app_emailing_client_campaign_create_select_diffusion', ['id' => $campaign->getId(), 'masquertemplate' => true]);
  111.             } else {
  112.                 return $this->redirectToRoute('app_emailing_client_campaign_create_select_diffusion', ['id' => $campaign->getId()]);
  113.             }
  114.         }
  115.         return $this->render('emailing_client_campaign/create_step_name.html.twig', [
  116.             'form' => $form->createView(),
  117.             'campaign' => $campaign
  118.         ]);
  119.     }
  120.     #[Route(path'/create/select-diffusion/{id}'name'app_emailing_client_campaign_create_select_diffusion'defaults: ['id' => null])]
  121.     #[IsGranted('ROLE_USER')]
  122.     public function campagnesCreateSelectDiffusionList(
  123.         Request                          $request,
  124.         EntityManagerInterface           $em,
  125.         EmailingClientCampaignRepository $emailingClientCampaignRepository,
  126.         EmailingClientListRepository     $emailingClientListRepository,
  127.         ?int                             $id null,
  128.     ): Response {
  129.         $campaign $this->checkValidCampaign($id$emailingClientCampaignRepository);
  130.         $user $this->getUser();
  131.         $form $this->createForm(EmailingClientCampaignSelectListType::class, $campaign);
  132.         $form->handleRequest($request);
  133.         if ($form->isSubmitted() && $form->isValid()) {
  134.             $collaborators $form->has('collaborators') ? $form->get('collaborators')->getData() : null;
  135.             if ($collaborators !== null) {
  136.                 $listName 'Temp - Campagne ' $campaign->getName() . ' - ' . (new DateTime())->format('Y-m-d H:i');
  137.                 $tempList = new EmailingClientList();
  138.                 $tempList->setName($listName)
  139.                     ->setAccountingFirm($user->getAccountingFirm())
  140.                     ->setIsTemporary(true);
  141.                 $emailingClientListEmails = [];
  142.                 $selectedCollaboratorIds = [];
  143.                 $survey $campaign->getSatisfactionClientSurvey();
  144.                 foreach ($collaborators as $collaborator) {
  145.                     if ($collaborator instanceof Collaborator) {
  146.                         $survey->addCollaborator($collaborator);
  147.                         $selectedCollaboratorIds[] = $collaborator->getId();
  148.                         foreach ($collaborator->getClientEmails() as $clientEmail) {
  149.                             if (isset($clientEmail['email'])) {
  150.                                 $emailEntry = new EmailingClientListEmail();
  151.                                 $emailEntry->setEmail($clientEmail['email']);
  152.                                 $emailEntry->setEmailingClientList($tempList);
  153.                                 $emailEntry->setCreatedAt(new DateTimeImmutable());
  154.                                 $em->persist($emailEntry);
  155.                                 $emailingClientListEmails[] = $emailEntry;
  156.                             }
  157.                         }
  158.                     }
  159.                 }
  160.                 $tempList->setEmailingClientListEmails(new ArrayCollection($emailingClientListEmails));
  161.                 $tempList->setCreatedAt(new DateTimeImmutable());
  162.                 $tempList->setUpdatedAt(new DateTimeImmutable());
  163.                 $em->persist($tempList);
  164.                 $em->flush();
  165.                 $em->persist($survey);
  166.                 $em->flush();
  167.                 $campaign->addDiffusionList($tempList);
  168.                 $campaign->setCollaboratorsData([
  169.                     'selected_ids' => $selectedCollaboratorIds,
  170.                     'temp_list_id' => $tempList->getId()
  171.                 ]);
  172.             }
  173.             $masquerTemplate $request->query->get('masquertemplate'false);
  174.             $em->persist($campaign);
  175.             $em->flush();
  176.             if ($masquerTemplate || $campaign->getSatisfactionClientSurvey()) {
  177.                 return $this->redirectToRoute('app_emailing_client_campaign_create_mail_editor', [
  178.                     'id' => $campaign->getId(),
  179.                     'masquertemplate' => true
  180.                 ]);
  181.             }
  182.             return $this->redirectToRoute('app_emailing_client_campaign_create_mail_editor', [
  183.                 'id' => $campaign->getId()
  184.             ]);
  185.         }
  186.         return $this->render('emailing_client_campaign/create_step_list.html.twig', [
  187.             'form' => $form->createView(),
  188.             'campaign' => $campaign,
  189.             'hasCollaborators' => $form->has('collaborators')
  190.         ]);
  191.     }
  192.     #[Route(path'/get-collaborator-emails'name'app_emailing_client_campaign_get_collaborator_emails')]
  193.     public function getCollaboratorEmails(Request $requestCollaboratorRepository $collaboratorRepository): JsonResponse
  194.     {
  195.         $collaboratorIds $request->query->get('collaboratorIds'null);
  196.         if (!is_array($collaboratorIds)) {
  197.             $collaboratorIds explode(','$collaboratorIds);
  198.         }
  199.         $emails = [];
  200.         foreach ($collaboratorIds as $id) {
  201.             $collaborator $collaboratorRepository->find($id);
  202.             if ($collaborator) {
  203.                 foreach ($collaborator->getClientEmails() as $clientEmail) {
  204.                     $emails[] = [
  205.                         'email' => $clientEmail['email'],
  206.                         'collaborator' => $collaborator->getFullName(),
  207.                         'collaboratorId' => $collaborator->getId(),
  208.                     ];
  209.                 }
  210.             }
  211.         }
  212.         return $this->json($emails);
  213.     }
  214.     /**
  215.      * API route pour récupérer les emails associés à une liste
  216.      */
  217.     #[Route('/get-emails-by-list'name'app_emailing_client_campaign_create_select_diffusion_get_emails'methods: ['GET'])]
  218.     #[Route('/count-emails'name'app_emailing_client_campaign_count_emails'methods: ['GET'])]
  219.     #[IsGranted('ROLE_USER')]
  220.     public function countEmails(Request $requestEmailingClientListRepository $emailingClientListRepository): JsonResponse
  221.     {
  222.         $listIds $request->query->all('lists');
  223.         if (empty($listIds)) {
  224.             return new JsonResponse(['count' => 0]);
  225.         }
  226.         $lists $emailingClientListRepository->findBy(['id' => $listIds]);
  227.         $processedEmails = [];
  228.         foreach ($lists as $list) {
  229.             if ($list->getAccountingFirm() !== $this->getUser()->getAccountingFirm()) {
  230.                 continue;
  231.             }
  232.             foreach ($list->getEmailingClientListEmails() as $email) {
  233.                 $emailAddress $email->getEmail();
  234.                 if (!in_array($emailAddress$processedEmails)) {
  235.                     $processedEmails[] = $emailAddress;
  236.                 }
  237.             }
  238.         }
  239.         return new JsonResponse(['count' => count($processedEmails)]);
  240.     }
  241.     #[Route('/get-emails-by-list'name'app_emailing_client_campaign_create_select_diffusion_get_emails'methods: ['GET'])]
  242.     public function getEmailsByList(Request $requestEmailingClientListRepository $emailingClientListRepository): JsonResponse
  243.     {
  244.         $listId $request->query->get('listId');
  245.         $emailingClientList $emailingClientListRepository->findWithoutTemp($listId);
  246.         if (!$emailingClientList) {
  247.             return new JsonResponse(['error' => 'Liste introuvable'], 404);
  248.         }
  249.         // Récupérer les emails associés à la liste
  250.         $emails $emailingClientList->getEmailingClientListEmails();
  251.         $emailData = [];
  252.         foreach ($emails as $email) {
  253.             $emailData[] = [
  254.                 'id' => $email->getId(),
  255.                 'email' => $email->getEmail(),
  256.             ];
  257.         }
  258.         return new JsonResponse($emailData);
  259.     }
  260.     #[Route(path'/create/mail-editor/{id}'name'app_emailing_client_campaign_create_mail_editor')]
  261.     #[IsGranted('ROLE_USER')]
  262.     public function campagnesCreateMailEditor(Request $requestEntityManagerInterface $emint $idKernelInterface $kernelEmailingClientCampaignRepository $emailingClientCampaignRepositoryAccountingFirmRepository $accountingFirmRepositoryStorageInterface $vichStorage): Response
  263.     {
  264.         $campaign $this->checkValidCampaign($id$emailingClientCampaignRepository);
  265.         if ($kernel->getEnvironment() == 'dev') {
  266.             $mediaViewerUrl "https://actucontent.loc:8080/";
  267.             // $mediaViewerUrl = "https://actucontent.lagence.expert/";
  268.         } else {
  269.             $mediaViewerUrl "https://actucontent.lagence.expert/";
  270.         }
  271.         $accountingFirm $this->getUser()->getAccountingFirm();
  272.         $filePath $vichStorage->resolvePath($accountingFirm'tmpMailingHeader');
  273.         $base64Img null;
  274.         if (file_exists($filePath)) {
  275.             $mimeType mime_content_type($filePath);
  276.             $fileContent file_get_contents($filePath);
  277.             $base64Img 'data:' $mimeType ';base64,' base64_encode($fileContent);
  278.         }
  279.         $accountingFirmJson null;
  280.         if ($accountingFirm) {
  281.             $accountingFirmTmp $accountingFirmRepository->findOneBy(['id' => $accountingFirm->getId()]);
  282.             // Nettoyage des valeurs pour éviter les problèmes de JSON
  283.             $address trim(preg_replace('/\s+/'' '$accountingFirmTmp->getAddress() . " " $accountingFirmTmp->getZipCode() . " " $accountingFirmTmp->getCity()));
  284.             $url trim($accountingFirmTmp->getUrl());
  285.             $phone trim(preg_replace('/\s+/'' '$accountingFirmTmp->getPhone()));
  286.             $accountingFirmArray = [
  287.                 'id' => $accountingFirmTmp->getId(),
  288.                 'name' => trim($accountingFirmTmp->getName()),
  289.                 'logo' => $base64Img,
  290.                 'address' => $address,
  291.                 'email' => trim($accountingFirmTmp->getEmail()),
  292.                 'phone' => $phone,
  293.                 'site' => $url,
  294.             ];
  295.             $accountingFirmJson json_encode($accountingFirmArrayJSON_HEX_QUOT JSON_HEX_TAG JSON_HEX_AMP JSON_HEX_APOS JSON_UNESCAPED_UNICODE);
  296.             $accountingFirmJson base64_encode($accountingFirmJson);
  297.         }
  298.         $form $this->createForm(EmailingClientCampaignMailType::class, $campaign);
  299.         $form->handleRequest($request);
  300.         if ($form->isSubmitted() && $form->isValid()) {
  301.             $mjmlStructure json_decode($form->get('mjmlStructure')->getData(), true);
  302.             $tmpMjmlStructure $mjmlStructure;
  303.             foreach ($mjmlStructure as $key => $part) {
  304.                 if ($part['type'] == 'image' && strpos($part['data']['image'], 'data:image') === 0) {
  305.                     $base64Image $part['data']['image']; // Récupérer la partie base64 de l'image
  306.                     $imageData base64_decode(preg_replace('#^data:image/\w+;base64,#i'''$base64Image)); // Supprimer le préfixe et décoder la base64
  307.                     $imageName uniqid() . '.png'// Générer un nom de fichier unique
  308.                     $destinationDirectory 'images/emailing_client/' $campaign->getAccountingFirm()->getId(); // Chemin du répertoire
  309.                     $destinationPath $destinationDirectory "/" $imageName// Chemin de destination pour sauvegarder l'image
  310.                     // Vérifier si le dossier existe, sinon le créer
  311.                     if (!is_dir($destinationDirectory)) {
  312.                         mkdir($destinationDirectory0755true); // Crée le dossier avec des permissions 0755
  313.                     }
  314.                     // Enregistrer les données binaires dans un fichier sur le serveur
  315.                     if (file_put_contents($destinationPath$imageData)) {
  316.                         $tmpMjmlStructure[$key]['data']['image'] = $destinationPath;
  317.                     }
  318.                 } elseif ($part['type'] == 'file' && isset($part['data']['file']) && strpos($part['data']['file'], 'data:') === 0) {
  319.                     $base64File $part['data']['file']; // Récupérer la partie base64 du fichier
  320.                     $fileData base64_decode(preg_replace('#^data:application/\w+;base64,#i'''$base64File)); // Supprimer le préfixe et décoder la base64
  321.                     // Déterminer le type MIME pour obtenir l'extension du fichier
  322.                     $mimeType explode(';'explode(':'$base64File)[1])[0]; // Obtenir le type MIME
  323.                     $extension '';
  324.                     // Assigner l'extension en fonction du type MIME
  325.                     switch ($mimeType) {
  326.                         case 'application/pdf':
  327.                             $extension 'pdf';
  328.                             break;
  329.                         case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
  330.                             $extension 'docx';
  331.                             break;
  332.                         // Ajoute d'autres types de fichiers si nécessaire
  333.                         default:
  334.                             $extension 'bin'// Utilise un type par défaut si le type n'est pas reconnu
  335.                             break;
  336.                     }
  337.                     $fileName uniqid() . '.' $extension// Générer un nom de fichier unique avec l'extension appropriée
  338.                     $destinationDirectory 'files/emailing_client/' $campaign->getAccountingFirm()->getId(); // Chemin du répertoire
  339.                     $destinationPath $destinationDirectory "/" $fileName// Chemin de destination pour sauvegarder le fichier
  340.                     // Vérifier si le dossier existe, sinon le créer
  341.                     if (!is_dir($destinationDirectory)) {
  342.                         mkdir($destinationDirectory0755true); // Crée le dossier avec des permissions 0755
  343.                     }
  344.                     // Enregistrer les données binaires dans un fichier sur le serveur
  345.                     if (file_put_contents($destinationPath$fileData)) {
  346.                         $tmpMjmlStructure[$key]['data']['file'] = $destinationPath// Met à jour le chemin du fichier enregistré
  347.                     }
  348.                 }
  349.             }
  350.             $mjmlStructure $tmpMjmlStructure;
  351.             $campaign->setmjmlStructure($mjmlStructure);
  352.             $em->persist($campaign);
  353.             $em->flush();
  354.             return $this->redirectToRoute('app_emailing_client_campaign_create_sending_options', ['id' => $campaign->getId()]);
  355.         }
  356.         // Vérifier si tous les contacts ont les champs requis
  357.         $allContactsHaveFirstName true;
  358.         $allContactsHaveLastName true;
  359.         $allContactsHaveCompany true;
  360.         foreach ($campaign->getDiffusionLists() as $list) {
  361.             foreach ($list->getEmailingClientListEmails() as $email) {
  362.                 if (empty($email->getFirstName())) {
  363.                     $allContactsHaveFirstName false;
  364.                 }
  365.                 if (empty($email->getLastName())) {
  366.                     $allContactsHaveLastName false;
  367.                 }
  368.                 if (empty($email->getCompanyName())) {
  369.                     $allContactsHaveCompany false;
  370.                 }
  371.                 // Si tous les champs sont faux, on peut arrêter la vérification
  372.                 if (!$allContactsHaveFirstName && !$allContactsHaveLastName && !$allContactsHaveCompany) {
  373.                     break 2;
  374.                 }
  375.             }
  376.         }
  377.         return $this->render('emailing_client_campaign/create_mail.html.twig', [
  378.             'form' => $form->createView(),
  379.             'campaign' => $campaign,
  380.             'mediaViewerUrl' => $mediaViewerUrl,
  381.             'accountingFirmJson' => $accountingFirmJson,
  382.             'allContactsHaveCompany' => $allContactsHaveCompany,
  383.             'allContactsHaveFirstName' => $allContactsHaveFirstName,
  384.             'allContactsHaveLastName' => $allContactsHaveLastName
  385.         ]);
  386.     }
  387.     #[Route(path'/create/sending-options/{id}'name'app_emailing_client_campaign_create_sending_options')]
  388.     #[IsGranted('ROLE_USER')]
  389.     public function campagnesCreateSendingOptions(Request $requestEntityManagerInterface $emint $idEmailingClientCampaignRepository $emailingClientCampaignRepositoryEmailingClientTokenManageService $emailingClientTokenManageService): Response
  390.     {
  391.         $campaign $this->checkValidCampaign($id$emailingClientCampaignRepository);
  392.         $form $this->createForm(EmailingClientCampaignDateType::class, $campaign);
  393.         $form->handleRequest($request);
  394.         if ($form->isSubmitted() && $form->isValid()) {
  395.             if (!$emailingClientTokenManageService->campaignEnoughTokenRemainingOnMonth($campaign)) {
  396.                 $this->addFlash('warning''Attention : Cette campagne dépasse votre quota mensuel d\'emails.');
  397.                 // Envoyer un email au client pour l'informer du dépassement
  398.                 $emailingClientTokenManageService->sendQuotaExceededEmail($campaign->getAccountingFirm());
  399.                 $this->redirectToRoute('app_emailing_client_campaign_create_sending_options', ['id' => $campaign->getId()]);
  400.             }
  401.             // check if campaign has enough token remaining
  402.             /*  if ($emailingClientTokenManageService->campaignEnoughTokenRemainingOnMonth($campaign))
  403.             {*/
  404.             $campaign->setStatus("PROGRAMMED");
  405.             $em->persist($campaign);
  406.             $em->flush();
  407.             $dateDisplay $campaign->getSendDate()->format('d/m/Y');
  408.             $this->addFlash('successSendEmailingClient'"Votre campagne a bien été programmée, elle sera envoyée le " $dateDisplay ".");
  409.             return $this->redirectToRoute('app_emailing_client_campaign_index');
  410.             /* } else {
  411.                $this->addFlash('error', 'Vous n\'avez pas assez d\'emails en réserve pour programmer cette campagne à cette date.');
  412.              }*/
  413.         }
  414.         return $this->render('emailing_client_campaign/create_recap.html.twig', [
  415.             'form' => $form->createView(),
  416.             'campaign' => $campaign,
  417.         ]);
  418.     }
  419.     #[Route(path'/create/sending-now/{id}'name'app_emailing_client_campaign_create_sending_now')]
  420.     #[IsGranted('ROLE_USER')]
  421.     public function campagnesCreateSendingNow(Request $requestEntityManagerInterface $emint $idEmailingClientCampaignRepository $emailingClientCampaignRepositoryAccountingFirmRepository $accountingFirmRepositoryMailing $mailingServiceEmailingClientTokenManageService $emailingClientTokenManageService): Response
  422.     {
  423.         $campaign $this->checkValidCampaign($id$emailingClientCampaignRepository);
  424.         if (!$campaign instanceof EmailingClientCampaign) {
  425.             return $campaign;
  426.         }
  427.         $diffusionLists $campaign->getDiffusionLists();
  428.         $emails = [];
  429.         $processedEmails = []; // Pour éviter les doublons
  430.         foreach ($diffusionLists as $list) {
  431.             foreach ($list->getEmailingClientListEmails() as $email) {
  432.                 $emailAddress $email->getEmail();
  433.                 if (!in_array($emailAddress$processedEmails)) {
  434.                     $emails[] = $emailAddress;
  435.                     $processedEmails[] = $emailAddress;
  436.                 }
  437.             }
  438.         }
  439.         if (!$emailingClientTokenManageService->campaignEnoughTokenRemainingOnMonth($campaign)) {
  440.             $this->addFlash('warning''Attention : Cette campagne dépasse votre quota mensuel d\'emails.');
  441.             // Envoyer un email au client pour l'informer du dépassement
  442.             //get token used
  443.             $emailingClientTokenManageService->sendQuotaExceededEmail($campaign->getAccountingFirm());
  444.             return $this->redirectToRoute('app_emailing_client_campaign_create_sending_options', ['id' => $campaign->getId()]);
  445.         }
  446.         $dateSend = new DateTime();
  447.         $dateSend->setTimezone(new DateTimeZone('Europe/Paris'));
  448.         $dateSend->setTime($dateSend->format('H') + 100);
  449.         $campaign->setSendDate($dateSend);
  450.         $campaign->setStatus("PROGRAMMED");
  451.         $em->persist($campaign);
  452.         $cabinet $campaign->getAccountingFirm();
  453.         $task = new MailingTask();
  454.         $task->setAccountingFirm($cabinet);
  455.         $task->setType("EMAILING_CLIENT");
  456.         $task->setStatus("PENDING");
  457.         $task->setEmailingClientCampaign($campaign);
  458.         $task->setWebinaireId(null);
  459.         if ($campaign->getSurvey() != null) {
  460.             $survey $campaign->getSurvey();
  461.             $task->setSurvey($survey);
  462.         }
  463.         $em->persist($task);
  464.         $em->flush();
  465.         $mailingService->createMailingLog($task"Initialisation de l'envoi.""INIT");
  466.         $this->messageBus->dispatch(new MailingProcessMessage(intval($task->getId())));
  467.         $this->addFlash('successSendEmailingClient'"Votre campagne a bien été ajoutée à la file d'attente, elle sera envoyée dès que possible.");
  468.         return $this->redirectToRoute('app_emailing_client_campaign_index');
  469.     }
  470.     #[Route(path'/campagnes/continue/{id}'name'app_emailing_client_campaign_continue')]
  471.     #[IsGranted('ROLE_USER')]
  472.     public function campagnesContinue(Request $requestEntityManagerInterface $emint $idEmailingClientCampaignRepository $emailingClientCampaignRepository): Response
  473.     {
  474.         $campaign $this->checkValidCampaign($id$emailingClientCampaignRepository);
  475.         if (!$campaign instanceof EmailingClientCampaign) {
  476.             return $campaign;
  477.         }
  478.         if ($campaign->getSendDate() == null && $campaign->getMjmlStructure() != null) {
  479.             return $this->redirectToRoute("app_emailing_client_campaign_create_sending_options", ['id' => $campaign->getId()]);
  480.         } elseif ($campaign->getMjmlStructure() == null && count($campaign->getDiffusionLists()) > 0) {
  481.             return $this->redirectToRoute("app_emailing_client_campaign_create_mail_editor", ['id' => $campaign->getId()]);
  482.         } elseif (count($campaign->getDiffusionLists()) === && $campaign->getName() != null) {
  483.             return $this->redirectToRoute("app_emailing_client_campaign_create_select_diffusion", ['id' => $campaign->getId()]);
  484.         } elseif ($campaign->getName() != null && count($campaign->getDiffusionLists()) > && $campaign->getMjmlStructure() != null && $campaign->getSendDate() != null) {
  485.             return $this->redirectToRoute("app_emailing_client_campaign_create_sending_options", ['id' => $campaign->getId()]);
  486.         }
  487.         return $this->redirectToRoute("app_emailing_client_campaign_create_name", ['id' => $campaign->getId()]);
  488.     }
  489.     #[Route(path'/campagnes/preview/{id}'name'app_emailing_client_campaign_preview')]
  490.     #[IsGranted('ROLE_USER')]
  491.     public function campagnesPreview(int $idMailingCommunicationRepository $mailingCommunicationRepositoryMjmlExtension $mjmlRendererEmailingClientCampaignRepository $emailingClientCampaignRepository): Response
  492.     {
  493.         $campaign $this->checkValidCampaign($id$emailingClientCampaignRepositoryfalse);
  494.         $mjmlStructure $campaign->getmjmlStructure();
  495.         $mediaViewerUrl "https://actucontent.loc:8000/";
  496.         if ($this->getParameter('kernel.environment') === 'prod') {
  497.             $mediaViewerUrl "https://actucontent.lagence.expert/";
  498.         }
  499.         $render_data = [
  500.             'mjmlCode' => $mjmlStructure,
  501.             'cabinet' => $campaign->getAccountingFirm(),
  502.             'serverUrl' => $mediaViewerUrl
  503.         ];
  504.         return new Response($mjmlRenderer->render($this->render('mjml/emailing_client/emailing_client.mjml'$render_data)));
  505.     }
  506.     #[Route(path'/campagnes/duplicate/{id}'name'app_emailing_client_campaign_duplicate')]
  507.     #[IsGranted('ROLE_USER')]
  508.     public function campagnesDuplicate(Request $requestEntityManagerInterface $emint $idEmailingClientCampaignRepository $emailingClientCampaignRepository): Response
  509.     {
  510.         $campaign $emailingClientCampaignRepository->findOneBy(['id' => $id]);
  511.         if ($campaign == null) {
  512.             $this->addFlash('error''Aucune campagne n\'a été trouvé avec cet identifiant.');
  513.             return $this->redirectToRoute("app_emailing_client_campaign_index");
  514.         }
  515.         if ($campaign->getAccountingFirm() != $this->getUser()->getAccountingFirm()) {
  516.             $this->addFlash('error''Vous n\'avez pas les droits pour accéder à cette campagne.');
  517.             return $this->redirectToRoute("app_emailing_client_campaign_index");
  518.         }
  519.         $newCampaign = new EmailingClientCampaign();
  520.         $newCampaign->setName($campaign->getName())
  521.             ->setObject($campaign->getObject())
  522.             ->setMjmlStructure($campaign->getMjmlStructure())
  523.             ->setStatus("ON_CREATE");
  524.         // Copie des listes de diffusion
  525.         foreach ($campaign->getDiffusionLists() as $list) {
  526.             $newCampaign->addDiffusionList($list);
  527.         }
  528.         if ($campaign->getSatisfactionClientSurvey() != null) {
  529.             $newCampaign->setSatisfactionClientSurvey($campaign->getSatisfactionClientSurvey());
  530.         }
  531.         if ($campaign->getCollaboratorsData() != null) {
  532.             $newCampaign->setCollaboratorsData($campaign->getCollaboratorsData());
  533.         }
  534.         $em->persist($newCampaign);
  535.         $em->flush();
  536.         return $this->redirectToRoute('app_emailing_client_campaign_create_name', ['id' => $newCampaign->getId()]);
  537.     }
  538.     #[Route(path'/campagnes/{id}/delete'name'app_emailing_client_campaign_delete'methods: ['POST'])]
  539.     #[IsGranted('ROLE_USER')]
  540.     public function delete(Request $requestEmailingClientCampaign $campaignEntityManagerInterface $em): Response
  541.     {
  542.         if ($this->isCsrfTokenValid('delete' $campaign->getId(), $request->request->get('_token'))) {
  543.             $tasks $campaign->getMailingTask();
  544.             foreach ($tasks as $task) {
  545.                 $task->setEmailingClientCampaign(null);
  546.                 $em->persist($task);
  547.             }
  548.             $em->remove($campaign);
  549.             $em->flush();
  550.         }
  551.         $this->addFlash('success''La campagne a bien été supprimée.');
  552.         return $this->redirectToRoute('app_emailing_client_campaign_index');
  553.     }
  554.     #[Route(path'/campagnes/{id}/stats'name'app_emailing_client_campaign_stats')]
  555.     #[IsGranted('ROLE_USER')]
  556.     public function stats(Request $requestEmailingClientCampaign $campaignCacheInterface $cache): Response
  557.     {
  558.         // Vérifier que la campagne appartient bien au cabinet connecté
  559.         if ($campaign->getAccountingFirm() !== $this->getUser()->getAccountingFirm()) {
  560.             throw $this->createAccessDeniedException('Vous n\'avez pas accès à cette campagne.');
  561.         }
  562.         // Vérifier que la campagne est bien envoyée
  563.         if ($campaign->getStatus() !== 'SEND') {
  564.             throw $this->createAccessDeniedException('Cette campagne n\'a pas encore été envoyée.');
  565.         }
  566.         $force $request->query->getBoolean('force'false);
  567.         // Throttle: éviter de forcer trop souvent (par user + campagne)
  568.         if ($force) {
  569.             $cooldownSeconds 3600;
  570.             $userId $this->getUser()->getId();
  571.             $campaignId $campaign->getId();
  572.             $throttleKey sprintf('emailing_client_force_refresh_%d_%d'$campaignId$userId);
  573.             $lastForcedAt $cache->get($throttleKey, function (ItemInterface $item) {
  574.                 // si pas encore forcé, on retourne 0 (epoch)
  575.                 $item->expiresAfter(3600);
  576.                 return 0;
  577.             });
  578.             $now time();
  579.             if (is_int($lastForcedAt) && ($now $lastForcedAt) < $cooldownSeconds) {
  580.                 $remaining $cooldownSeconds - ($now $lastForcedAt);
  581.                 if ($remaining 60) {
  582.                     $timeLabel $remaining ' seconde' . ($remaining 's' '');
  583.                 } elseif ($remaining 3600) {
  584.                     $minutes = (int) ceil($remaining 60);
  585.                     $timeLabel $minutes ' minute' . ($minutes 's' '');
  586.                 } else {
  587.                     $hours = (int) floor($remaining 3600);
  588.                     $minutes = (int) ceil(($remaining 3600) / 60);
  589.                     if ($minutes 0) {
  590.                         $timeLabel $hours ' heure' . ($hours 's' '') . ' et ' $minutes ' minute' . ($minutes 's' '');
  591.                     } else {
  592.                         $timeLabel $hours ' heure' . ($hours 's' '');
  593.                     }
  594.                 }
  595.                 $this->addFlash(
  596.                     'error',
  597.                     sprintf('Rafraîchissement déjà effectué. Vous pourrez réessayer dans %s.'$timeLabel)
  598.                 );
  599.                 $force false;
  600.                 return $this->redirectToRoute('app_emailing_client_campaign_stats', ['id' => $campaign->getId()]);
  601.             } else {
  602.                 $cache->delete($throttleKey);
  603.                 $cache->get($throttleKey, function (ItemInterface $item) use ($now$cooldownSeconds) {
  604.                     $item->expiresAfter($cooldownSeconds 10);
  605.                     return $now;
  606.                 });
  607.             }
  608.         }
  609.         // Récupérer les statistiques depuis Mailjet
  610.         try {
  611.             $stats $this->mailjetService->getCampaignStatsEmailingClient($campaign$force);
  612.             // Est-ce que ça a changé ?
  613.             $changed false;
  614.             // floats : on compare arrondis (tes stats sont déjà round à 2)
  615.             if (round((float)($campaign->getOpenRate() ?? 0), 2) !== round((float)($stats['openRate'] ?? 0), 2)) $changed true;
  616.             if (round((float)($campaign->getClickRate() ?? 0), 2) !== round((float)($stats['clickRate'] ?? 0), 2)) $changed true;
  617.             if (round((float)($campaign->getUnsubscribeRate() ?? 0), 2) !== round((float)($stats['unsubscribeRate'] ?? 0), 2)) $changed true;
  618.             if (round((float)($campaign->getBounceRate() ?? 0), 2) !== round((float)($stats['bounceRate'] ?? 0), 2)) $changed true;
  619.             // int
  620.             if ((int)($campaign->getDeliveredCount() ?? 0) !== (int)($stats['deliveredCount'] ?? 0)) $changed true;
  621.             // Si changement OU stats jamais enregistrées -> on persiste
  622.             if ($changed || $campaign->getStatsUpdatedAt() === null) {
  623.                 $campaign->setOpenRate((float)($stats['openRate'] ?? 0));
  624.                 $campaign->setClickRate((float)($stats['clickRate'] ?? 0));
  625.                 $campaign->setUnsubscribeRate((float)($stats['unsubscribeRate'] ?? 0));
  626.                 $campaign->setBounceRate((float)($stats['bounceRate'] ?? 0));
  627.                 $campaign->setDeliveredCount((int)($stats['deliveredCount'] ?? 0));
  628.                 $campaign->setStatsUpdatedAt(new \DateTimeImmutable());
  629.                 $this->em->flush();
  630.             }
  631.             $this->em->flush();
  632.             if ($force) {
  633.                 $this->addFlash('success''Statistiques rafraîchies depuis Mailjet.');
  634.             }
  635.         } catch (Exception $e) {
  636.             // En cas d'erreur, utiliser les valeurs stockées
  637.             $stats = [
  638.                 'openRate' => $campaign->getOpenRate() ?? 0,
  639.                 'clickRate' => $campaign->getClickRate() ?? 0,
  640.                 'unsubscribeRate' => $campaign->getUnsubscribeRate() ?? 0,
  641.                 'bounceRate' => $campaign->getBounceRate() ?? 0,
  642.                 'deliveredCount' => $campaign->getDeliveredCount() ?? 0,
  643.             ];
  644.             $this->addFlash('warning''Impossible de récupérer les stats Mailjet, affichage des stats enregistrées.');
  645.         }
  646.         return $this->render('emailing_client_campaign/stats.html.twig', [
  647.             'campaign' => $campaign,
  648.             'openRate' => number_format($stats['openRate'] ?? 01) . '%',
  649.             'clickRate' => number_format($stats['clickRate'] ?? 01) . '%',
  650.             'unsubscribeRate' => number_format($stats['unsubscribeRate'] ?? 01) . '%',
  651.             'bounceRate' => number_format($stats['bounceRate'] ?? 01) . '%',
  652.             'deliveredCount' => (int)($stats['deliveredCount'] ?? 0),
  653.             'openedCount' => (int)($stats['openedCount'] ?? 0),
  654.             'clickedCount' => (int)($stats['clickedCount'] ?? 0),
  655.             'unsubscribedCount' => (int)($stats['unsubscribedCount'] ?? 0),
  656.             'hardBouncedCount' => (int)($stats['hardBouncedCount'] ?? 0),
  657.             'softBouncedCount' => (int)($stats['softBouncedCount'] ?? 0),
  658.         ]);
  659.     }
  660. }