<?php
namespace App\Controller;
use App\Datatable\EmailingClientCampaignTable;
use App\Entity\Collaborator;
use App\Entity\EmailingClientCampaign;
use App\Entity\EmailingClientList;
use App\Entity\EmailingClientListEmail;
use App\Entity\MailingTask;
use App\Form\EmailingClientCampaignDateType;
use App\Form\EmailingClientCampaignMailType;
use App\Form\EmailingClientCampaignNameType;
use App\Form\EmailingClientCampaignSelectListType;
use App\Form\EmailingClientCampaignSelectTagType;
use App\Message\MailingProcessMessage;
use App\Repository\AccountingFirmRepository;
use App\Repository\CollaboratorRepository;
use App\Repository\EmailingClientCampaignRepository;
use App\Repository\EmailingClientListRepository;
use App\Repository\MailingCommunicationRepository;
use App\Repository\SatisfactionClientSurveyRepository;
use App\Services\EmailingClientTokenManage;
use App\Services\EmailingClientTokenManageService;
use App\Services\Mailing;
use App\Services\MailjetService;
use DateTime;
use DateTimeImmutable;
use DateTimeZone;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Qferrer\Mjml\Twig\MjmlExtension;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Vich\UploaderBundle\Storage\StorageInterface;
#[Route('/emailing-client/campagnes')]
class EmailingClientCampaignController extends AbstractController
{
public function __construct(
protected readonly MessageBusInterface $messageBus,
private readonly EntityManagerInterface $em,
private readonly MailjetService $mailjetService,
private readonly SatisfactionClientSurveyRepository $surveyRepository,
private readonly EmailingClientCampaignRepository $emailingClientCampaignRepository
) {
}
private function checkValidCampaign($id, EmailingClientCampaignRepository $emailingClientCampaignRepository, $strict = true): EmailingClientCampaign|\Symfony\Component\HttpFoundation\RedirectResponse
{
$campaign = $emailingClientCampaignRepository->findOneBy(['id' => $id]);
if ($campaign == null) {
$this->addFlash('error', 'Aucune campagne n\'a été trouvé avec cet identifiant.');
return $this->redirectToRoute("app_emailing_client_campaign_index");
}
if ($campaign->getAccountingFirm() != $this->getUser()->getAccountingFirm()) {
$this->addFlash('error', 'Vous n\'avez pas les droits pour accéder à cette campagne.');
return $this->redirectToRoute("app_emailing_client_campaign_index");
}
if ($strict) {
if ($campaign->getStatus() != "ON_CREATE" && $campaign->getStatus() != "PROGRAMMED") {
$this->addFlash('error', 'Votre campagne a déjà été traité, vous ne pouvez plus la modifier.');
return $this->redirectToRoute("app_emailing_client_campaign_index");
}
}
return $campaign;
}
#[Route('/', name: 'app_emailing_client_campaign_index')]
#[IsGranted('ROLE_USER')]
public function index(Request $request, EmailingClientCampaignTable $datatable): Response
{
if (!$this->isGranted('access_emailing')) {
return $this->redirectToRoute('app_emailing_client_sender_edit');
}
$table = $datatable->buildTable();
$table->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse();
}
return $this->render('emailing_client_campaign/index.html.twig', [
'datatable' => $table
]);
}
#[Route(path: '/create/name/{id}', name: 'app_emailing_client_campaign_create_name', defaults: ['id' => null])]
#[IsGranted('ROLE_USER')]
public function campagnesCreateName(Request $request, EntityManagerInterface $em, ?int $id = null, EmailingClientCampaignRepository $emailingClientCampaignRepository): Response
{
$campaign = null;
if ($id != null) {
$campaign = $emailingClientCampaignRepository->findOneBy(['id' => $id]);
}
if (!$campaign) {
$campaign = new EmailingClientCampaign();
}
$form = $this->createForm(EmailingClientCampaignNameType::class, $campaign);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$masquerTemplate = $request->query->get('masquertemplate', false);
$campaign->setStatus("ON_CREATE");
$campaign->setAccountingFirm($this->getUser()->getAccountingFirm());
$em->persist($campaign);
$em->flush();
if ($masquerTemplate) {
return $this->redirectToRoute('app_emailing_client_campaign_create_select_diffusion', ['id' => $campaign->getId(), 'masquertemplate' => true]);
} else {
return $this->redirectToRoute('app_emailing_client_campaign_create_select_diffusion', ['id' => $campaign->getId()]);
}
}
return $this->render('emailing_client_campaign/create_step_name.html.twig', [
'form' => $form->createView(),
'campaign' => $campaign
]);
}
#[Route(path: '/create/select-diffusion/{id}', name: 'app_emailing_client_campaign_create_select_diffusion', defaults: ['id' => null])]
#[IsGranted('ROLE_USER')]
public function campagnesCreateSelectDiffusionList(
Request $request,
EntityManagerInterface $em,
EmailingClientCampaignRepository $emailingClientCampaignRepository,
EmailingClientListRepository $emailingClientListRepository,
?int $id = null,
): Response {
$campaign = $this->checkValidCampaign($id, $emailingClientCampaignRepository);
$user = $this->getUser();
$form = $this->createForm(EmailingClientCampaignSelectListType::class, $campaign);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$collaborators = $form->has('collaborators') ? $form->get('collaborators')->getData() : null;
if ($collaborators !== null) {
$listName = 'Temp - Campagne ' . $campaign->getName() . ' - ' . (new DateTime())->format('Y-m-d H:i');
$tempList = new EmailingClientList();
$tempList->setName($listName)
->setAccountingFirm($user->getAccountingFirm())
->setIsTemporary(true);
$emailingClientListEmails = [];
$selectedCollaboratorIds = [];
$survey = $campaign->getSatisfactionClientSurvey();
foreach ($collaborators as $collaborator) {
if ($collaborator instanceof Collaborator) {
$survey->addCollaborator($collaborator);
$selectedCollaboratorIds[] = $collaborator->getId();
foreach ($collaborator->getClientEmails() as $clientEmail) {
if (isset($clientEmail['email'])) {
$emailEntry = new EmailingClientListEmail();
$emailEntry->setEmail($clientEmail['email']);
$emailEntry->setEmailingClientList($tempList);
$emailEntry->setCreatedAt(new DateTimeImmutable());
$em->persist($emailEntry);
$emailingClientListEmails[] = $emailEntry;
}
}
}
}
$tempList->setEmailingClientListEmails(new ArrayCollection($emailingClientListEmails));
$tempList->setCreatedAt(new DateTimeImmutable());
$tempList->setUpdatedAt(new DateTimeImmutable());
$em->persist($tempList);
$em->flush();
$em->persist($survey);
$em->flush();
$campaign->addDiffusionList($tempList);
$campaign->setCollaboratorsData([
'selected_ids' => $selectedCollaboratorIds,
'temp_list_id' => $tempList->getId()
]);
}
$masquerTemplate = $request->query->get('masquertemplate', false);
$em->persist($campaign);
$em->flush();
if ($masquerTemplate || $campaign->getSatisfactionClientSurvey()) {
return $this->redirectToRoute('app_emailing_client_campaign_create_mail_editor', [
'id' => $campaign->getId(),
'masquertemplate' => true
]);
}
return $this->redirectToRoute('app_emailing_client_campaign_create_mail_editor', [
'id' => $campaign->getId()
]);
}
return $this->render('emailing_client_campaign/create_step_list.html.twig', [
'form' => $form->createView(),
'campaign' => $campaign,
'hasCollaborators' => $form->has('collaborators')
]);
}
#[Route(path: '/get-collaborator-emails', name: 'app_emailing_client_campaign_get_collaborator_emails')]
public function getCollaboratorEmails(Request $request, CollaboratorRepository $collaboratorRepository): JsonResponse
{
$collaboratorIds = $request->query->get('collaboratorIds', null);
if (!is_array($collaboratorIds)) {
$collaboratorIds = explode(',', $collaboratorIds);
}
$emails = [];
foreach ($collaboratorIds as $id) {
$collaborator = $collaboratorRepository->find($id);
if ($collaborator) {
foreach ($collaborator->getClientEmails() as $clientEmail) {
$emails[] = [
'email' => $clientEmail['email'],
'collaborator' => $collaborator->getFullName(),
'collaboratorId' => $collaborator->getId(),
];
}
}
}
return $this->json($emails);
}
/**
* API route pour récupérer les emails associés à une liste
*/
#[Route('/get-emails-by-list', name: 'app_emailing_client_campaign_create_select_diffusion_get_emails', methods: ['GET'])]
#[Route('/count-emails', name: 'app_emailing_client_campaign_count_emails', methods: ['GET'])]
#[IsGranted('ROLE_USER')]
public function countEmails(Request $request, EmailingClientListRepository $emailingClientListRepository): JsonResponse
{
$listIds = $request->query->all('lists');
if (empty($listIds)) {
return new JsonResponse(['count' => 0]);
}
$lists = $emailingClientListRepository->findBy(['id' => $listIds]);
$processedEmails = [];
foreach ($lists as $list) {
if ($list->getAccountingFirm() !== $this->getUser()->getAccountingFirm()) {
continue;
}
foreach ($list->getEmailingClientListEmails() as $email) {
$emailAddress = $email->getEmail();
if (!in_array($emailAddress, $processedEmails)) {
$processedEmails[] = $emailAddress;
}
}
}
return new JsonResponse(['count' => count($processedEmails)]);
}
#[Route('/get-emails-by-list', name: 'app_emailing_client_campaign_create_select_diffusion_get_emails', methods: ['GET'])]
public function getEmailsByList(Request $request, EmailingClientListRepository $emailingClientListRepository): JsonResponse
{
$listId = $request->query->get('listId');
$emailingClientList = $emailingClientListRepository->findWithoutTemp($listId);
if (!$emailingClientList) {
return new JsonResponse(['error' => 'Liste introuvable'], 404);
}
// Récupérer les emails associés à la liste
$emails = $emailingClientList->getEmailingClientListEmails();
$emailData = [];
foreach ($emails as $email) {
$emailData[] = [
'id' => $email->getId(),
'email' => $email->getEmail(),
];
}
return new JsonResponse($emailData);
}
#[Route(path: '/create/mail-editor/{id}', name: 'app_emailing_client_campaign_create_mail_editor')]
#[IsGranted('ROLE_USER')]
public function campagnesCreateMailEditor(Request $request, EntityManagerInterface $em, int $id, KernelInterface $kernel, EmailingClientCampaignRepository $emailingClientCampaignRepository, AccountingFirmRepository $accountingFirmRepository, StorageInterface $vichStorage): Response
{
$campaign = $this->checkValidCampaign($id, $emailingClientCampaignRepository);
if ($kernel->getEnvironment() == 'dev') {
$mediaViewerUrl = "https://actucontent.loc:8080/";
// $mediaViewerUrl = "https://actucontent.lagence.expert/";
} else {
$mediaViewerUrl = "https://actucontent.lagence.expert/";
}
$accountingFirm = $this->getUser()->getAccountingFirm();
$filePath = $vichStorage->resolvePath($accountingFirm, 'tmpMailingHeader');
$base64Img = null;
if (file_exists($filePath)) {
$mimeType = mime_content_type($filePath);
$fileContent = file_get_contents($filePath);
$base64Img = 'data:' . $mimeType . ';base64,' . base64_encode($fileContent);
}
$accountingFirmJson = null;
if ($accountingFirm) {
$accountingFirmTmp = $accountingFirmRepository->findOneBy(['id' => $accountingFirm->getId()]);
// Nettoyage des valeurs pour éviter les problèmes de JSON
$address = trim(preg_replace('/\s+/', ' ', $accountingFirmTmp->getAddress() . " " . $accountingFirmTmp->getZipCode() . " " . $accountingFirmTmp->getCity()));
$url = trim($accountingFirmTmp->getUrl());
$phone = trim(preg_replace('/\s+/', ' ', $accountingFirmTmp->getPhone()));
$accountingFirmArray = [
'id' => $accountingFirmTmp->getId(),
'name' => trim($accountingFirmTmp->getName()),
'logo' => $base64Img,
'address' => $address,
'email' => trim($accountingFirmTmp->getEmail()),
'phone' => $phone,
'site' => $url,
];
$accountingFirmJson = json_encode($accountingFirmArray, JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_UNESCAPED_UNICODE);
$accountingFirmJson = base64_encode($accountingFirmJson);
}
$form = $this->createForm(EmailingClientCampaignMailType::class, $campaign);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$mjmlStructure = json_decode($form->get('mjmlStructure')->getData(), true);
$tmpMjmlStructure = $mjmlStructure;
foreach ($mjmlStructure as $key => $part) {
if ($part['type'] == 'image' && strpos($part['data']['image'], 'data:image') === 0) {
$base64Image = $part['data']['image']; // Récupérer la partie base64 de l'image
$imageData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $base64Image)); // Supprimer le préfixe et décoder la base64
$imageName = uniqid() . '.png'; // Générer un nom de fichier unique
$destinationDirectory = 'images/emailing_client/' . $campaign->getAccountingFirm()->getId(); // Chemin du répertoire
$destinationPath = $destinationDirectory . "/" . $imageName; // Chemin de destination pour sauvegarder l'image
// Vérifier si le dossier existe, sinon le créer
if (!is_dir($destinationDirectory)) {
mkdir($destinationDirectory, 0755, true); // Crée le dossier avec des permissions 0755
}
// Enregistrer les données binaires dans un fichier sur le serveur
if (file_put_contents($destinationPath, $imageData)) {
$tmpMjmlStructure[$key]['data']['image'] = $destinationPath;
}
} elseif ($part['type'] == 'file' && isset($part['data']['file']) && strpos($part['data']['file'], 'data:') === 0) {
$base64File = $part['data']['file']; // Récupérer la partie base64 du fichier
$fileData = base64_decode(preg_replace('#^data:application/\w+;base64,#i', '', $base64File)); // Supprimer le préfixe et décoder la base64
// Déterminer le type MIME pour obtenir l'extension du fichier
$mimeType = explode(';', explode(':', $base64File)[1])[0]; // Obtenir le type MIME
$extension = '';
// Assigner l'extension en fonction du type MIME
switch ($mimeType) {
case 'application/pdf':
$extension = 'pdf';
break;
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
$extension = 'docx';
break;
// Ajoute d'autres types de fichiers si nécessaire
default:
$extension = 'bin'; // Utilise un type par défaut si le type n'est pas reconnu
break;
}
$fileName = uniqid() . '.' . $extension; // Générer un nom de fichier unique avec l'extension appropriée
$destinationDirectory = 'files/emailing_client/' . $campaign->getAccountingFirm()->getId(); // Chemin du répertoire
$destinationPath = $destinationDirectory . "/" . $fileName; // Chemin de destination pour sauvegarder le fichier
// Vérifier si le dossier existe, sinon le créer
if (!is_dir($destinationDirectory)) {
mkdir($destinationDirectory, 0755, true); // Crée le dossier avec des permissions 0755
}
// Enregistrer les données binaires dans un fichier sur le serveur
if (file_put_contents($destinationPath, $fileData)) {
$tmpMjmlStructure[$key]['data']['file'] = $destinationPath; // Met à jour le chemin du fichier enregistré
}
}
}
$mjmlStructure = $tmpMjmlStructure;
$campaign->setmjmlStructure($mjmlStructure);
$em->persist($campaign);
$em->flush();
return $this->redirectToRoute('app_emailing_client_campaign_create_sending_options', ['id' => $campaign->getId()]);
}
// Vérifier si tous les contacts ont les champs requis
$allContactsHaveFirstName = true;
$allContactsHaveLastName = true;
$allContactsHaveCompany = true;
foreach ($campaign->getDiffusionLists() as $list) {
foreach ($list->getEmailingClientListEmails() as $email) {
if (empty($email->getFirstName())) {
$allContactsHaveFirstName = false;
}
if (empty($email->getLastName())) {
$allContactsHaveLastName = false;
}
if (empty($email->getCompanyName())) {
$allContactsHaveCompany = false;
}
// Si tous les champs sont faux, on peut arrêter la vérification
if (!$allContactsHaveFirstName && !$allContactsHaveLastName && !$allContactsHaveCompany) {
break 2;
}
}
}
return $this->render('emailing_client_campaign/create_mail.html.twig', [
'form' => $form->createView(),
'campaign' => $campaign,
'mediaViewerUrl' => $mediaViewerUrl,
'accountingFirmJson' => $accountingFirmJson,
'allContactsHaveCompany' => $allContactsHaveCompany,
'allContactsHaveFirstName' => $allContactsHaveFirstName,
'allContactsHaveLastName' => $allContactsHaveLastName
]);
}
#[Route(path: '/create/sending-options/{id}', name: 'app_emailing_client_campaign_create_sending_options')]
#[IsGranted('ROLE_USER')]
public function campagnesCreateSendingOptions(Request $request, EntityManagerInterface $em, int $id, EmailingClientCampaignRepository $emailingClientCampaignRepository, EmailingClientTokenManageService $emailingClientTokenManageService): Response
{
$campaign = $this->checkValidCampaign($id, $emailingClientCampaignRepository);
$form = $this->createForm(EmailingClientCampaignDateType::class, $campaign);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if (!$emailingClientTokenManageService->campaignEnoughTokenRemainingOnMonth($campaign)) {
$this->addFlash('warning', 'Attention : Cette campagne dépasse votre quota mensuel d\'emails.');
// Envoyer un email au client pour l'informer du dépassement
$emailingClientTokenManageService->sendQuotaExceededEmail($campaign->getAccountingFirm());
$this->redirectToRoute('app_emailing_client_campaign_create_sending_options', ['id' => $campaign->getId()]);
}
// check if campaign has enough token remaining
/* if ($emailingClientTokenManageService->campaignEnoughTokenRemainingOnMonth($campaign))
{*/
$campaign->setStatus("PROGRAMMED");
$em->persist($campaign);
$em->flush();
$dateDisplay = $campaign->getSendDate()->format('d/m/Y');
$this->addFlash('successSendEmailingClient', "Votre campagne a bien été programmée, elle sera envoyée le " . $dateDisplay . ".");
return $this->redirectToRoute('app_emailing_client_campaign_index');
/* } else {
$this->addFlash('error', 'Vous n\'avez pas assez d\'emails en réserve pour programmer cette campagne à cette date.');
}*/
}
return $this->render('emailing_client_campaign/create_recap.html.twig', [
'form' => $form->createView(),
'campaign' => $campaign,
]);
}
#[Route(path: '/create/sending-now/{id}', name: 'app_emailing_client_campaign_create_sending_now')]
#[IsGranted('ROLE_USER')]
public function campagnesCreateSendingNow(Request $request, EntityManagerInterface $em, int $id, EmailingClientCampaignRepository $emailingClientCampaignRepository, AccountingFirmRepository $accountingFirmRepository, Mailing $mailingService, EmailingClientTokenManageService $emailingClientTokenManageService): Response
{
$campaign = $this->checkValidCampaign($id, $emailingClientCampaignRepository);
if (!$campaign instanceof EmailingClientCampaign) {
return $campaign;
}
$diffusionLists = $campaign->getDiffusionLists();
$emails = [];
$processedEmails = []; // Pour éviter les doublons
foreach ($diffusionLists as $list) {
foreach ($list->getEmailingClientListEmails() as $email) {
$emailAddress = $email->getEmail();
if (!in_array($emailAddress, $processedEmails)) {
$emails[] = $emailAddress;
$processedEmails[] = $emailAddress;
}
}
}
if (!$emailingClientTokenManageService->campaignEnoughTokenRemainingOnMonth($campaign)) {
$this->addFlash('warning', 'Attention : Cette campagne dépasse votre quota mensuel d\'emails.');
// Envoyer un email au client pour l'informer du dépassement
//get token used
$emailingClientTokenManageService->sendQuotaExceededEmail($campaign->getAccountingFirm());
return $this->redirectToRoute('app_emailing_client_campaign_create_sending_options', ['id' => $campaign->getId()]);
}
$dateSend = new DateTime();
$dateSend->setTimezone(new DateTimeZone('Europe/Paris'));
$dateSend->setTime($dateSend->format('H') + 1, 0, 0);
$campaign->setSendDate($dateSend);
$campaign->setStatus("PROGRAMMED");
$em->persist($campaign);
$cabinet = $campaign->getAccountingFirm();
$task = new MailingTask();
$task->setAccountingFirm($cabinet);
$task->setType("EMAILING_CLIENT");
$task->setStatus("PENDING");
$task->setEmailingClientCampaign($campaign);
$task->setWebinaireId(null);
if ($campaign->getSurvey() != null) {
$survey = $campaign->getSurvey();
$task->setSurvey($survey);
}
$em->persist($task);
$em->flush();
$mailingService->createMailingLog($task, "Initialisation de l'envoi.", "INIT");
$this->messageBus->dispatch(new MailingProcessMessage(intval($task->getId())));
$this->addFlash('successSendEmailingClient', "Votre campagne a bien été ajoutée à la file d'attente, elle sera envoyée dès que possible.");
return $this->redirectToRoute('app_emailing_client_campaign_index');
}
#[Route(path: '/campagnes/continue/{id}', name: 'app_emailing_client_campaign_continue')]
#[IsGranted('ROLE_USER')]
public function campagnesContinue(Request $request, EntityManagerInterface $em, int $id, EmailingClientCampaignRepository $emailingClientCampaignRepository): Response
{
$campaign = $this->checkValidCampaign($id, $emailingClientCampaignRepository);
if (!$campaign instanceof EmailingClientCampaign) {
return $campaign;
}
if ($campaign->getSendDate() == null && $campaign->getMjmlStructure() != null) {
return $this->redirectToRoute("app_emailing_client_campaign_create_sending_options", ['id' => $campaign->getId()]);
} elseif ($campaign->getMjmlStructure() == null && count($campaign->getDiffusionLists()) > 0) {
return $this->redirectToRoute("app_emailing_client_campaign_create_mail_editor", ['id' => $campaign->getId()]);
} elseif (count($campaign->getDiffusionLists()) === 0 && $campaign->getName() != null) {
return $this->redirectToRoute("app_emailing_client_campaign_create_select_diffusion", ['id' => $campaign->getId()]);
} elseif ($campaign->getName() != null && count($campaign->getDiffusionLists()) > 0 && $campaign->getMjmlStructure() != null && $campaign->getSendDate() != null) {
return $this->redirectToRoute("app_emailing_client_campaign_create_sending_options", ['id' => $campaign->getId()]);
}
return $this->redirectToRoute("app_emailing_client_campaign_create_name", ['id' => $campaign->getId()]);
}
#[Route(path: '/campagnes/preview/{id}', name: 'app_emailing_client_campaign_preview')]
#[IsGranted('ROLE_USER')]
public function campagnesPreview(int $id, MailingCommunicationRepository $mailingCommunicationRepository, MjmlExtension $mjmlRenderer, EmailingClientCampaignRepository $emailingClientCampaignRepository): Response
{
$campaign = $this->checkValidCampaign($id, $emailingClientCampaignRepository, false);
$mjmlStructure = $campaign->getmjmlStructure();
$mediaViewerUrl = "https://actucontent.loc:8000/";
if ($this->getParameter('kernel.environment') === 'prod') {
$mediaViewerUrl = "https://actucontent.lagence.expert/";
}
$render_data = [
'mjmlCode' => $mjmlStructure,
'cabinet' => $campaign->getAccountingFirm(),
'serverUrl' => $mediaViewerUrl
];
return new Response($mjmlRenderer->render($this->render('mjml/emailing_client/emailing_client.mjml', $render_data)));
}
#[Route(path: '/campagnes/duplicate/{id}', name: 'app_emailing_client_campaign_duplicate')]
#[IsGranted('ROLE_USER')]
public function campagnesDuplicate(Request $request, EntityManagerInterface $em, int $id, EmailingClientCampaignRepository $emailingClientCampaignRepository): Response
{
$campaign = $emailingClientCampaignRepository->findOneBy(['id' => $id]);
if ($campaign == null) {
$this->addFlash('error', 'Aucune campagne n\'a été trouvé avec cet identifiant.');
return $this->redirectToRoute("app_emailing_client_campaign_index");
}
if ($campaign->getAccountingFirm() != $this->getUser()->getAccountingFirm()) {
$this->addFlash('error', 'Vous n\'avez pas les droits pour accéder à cette campagne.');
return $this->redirectToRoute("app_emailing_client_campaign_index");
}
$newCampaign = new EmailingClientCampaign();
$newCampaign->setName($campaign->getName())
->setObject($campaign->getObject())
->setMjmlStructure($campaign->getMjmlStructure())
->setStatus("ON_CREATE");
// Copie des listes de diffusion
foreach ($campaign->getDiffusionLists() as $list) {
$newCampaign->addDiffusionList($list);
}
if ($campaign->getSatisfactionClientSurvey() != null) {
$newCampaign->setSatisfactionClientSurvey($campaign->getSatisfactionClientSurvey());
}
if ($campaign->getCollaboratorsData() != null) {
$newCampaign->setCollaboratorsData($campaign->getCollaboratorsData());
}
$em->persist($newCampaign);
$em->flush();
return $this->redirectToRoute('app_emailing_client_campaign_create_name', ['id' => $newCampaign->getId()]);
}
#[Route(path: '/campagnes/{id}/delete', name: 'app_emailing_client_campaign_delete', methods: ['POST'])]
#[IsGranted('ROLE_USER')]
public function delete(Request $request, EmailingClientCampaign $campaign, EntityManagerInterface $em): Response
{
if ($this->isCsrfTokenValid('delete' . $campaign->getId(), $request->request->get('_token'))) {
$tasks = $campaign->getMailingTask();
foreach ($tasks as $task) {
$task->setEmailingClientCampaign(null);
$em->persist($task);
}
$em->remove($campaign);
$em->flush();
}
$this->addFlash('success', 'La campagne a bien été supprimée.');
return $this->redirectToRoute('app_emailing_client_campaign_index');
}
#[Route(path: '/campagnes/{id}/stats', name: 'app_emailing_client_campaign_stats')]
#[IsGranted('ROLE_USER')]
public function stats(Request $request, EmailingClientCampaign $campaign, CacheInterface $cache): Response
{
// Vérifier que la campagne appartient bien au cabinet connecté
if ($campaign->getAccountingFirm() !== $this->getUser()->getAccountingFirm()) {
throw $this->createAccessDeniedException('Vous n\'avez pas accès à cette campagne.');
}
// Vérifier que la campagne est bien envoyée
if ($campaign->getStatus() !== 'SEND') {
throw $this->createAccessDeniedException('Cette campagne n\'a pas encore été envoyée.');
}
$force = $request->query->getBoolean('force', false);
// Throttle: éviter de forcer trop souvent (par user + campagne)
if ($force) {
$cooldownSeconds = 3600;
$userId = $this->getUser()->getId();
$campaignId = $campaign->getId();
$throttleKey = sprintf('emailing_client_force_refresh_%d_%d', $campaignId, $userId);
$lastForcedAt = $cache->get($throttleKey, function (ItemInterface $item) {
// si pas encore forcé, on retourne 0 (epoch)
$item->expiresAfter(3600);
return 0;
});
$now = time();
if (is_int($lastForcedAt) && ($now - $lastForcedAt) < $cooldownSeconds) {
$remaining = $cooldownSeconds - ($now - $lastForcedAt);
if ($remaining < 60) {
$timeLabel = $remaining . ' seconde' . ($remaining > 1 ? 's' : '');
} elseif ($remaining < 3600) {
$minutes = (int) ceil($remaining / 60);
$timeLabel = $minutes . ' minute' . ($minutes > 1 ? 's' : '');
} else {
$hours = (int) floor($remaining / 3600);
$minutes = (int) ceil(($remaining % 3600) / 60);
if ($minutes > 0) {
$timeLabel = $hours . ' heure' . ($hours > 1 ? 's' : '') . ' et ' . $minutes . ' minute' . ($minutes > 1 ? 's' : '');
} else {
$timeLabel = $hours . ' heure' . ($hours > 1 ? 's' : '');
}
}
$this->addFlash(
'error',
sprintf('Rafraîchissement déjà effectué. Vous pourrez réessayer dans %s.', $timeLabel)
);
$force = false;
return $this->redirectToRoute('app_emailing_client_campaign_stats', ['id' => $campaign->getId()]);
} else {
$cache->delete($throttleKey);
$cache->get($throttleKey, function (ItemInterface $item) use ($now, $cooldownSeconds) {
$item->expiresAfter($cooldownSeconds + 10);
return $now;
});
}
}
// Récupérer les statistiques depuis Mailjet
try {
$stats = $this->mailjetService->getCampaignStatsEmailingClient($campaign, $force);
// Est-ce que ça a changé ?
$changed = false;
// floats : on compare arrondis (tes stats sont déjà round à 2)
if (round((float)($campaign->getOpenRate() ?? 0), 2) !== round((float)($stats['openRate'] ?? 0), 2)) $changed = true;
if (round((float)($campaign->getClickRate() ?? 0), 2) !== round((float)($stats['clickRate'] ?? 0), 2)) $changed = true;
if (round((float)($campaign->getUnsubscribeRate() ?? 0), 2) !== round((float)($stats['unsubscribeRate'] ?? 0), 2)) $changed = true;
if (round((float)($campaign->getBounceRate() ?? 0), 2) !== round((float)($stats['bounceRate'] ?? 0), 2)) $changed = true;
// int
if ((int)($campaign->getDeliveredCount() ?? 0) !== (int)($stats['deliveredCount'] ?? 0)) $changed = true;
// Si changement OU stats jamais enregistrées -> on persiste
if ($changed || $campaign->getStatsUpdatedAt() === null) {
$campaign->setOpenRate((float)($stats['openRate'] ?? 0));
$campaign->setClickRate((float)($stats['clickRate'] ?? 0));
$campaign->setUnsubscribeRate((float)($stats['unsubscribeRate'] ?? 0));
$campaign->setBounceRate((float)($stats['bounceRate'] ?? 0));
$campaign->setDeliveredCount((int)($stats['deliveredCount'] ?? 0));
$campaign->setStatsUpdatedAt(new \DateTimeImmutable());
$this->em->flush();
}
$this->em->flush();
if ($force) {
$this->addFlash('success', 'Statistiques rafraîchies depuis Mailjet.');
}
} catch (Exception $e) {
// En cas d'erreur, utiliser les valeurs stockées
$stats = [
'openRate' => $campaign->getOpenRate() ?? 0,
'clickRate' => $campaign->getClickRate() ?? 0,
'unsubscribeRate' => $campaign->getUnsubscribeRate() ?? 0,
'bounceRate' => $campaign->getBounceRate() ?? 0,
'deliveredCount' => $campaign->getDeliveredCount() ?? 0,
];
$this->addFlash('warning', 'Impossible de récupérer les stats Mailjet, affichage des stats enregistrées.');
}
return $this->render('emailing_client_campaign/stats.html.twig', [
'campaign' => $campaign,
'openRate' => number_format($stats['openRate'] ?? 0, 1) . '%',
'clickRate' => number_format($stats['clickRate'] ?? 0, 1) . '%',
'unsubscribeRate' => number_format($stats['unsubscribeRate'] ?? 0, 1) . '%',
'bounceRate' => number_format($stats['bounceRate'] ?? 0, 1) . '%',
'deliveredCount' => (int)($stats['deliveredCount'] ?? 0),
'openedCount' => (int)($stats['openedCount'] ?? 0),
'clickedCount' => (int)($stats['clickedCount'] ?? 0),
'unsubscribedCount' => (int)($stats['unsubscribedCount'] ?? 0),
'hardBouncedCount' => (int)($stats['hardBouncedCount'] ?? 0),
'softBouncedCount' => (int)($stats['softBouncedCount'] ?? 0),
]);
}
}