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