src/Controller/AdminBundle/CRONController.php line 178

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by PhpStorm.
  4.  * User: grego
  5.  * Date: 07/11/2022
  6.  * Time: 12:25
  7.  */
  8. namespace App\Controller\AdminBundle;
  9. use App\Entity\Badge;
  10. use App\Entity\BatchFeedbackEmail;
  11. use App\Entity\Recipient;
  12. use App\Entity\RequestLog;
  13. use App\Entity\Skill;
  14. use App\Entity\User;
  15. use App\Manager\CossManager;
  16. use App\Manager\GamificationManager;
  17. use App\Manager\MailerManager;
  18. use App\Manager\MistralManager;
  19. use App\Manager\PeopleDataLabsManager;
  20. use App\Manager\UserManager;
  21. use App\Repository\BatchFeedbackEmailRepository;
  22. use App\Repository\RecipientRepository;
  23. use DateTime;
  24. use Doctrine\ORM\EntityManagerInterface;
  25. use Exception;
  26. use Sonata\MediaBundle\Provider\Pool;
  27. use Symfony\Bridge\Twig\Mime\TemplatedEmail;
  28. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  29. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  30. use Symfony\Component\HttpClient\HttpClient;
  31. use Symfony\Component\HttpFoundation\JsonResponse;
  32. use Symfony\Component\HttpFoundation\Request;
  33. use Symfony\Component\HttpFoundation\Response;
  34. use Symfony\Component\HttpFoundation\StreamedResponse;
  35. use Symfony\Component\Mailer\MailerInterface;
  36. use Symfony\Component\Mime\Address;
  37. use Symfony\Component\Routing\Annotation\Route;
  38. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  39. use Symfony\Contracts\HttpClient\HttpClientInterface;
  40. use Symfony\Contracts\Translation\TranslatorInterface;
  41. class CRONController extends AbstractController
  42. {
  43.     private $CRON_TOKEN "ffd6600b2594eafae3b2f0528a4cb51a";
  44.     /**
  45.      * @Route("/cron/sendFeedbackRecap/{type}/{token}")
  46.      */
  47.     public function sendFeedbackRecapAction(Request $request$type$tokenEntityManagerInterface $entityManagerMailerManager $mailerManager)
  48.     {
  49.         if($token == $this->CRON_TOKEN) {
  50.             set_time_limit(0);
  51.             ini_set('max_execution_time'3600); //3600 seconds = 1 heur
  52.             $twoDays = new DateTime();
  53.             $twoDays->modify('-2 days');
  54.             $userRepository $entityManager->getRepository(User::class);
  55.             $users $userRepository->createQueryBuilder('u')
  56.                 ->select('distinct u')
  57.                 ->leftJoin('u.recipients',  'r')
  58.                 ->where("u.enabled = false or u.lastLogin is NULL or u.platform = 'browser'")
  59.                 ->andWhere('r.lastRecallDate <= :twoDays or r.lastRecallDate is NULL');
  60.             $users->setParameter('twoDays'$twoDays);
  61.             $users->orderBy('u.id');
  62.             $users $users->getQuery()->getResult();
  63.             foreach ($users as $user) {
  64.                 if($user->getLocale()){
  65.                     $request->setLocale($user->getLocale());
  66.                 } else {
  67.                     $request->setLocale('fr');
  68.                 }
  69.                 \Sentry\withScope(function (\Sentry\State\Scope $scope) use ($user$mailerManager$type) {
  70.                     $scope->setUser([
  71.                         'id' => $user->getId(),
  72.                         'email' => $user->getEmail(),
  73.                     ]);
  74.                     $scope->setTag('type''email');
  75.                     try {
  76.                         $mailerManager->sendFeedbackRecap($user$type);
  77.                     } catch (\Throwable $e) {
  78.                         \Sentry\captureException($e);
  79.                     }
  80.                 });
  81.             }
  82.             return New Response(""200);
  83.         } else {
  84.             return New Response("access denied"401);
  85.         }
  86.     }
  87.     /**
  88.      * @Route("/cron/debug/quality/{token}")
  89.      */
  90.     public function debugQuality($tokenEntityManagerInterface $entityManagerHttpClientInterface $httpClientMailerManager $mailerManagerUserManager $userManagerCossManager $cossManagerGamificationManager $gamificationManagerPeopleDataLabsManager $peopleDataLabsManagerMistralManager $mistralManagerTranslatorInterface $translatorParameterBagInterface $parameterBagUrlGeneratorInterface $router){
  91.         set_time_limit(0);
  92.         ini_set('max_execution_time'3600); //3600 seconds = 1 heure
  93.         ini_set('memory_limit''-1');
  94.         $user $entityManager->getRepository(User::class)->find(12);
  95.         //$userStats = $mistralManager->getUserStatisticsForMistral($user);
  96.         //$response = $mistralManager->analyzeUserStats($userStats);
  97.         //$newAiSynthesis = $mistralManager->saveUserAISynthesis($user, $response);
  98.         return New Response("OK"200);
  99.     }
  100.     /**
  101.      * @Route("/cron/export/connections/{token}/{start}/{end}")
  102.      */
  103.     public function exportConnections($token$start$endEntityManagerInterface $entityManager)
  104.     {
  105.         if($token == $this->CRON_TOKEN) {
  106.             $startDate DateTime::createFromFormat('d-m-Y'$start);
  107.             $endDate DateTime::createFromFormat('d-m-Y'$end);
  108.             if($startDate && $endDate){
  109.                 $response = new StreamedResponse();
  110.                 $response->setCallback(function() use ($entityManager$startDate$endDate){
  111.                     $handle fopen('php://output''w+');
  112.                     fwrite($handleimplode(';',
  113.                             [
  114.                                 '"Id utilisateur"',
  115.                                 '"Prénom utilisateur"',
  116.                                 '"Nom utilisateur"',
  117.                                 '"Email utilisateur"',
  118.                                 '"Société utilisateur"',
  119.                                 '"Fonction utilisateur"',
  120.                                 '"Niveau hiérarchique utilisateur"',
  121.                                 '"Date connexion"',
  122.                                 '"Heure connexion"',
  123.                                 '"Date création"',
  124.                                 '"Date suppression"'
  125.                             ]) . PHP_EOL);
  126.                     $connexions $entityManager->getRepository(RequestLog::class)->getLogsBetweenDates($startDate$endDate);
  127.                     foreach ($connexions as $connexion){
  128.                         fwrite($handleimplode(';',
  129.                                 [
  130.                                     $connexion->getUser()->getId(),
  131.                                     $connexion->getUser()->getFirstname(),
  132.                                     $connexion->getUser()->getLastname(),
  133.                                     $connexion->getUser()->getEmail(),
  134.                                     $connexion->getUser()->getCompany(),
  135.                                     $connexion->getUser()->getJobTitle(),
  136.                                     $connexion->getUser()->getHierarchicalLevel(),
  137.                                     $connexion->getCreatedAt()->format("d/m/Y"),
  138.                                     $connexion->getCreatedAt()->format("H:i:s"),
  139.                                     $connexion->getUser()->getCreatedAt() ? $connexion->getUser()->getCreatedAt()->format('d/m/Y'): "",
  140.                                     $connexion->getUser()->getDeletedAt() ? $connexion->getUser()->getDeletedAt()->format('d/m/Y') : ""
  141.                                 ]) . PHP_EOL);
  142.                     }
  143.                     fclose($handle);
  144.                 });
  145.                 $date = new \DateTime();
  146.                 $now $date->format('d_m_Y_H_i_s');
  147.                 $filename "coss_b2c_connections_export".$now.".csv";
  148.                 $response->setStatusCode(200);
  149.                 $response->headers->set('Content-Type''text/csv; charset=utf-8');
  150.                 $response->headers->set('Content-Disposition','attachment; filename='.$filename.'');
  151.                 return $response;
  152.             } else {
  153.                 return New Response("wrong dates param"401);
  154.             }
  155.         } else {
  156.             return New Response("access denied"401);
  157.         }
  158.     }
  159.     /**
  160.      * @Route("/cron/send/batch/feedback/email/{token}")
  161.      */
  162.     public function sendBatchFeedbackEmail($tokenRequest $requestEntityManagerInterface $entityManagerMailerManager $mailerManagerPool $poolTranslatorInterface $translator)
  163.     {
  164.         if ($token == $this->CRON_TOKEN) {
  165.             $batchFeedbacks $entityManager->getRepository(BatchFeedbackEmail::class)->getPendingBatchFeedbacksPastFiveMinuts();
  166.             $groupedFeedbacks = array();
  167.             foreach ($batchFeedbacks as $batchFeedback) {
  168.                 $feedback $batchFeedback->getFeedback();
  169.                 $request->setLocale($feedback->getSender()->getLocale());
  170.                 $senderRecipient $feedback->getSender()->getId() . '-' $feedback->getRecipient()->getId();
  171.                 // Get sender's photo url and name
  172.                 $featured $feedback->getSender()->getImage();
  173.                 if ($featured) {
  174.                     $provider $pool->getProvider($featured->getProviderName());
  175.                     $format $provider->getFormatName($featured"reference");
  176.                     $senderImage $request->getSchemeAndHttpHost()."/uploads/media/".$provider->generatePrivateUrl($featured$format);
  177.                 } else {
  178.                     $senderImage null;
  179.                 }
  180.                 $senderName $feedback->getSender()->getFirstname()." ".$feedback->getSender()->getLastname();
  181.                 if($feedback->getSender()->getCompany() != null){
  182.                     $senderCompany $feedback->getSender()->getCompany();
  183.                 } else {
  184.                     $senderCompany $translator->trans(
  185.                         "Pas de société", array(), 'mail'
  186.                     );
  187.                 }
  188.                 if (isset($groupedFeedbacks[$senderRecipient])) {
  189.                     $groupedFeedbacks[$senderRecipient]["feedbacks"][] = $feedback;
  190.                     $groupedFeedbacks[$senderRecipient]["batchFeedbacks"][] = $batchFeedback;
  191.                 } else {
  192.                     $groupedFeedbacks[$senderRecipient]["name"] = $senderName;
  193.                     $groupedFeedbacks[$senderRecipient]["company"] = $senderCompany;
  194.                     $groupedFeedbacks[$senderRecipient]["photo"] = $senderImage;
  195.                     $groupedFeedbacks[$senderRecipient]["feedbacks"] = array($feedback);
  196.                     $groupedFeedbacks[$senderRecipient]["batchFeedbacks"] = array($batchFeedback);
  197.                 }
  198.             }
  199.             $mailerManager->sendBatchFeedbackEmail($groupedFeedbacks);
  200.             foreach ($groupedFeedbacks as $groupedFeedback){
  201.                 foreach ($groupedFeedback['batchFeedbacks'] as $batchFeedback){
  202.                     $batchFeedback->setStatus(BatchFeedbackEmailRepository::STATUS_TREATED);
  203.                     $entityManager->persist($batchFeedback);
  204.                 }
  205.             }
  206.             $entityManager->flush();
  207.             return New Response(""200);
  208.         } else {
  209.             return New Response("access denied"401);
  210.         }
  211.     }
  212.     /**
  213.      * @Route("/cron/disable/licences/badge/{token}")
  214.      */
  215.     public function checkLicencesBadgesAccess($tokenEntityManagerInterface $entityManager)
  216.     {
  217.         if($token == $this->CRON_TOKEN) {
  218.             $users $entityManager->getRepository(User::class)->findAll();
  219.             foreach($users as $user){
  220.                 $badgesAccessExpirationDate $user->getLicenceEndDate();
  221.                 if($badgesAccessExpirationDate){
  222.                     $badgesAccessExpirationDate->modify('+12 months');
  223.                     $today = new DateTime();
  224.                     if ($badgesAccessExpirationDate >= $today) {
  225.                         $user->setBadgesAccess(false);
  226.                         $entityManager->persist($user);
  227.                     }
  228.                 } else {
  229.                     $user->setBadgesAccess(false);
  230.                     $entityManager->persist($user);
  231.                 }
  232.             }
  233.             $entityManager->flush();
  234.             return New Response(""200);
  235.         } else {
  236.             return New Response("access denied"401);
  237.         }
  238.     }
  239.     /**
  240.      * @Route("/cron/analyse/recipients/{token}")
  241.      */
  242.     public function analyseRecipients($tokenEntityManagerInterface $entityManagerPeopleDataLabsManager $peopleDataLabsManagerMistralManager $mistralManager){
  243.         if($token == $this->CRON_TOKEN) {
  244.             set_time_limit(0);
  245.             ini_set('max_execution_time'600); //600 seconds = 10min
  246.             $pendingRecipients $entityManager->getRepository(Recipient::class)->findBy(array('status' => RecipientRepository::STATUS_PENDING'analysisStatus' => RecipientRepository::ANALYSIS_PENDING));
  247.             foreach ($pendingRecipients as $recipient) {
  248.                 try {
  249.                     $user $recipient->getAppUser();
  250.                     if(!$user->getValidator()){ // Analysis only for 5Feedback validation
  251.                         $userData $peopleDataLabsManager->getUserCareer($user);
  252.                         if ($userData['found'] == true) {
  253.                             $senderData = array(
  254.                                 "firstname" => $user->getFirstname(),
  255.                                 "lastname" => $user->getLastname(),
  256.                                 "email" => $user->getEmail(),
  257.                                 "career" => $userData['data']
  258.                             );
  259.                         } else {
  260.                             $senderData = array(
  261.                                 "firstname" => $user->getFirstname(),
  262.                                 "lastname" => $user->getLastname(),
  263.                                 "email" => $user->getEmail(),
  264.                                 "career" => array(
  265.                                     "company" => $user->getCompany(),
  266.                                     "job_title" => $user->getJobTitle(),
  267.                                 )
  268.                             );
  269.                         }
  270.                         $status "Réseau professionnel";
  271.                         switch ($recipient->getStatus()) {
  272.                             case RecipientRepository::SUPERIOR_RECIPIENT:
  273.                                 $status "Manager";
  274.                                 break;
  275.                             case RecipientRepository::PAIR_RECIPIENT:
  276.                                 $status "Collègue";
  277.                                 break;
  278.                             case RecipientRepository::GUEST_RECIPIENT:
  279.                                 $status "Réseau professionnel";
  280.                                 break;
  281.                         }
  282.                         $recipientData = array(
  283.                             "status" => $status,
  284.                             "firstname" => $recipient->getUser()->getFirstname(),
  285.                             "lastname" => $recipient->getUser()->getLastname(),
  286.                             "email" => $recipient->getUser()->getEmail(),
  287.                             "company" => $recipient->getCompany(),
  288.                             "job_title" => $recipient->getTitle()
  289.                         );
  290.                         $response $mistralManager->validateRecipient($senderData$recipientData);
  291.                         if ($response['status'] == true) {
  292.                             if (isset($response['data'])) {
  293.                                 $recipient->setAnalysisData($response['data']);
  294.                                 $recipient->setAnalysisStatus(RecipientRepository::ANALYSIS_DONE);
  295.                                 $entityManager->persist($recipient);
  296.                                 $entityManager->flush();
  297.                             }
  298.                         } else {
  299.                             $iterations $recipient->getAnalysisIteration();
  300.                             $iterations++;
  301.                             if ($iterations >= 5) {
  302.                                 $recipient->setAnalysisStatus(RecipientRepository::ANALYSIS_EXPIRED);
  303.                             } else {
  304.                                 $recipient->setAnalysisIteration($iterations);
  305.                             }
  306.                             $entityManager->persist($recipient);
  307.                             $entityManager->flush();
  308.                             continue;
  309.                         }
  310.                     }
  311.                 } catch (Exception $exception) {
  312.                     $iterations $recipient->getAnalysisIteration();
  313.                     $iterations++;
  314.                     if ($iterations >= 5) {
  315.                         $recipient->setAnalysisStatus(RecipientRepository::ANALYSIS_EXPIRED);
  316.                     } else {
  317.                         $recipient->setAnalysisIteration($iterations);
  318.                     }
  319.                     $entityManager->persist($recipient);
  320.                     $entityManager->flush();
  321.                     continue;
  322.                 }
  323.             }
  324.             return New Response(""200);
  325.         } else {
  326.             return New Response("access denied"401);
  327.         }
  328.     }
  329.     /**
  330.      * @Route("/cron/test-all-emails/{token}", name="cron_test_all_emails")
  331.      */
  332.     public function testAllEmailsAction(
  333.         $token,
  334.         Request $request,
  335.         MailerInterface $mailer,
  336.         ParameterBagInterface $parameterBag,
  337.         TranslatorInterface $translator,
  338.         UrlGeneratorInterface $router
  339.     ) {
  340.         if ($token !== $this->CRON_TOKEN) {
  341.             return new Response("Access denied"401);
  342.         }
  343.         set_time_limit(0);
  344.         ini_set('max_execution_time'600);
  345.         $targetEmail $request->query->get('to''bertrandpon@yopmail.com');
  346.         $locale $request->query->get('locale''fr');
  347.         $request->setLocale($locale);
  348.         $translator->setLocale($locale);
  349.         $projectDir $parameterBag->get('kernel.project_dir');
  350.         $dummyImagePath $projectDir '/public/build/images/avatar-default.png';
  351.         if (!file_exists($dummyImagePath)) {
  352.             $dummyImagePath null;
  353.         }
  354.         $defaultAvatarUrl = ($request->getSchemeAndHttpHost() ?: 'https://b2c-back.dev.globalcoss.com') . '/build/images/avatar-default.png';
  355.         // 1. Mock User Object
  356.         $dummyUser = new class($targetEmail) {
  357.             private $email;
  358.             public function __construct($email) { $this->email $email; }
  359.             public function getId() { return 1; }
  360.             public function getFirstname() { return 'Bertrand'; }
  361.             public function getLastname() { return 'Pon'; }
  362.             public function getEmail() { return $this->email; }
  363.             public function getLocale() { return 'fr'; }
  364.             public function getCompany() { return 'COSS Corp'; }
  365.             public function getJobTitle() { return 'Directeur de Projet'; }
  366.             public function getRoles() { return ['ROLE_COSS_USER']; }
  367.             public function getRecipientProofGuestToken() { return 'dummy-proof-token-123456'; }
  368.             public function getStudentProofGuestToken() { return 'dummy-student-token-654321'; }
  369.             public function getImage() { return null; }
  370.             public function __toString() { return 'Bertrand Pon'; }
  371.         };
  372.         // 2. Mock Skills
  373.         $dummySkill1 = new class {
  374.             public function getId() { return 1; }
  375.             public function getTitle() { return 'Écoute active'; }
  376.             public function getDescription() { return 'Capacité à écouter attentivement et reformuler les besoins des interlocuteurs.'; }
  377.             public function __toString() { return 'Écoute active'; }
  378.         };
  379.         $dummySkill2 = new class {
  380.             public function getId() { return 2; }
  381.             public function getTitle() { return 'Leadership & Coopération'; }
  382.             public function getDescription() { return 'Aptitude à fédérer et inspirer une équipe autour d\'une vision partagée.'; }
  383.             public function __toString() { return 'Leadership & Coopération'; }
  384.         };
  385.         $dummySkill3 = new class {
  386.             public function getId() { return 3; }
  387.             public function getTitle() { return 'Prise de décision'; }
  388.             public function getDescription() { return 'Capacité à trancher avec discernement dans des situations d\'incertitude.'; }
  389.             public function __toString() { return 'Prise de décision'; }
  390.         };
  391.         // 3. Mock Feedbacks
  392.         $createDummyFeedback = function($id$skill) use ($dummyUser) {
  393.             return new class($id$skill$dummyUser) {
  394.                 private $id;
  395.                 private $skill;
  396.                 private $user;
  397.                 public function __construct($id$skill$user) {
  398.                     $this->id $id;
  399.                     $this->skill $skill;
  400.                     $this->user $user;
  401.                 }
  402.                 public function getId() { return $this->id; }
  403.                 public function getSkill() { return $this->skill; }
  404.                 public function getSender() { return $this->user; }
  405.                 public function getRecipient() { return $this->user; }
  406.                 public function getGuestToken() { return 'dummy-token-' $this->id; }
  407.                 public function getGuestUrl() { return 'https://coss-b2c.web.app/guest-answer/dummy-token-' $this->id '/' $this->id; }
  408.                 public function getStatus() { return 1; }
  409.             };
  410.         };
  411.         $dummyFeedback1 $createDummyFeedback(101$dummySkill1);
  412.         $dummyFeedback2 $createDummyFeedback(102$dummySkill2);
  413.         $dummyFeedback3 $createDummyFeedback(103$dummySkill3);
  414.         $results = [];
  415.         $definitions $this->getTestEmailDefinitions(
  416.             $dummyUser,
  417.             $dummyFeedback1,
  418.             $dummyFeedback2,
  419.             $dummyFeedback3,
  420.             $defaultAvatarUrl,
  421.             $dummyImagePath,
  422.             $targetEmail
  423.         );
  424.         foreach ($definitions as $defKey => $def) {
  425.             try {
  426.                 $email = (new TemplatedEmail())
  427.                     ->from(new Address(MailerManager::SENDER_EMAILMailerManager::SENDER_LABEL))
  428.                     ->to($targetEmail)
  429.                     ->subject('[TEST] ' $def['subject'])
  430.                     ->htmlTemplate($def['template'])
  431.                     ->context($def['params']);
  432.                 foreach ($def['attachments'] as $att) {
  433.                     if (!empty($att['path']) && file_exists($att['path'])) {
  434.                         $email->attachFromPath($att['path'], $att['name'] ?? null);
  435.                     }
  436.                 }
  437.                 $mailer->send($email);
  438.                 $results[] = [
  439.                     'key' => $defKey,
  440.                     'name' => $def['name'],
  441.                     'template' => $def['template'],
  442.                     'subject' => $def['subject'],
  443.                     'status' => 'SUCCESS',
  444.                 ];
  445.             } catch (\Throwable $e) {
  446.                 $results[] = [
  447.                     'key' => $defKey,
  448.                     'name' => $def['name'],
  449.                     'template' => $def['template'],
  450.                     'subject' => $def['subject'],
  451.                     'status' => 'ERROR',
  452.                     'error' => $e->getMessage(),
  453.                 ];
  454.             }
  455.         }
  456.         $totalSent count($results);
  457.         $successCount count(array_filter($results, function($r) { return $r['status'] === 'SUCCESS'; }));
  458.         $errorCount count(array_filter($results, function($r) { return $r['status'] === 'ERROR'; }));
  459.         return new JsonResponse([
  460.             'target_email' => $targetEmail,
  461.             'total' => $totalSent,
  462.             'success' => $successCount,
  463.             'errors' => $errorCount,
  464.             'details' => $results
  465.         ]);
  466.     }
  467.     /**
  468.      * @Route("/cron/test-email/{templateKey}/{token}", name="cron_test_single_email", defaults={"templateKey"="badges_automatic_email"})
  469.      */
  470.     public function testSingleEmailAction(
  471.         $templateKey,
  472.         $token,
  473.         Request $request,
  474.         MailerInterface $mailer,
  475.         TranslatorInterface $translator,
  476.         ParameterBagInterface $parameterBag,
  477.         \Twig\Environment $twig
  478.     ) {
  479.         if ($token !== $this->CRON_TOKEN) {
  480.             return new Response("Access denied"401);
  481.         }
  482.         $targetEmail $request->query->get('to''bertrandpon@yopmail.com');
  483.         $locale $request->query->get('locale''fr');
  484.         $preview $request->query->getBoolean('preview'false);
  485.         $request->setLocale($locale);
  486.         $translator->setLocale($locale);
  487.         $projectDir $parameterBag->get('kernel.project_dir');
  488.         $dummyImagePath $projectDir '/public/build/images/avatar-default.png';
  489.         if (!file_exists($dummyImagePath)) {
  490.             $dummyImagePath null;
  491.         }
  492.         $defaultAvatarUrl = ($request->getSchemeAndHttpHost() ?: 'https://b2c-back.dev.globalcoss.com') . '/build/images/avatar-default.png';
  493.         $dummyUser = new class($targetEmail) {
  494.             private $email;
  495.             public function __construct($email) { $this->email $email; }
  496.             public function getId() { return 1; }
  497.             public function getFirstname() { return 'Bertrand'; }
  498.             public function getLastname() { return 'Pon'; }
  499.             public function getEmail() { return $this->email; }
  500.             public function getLocale() { return 'fr'; }
  501.             public function getCompany() { return 'COSS Corp'; }
  502.             public function getJobTitle() { return 'Directeur de Projet'; }
  503.             public function getRoles() { return ['ROLE_SUPER_ADMIN']; }
  504.             public function getRecipientProofGuestToken() { return 'dummy-proof-token-123456'; }
  505.             public function getStudentProofGuestToken() { return 'dummy-student-token-654321'; }
  506.             public function getImage() { return null; }
  507.             public function __toString() { return 'Bertrand Pon'; }
  508.         };
  509.         $dummySkill1 = new class {
  510.             public function getId() { return 1; }
  511.             public function getTitle() { return 'Écoute active'; }
  512.             public function getDescription() { return 'Capacité à écouter attentivement et reformuler les besoins des interlocuteurs.'; }
  513.             public function __toString() { return 'Écoute active'; }
  514.         };
  515.         $dummySkill2 = new class {
  516.             public function getId() { return 2; }
  517.             public function getTitle() { return 'Leadership & Coopération'; }
  518.             public function getDescription() { return 'Aptitude à fédérer et inspirer une équipe autour d\'une vision partagée.'; }
  519.             public function __toString() { return 'Leadership & Coopération'; }
  520.         };
  521.         $dummySkill3 = new class {
  522.             public function getId() { return 3; }
  523.             public function getTitle() { return 'Prise de décision'; }
  524.             public function getDescription() { return 'Capacité à trancher avec discernement dans des situations d\'incertitude.'; }
  525.             public function __toString() { return 'Prise de décision'; }
  526.         };
  527.         $createDummyFeedback = function($id$skill) use ($dummyUser) {
  528.             return new class($id$skill$dummyUser) {
  529.                 private $id;
  530.                 private $skill;
  531.                 private $user;
  532.                 public function __construct($id$skill$user) {
  533.                     $this->id $id;
  534.                     $this->skill $skill;
  535.                     $this->user $user;
  536.                 }
  537.                 public function getId() { return $this->id; }
  538.                 public function getSkill() { return $this->skill; }
  539.                 public function getSender() { return $this->user; }
  540.                 public function getRecipient() { return $this->user; }
  541.                 public function getGuestToken() { return 'dummy-token-' $this->id; }
  542.                 public function getGuestUrl() { return 'https://coss-b2c.web.app/guest-answer/dummy-token-' $this->id '/' $this->id; }
  543.                 public function getStatus() { return 1; }
  544.             };
  545.         };
  546.         $dummyFeedback1 $createDummyFeedback(101$dummySkill1);
  547.         $dummyFeedback2 $createDummyFeedback(102$dummySkill2);
  548.         $dummyFeedback3 $createDummyFeedback(103$dummySkill3);
  549.         $definitions $this->getTestEmailDefinitions(
  550.             $dummyUser,
  551.             $dummyFeedback1,
  552.             $dummyFeedback2,
  553.             $dummyFeedback3,
  554.             $defaultAvatarUrl,
  555.             $dummyImagePath,
  556.             $targetEmail
  557.         );
  558.         $def null;
  559.         if (isset($definitions[$templateKey])) {
  560.             $def $definitions[$templateKey];
  561.         } else {
  562.             // Check by numeric index or prefix
  563.             foreach ($definitions as $k => $d) {
  564.                 if ($k === $templateKey || str_starts_with($d['name'], $templateKey '.')) {
  565.                     $def $d;
  566.                     break;
  567.                 }
  568.             }
  569.         }
  570.         if (!$def) {
  571.             return new JsonResponse([
  572.                 'status' => 'ERROR',
  573.                 'message' => "Unknown templateKey '{$templateKey}'. Available keys: " implode(', 'array_keys($definitions)),
  574.             ], 404);
  575.         }
  576.         if ($preview) {
  577.             $rendered $twig->render($def['template'], $def['params']);
  578.             return new Response($rendered200, ['Content-Type' => 'text/html; charset=UTF-8']);
  579.         }
  580.         try {
  581.             $email = (new TemplatedEmail())
  582.                 ->from(new Address(MailerManager::SENDER_EMAILMailerManager::SENDER_LABEL))
  583.                 ->to($targetEmail)
  584.                 ->subject('[TEST] ' $def['subject'])
  585.                 ->htmlTemplate($def['template'])
  586.                 ->context($def['params']);
  587.             foreach ($def['attachments'] as $att) {
  588.                 if (!empty($att['path']) && file_exists($att['path'])) {
  589.                     $email->attachFromPath($att['path'], $att['name'] ?? null);
  590.                 }
  591.             }
  592.             $mailer->send($email);
  593.             return new JsonResponse([
  594.                 'status' => 'SUCCESS',
  595.                 'template_key' => $templateKey,
  596.                 'template_file' => $def['template'],
  597.                 'subject' => '[TEST] ' $def['subject'],
  598.                 'target_email' => $targetEmail,
  599.                 'preview_url' => $request->getSchemeAndHttpHost() . '/cron/test-email/' $templateKey '/' $token '?preview=1'
  600.             ]);
  601.         } catch (\Throwable $e) {
  602.             return new JsonResponse([
  603.                 'status' => 'ERROR',
  604.                 'template_key' => $templateKey,
  605.                 'template_file' => $def['template'],
  606.                 'error' => $e->getMessage(),
  607.                 'trace' => $e->getTraceAsString()
  608.             ], 500);
  609.         }
  610.     }
  611.     /**
  612.      * Helper returning all 25 synchronized test email definitions.
  613.      */
  614.     private function getTestEmailDefinitions(
  615.         $dummyUser,
  616.         $dummyFeedback1,
  617.         $dummyFeedback2,
  618.         $dummyFeedback3,
  619.         $defaultAvatarUrl,
  620.         $dummyImagePath,
  621.         $targetEmail
  622.     ): array {
  623.         return [
  624.             // --- GROUP 1: Authentication & Account Creation ---
  625.             'coss_account_student' => [
  626.                 'name' => '1. Compte Étudiant / Candidat',
  627.                 'subject' => 'Accédez dès maintenant à votre certification COSS by ESSEC & PwC',
  628.                 'template' => 'ApiBundle/Mail/coss_account_student.html.twig',
  629.                 'params' => ['user' => $dummyUser'password' => 'TempPass123!'],
  630.                 'attachments' => []
  631.             ],
  632.             'coss_account_client_admin' => [
  633.                 'name' => '2. Compte Administrateur Client',
  634.                 'subject' => 'Création de votre compte administrateur pour COSS by ESSEC & PwC',
  635.                 'template' => 'ApiBundle/Mail/coss_account_client_admin.html.twig',
  636.                 'params' => ['user' => $dummyUser'password' => 'TempPass123!'],
  637.                 'attachments' => []
  638.             ],
  639.             'coss_account_coach' => [
  640.                 'name' => '3. Compte Coach / Tuteur',
  641.                 'subject' => 'Accès administrateur à COSS by ESSEC & PwC',
  642.                 'template' => 'ApiBundle/Mail/coss_account_coach.html.twig',
  643.                 'params' => ['user' => $dummyUser'password' => 'TempPass123!'],
  644.                 'attachments' => []
  645.             ],
  646.             'coss_account_admin' => [
  647.                 'name' => '4. Compte Administrateur Back-Office',
  648.                 'subject' => 'COSS - Création de votre compte Super Administrateur',
  649.                 'template' => 'ApiBundle/Mail/coss_account_admin.html.twig',
  650.                 'params' => ['user' => $dummyUser'password' => 'TempPass123!''role' => 'Super Administrateur'],
  651.                 'attachments' => []
  652.             ],
  653.             'two_factor_email' => [
  654.                 'name' => '5. Code 2FA / Connexion',
  655.                 'subject' => 'COSS - Authentification à deux facteurs',
  656.                 'template' => 'ApiBundle/Mail/two_factor_email.html.twig',
  657.                 'params' => ['authCode' => '748291'],
  658.                 'attachments' => []
  659.             ],
  660.             'reset_password' => [
  661.                 'name' => '6. Réinitialisation mot de passe',
  662.                 'subject' => 'COSS - Réinitialisation de votre mot de passe',
  663.                 'template' => 'CossBundle/reset_password/email.html.twig',
  664.                 'params' => ['resetToken' => (object)['token' => 'sample-reset-token-xyz'], 'resetType' => 'client'],
  665.                 'attachments' => []
  666.             ],
  667.             // --- GROUP 2: Demandes d\'évaluation & Relances ---
  668.             'batch_feedback_recap_email' => [
  669.                 'name' => '7. Demande d\'évaluation groupée',
  670.                 'subject' => 'Aidez Bertrand Pon à certifier ses soft skills',
  671.                 'template' => 'ApiBundle/Mail/batch_feedback_recap_email.html.twig',
  672.                 'params' => [
  673.                     'firstnameSender' => 'Bertrand',
  674.                     'lastnameSender' => 'Pon',
  675.                     'feedbacksBySender' => [
  676.                         'name' => 'Bertrand Pon',
  677.                         'company' => 'COSS Corp',
  678.                         'photo' => null,
  679.                         'feedbacks' => [$dummyFeedback1$dummyFeedback2$dummyFeedback3]
  680.                     ],
  681.                     'locale' => 'fr',
  682.                     'defaultAvatar' => $defaultAvatarUrl
  683.                 ],
  684.                 'attachments' => []
  685.             ],
  686.             'recall_2_feedback_recap_email' => [
  687.                 'name' => '8. Relance évaluation J+2',
  688.                 'subject' => 'Relance - Certification de Bertrand Pon, votre réponse est attendue dans les 48 heures',
  689.                 'template' => 'ApiBundle/Mail/recall_2_feedback_recap_email.html.twig',
  690.                 'params' => [
  691.                     'fullnameFirstFeedback' => 'Bertrand Pon',
  692.                     'feedbacksBySender' => [
  693.                         'name' => 'Bertrand Pon',
  694.                         'company' => 'COSS Corp',
  695.                         'photo' => null,
  696.                         'feedbacks' => [$dummyFeedback1$dummyFeedback2]
  697.                     ],
  698.                     'locale' => 'fr',
  699.                     'defaultAvatar' => $defaultAvatarUrl
  700.                 ],
  701.                 'attachments' => []
  702.             ],
  703.             'recall_7_feedback_recap_email' => [
  704.                 'name' => '9. Relance évaluation J+7',
  705.                 'subject' => 'Relance - Bertrand Pon a besoin de 8 réponses minimum',
  706.                 'template' => 'ApiBundle/Mail/recall_7_feedback_recap_email.html.twig',
  707.                 'params' => [
  708.                     'fullnameFirstFeedback' => 'Bertrand Pon',
  709.                     'feedbacksBySender' => [
  710.                         'name' => 'Bertrand Pon',
  711.                         'company' => 'COSS Corp',
  712.                         'photo' => null,
  713.                         'feedbacks' => [$dummyFeedback1$dummyFeedback2]
  714.                     ],
  715.                     'locale' => 'fr',
  716.                     'defaultAvatar' => $defaultAvatarUrl
  717.                 ],
  718.                 'attachments' => []
  719.             ],
  720.             'recall_manual_feedback_recap_email' => [
  721.                 'name' => '10. Relance évaluation manuelle',
  722.                 'subject' => 'Bertrand Pon vous relance pour sa certification ESSEC & PwC',
  723.                 'template' => 'ApiBundle/Mail/recall_manual_feedback_recap_email.html.twig',
  724.                 'params' => [
  725.                     'fullnameFirstFeedback' => 'Bertrand Pon',
  726.                     'feedbacksBySender' => [
  727.                         'name' => 'Bertrand Pon',
  728.                         'company' => 'COSS Corp',
  729.                         'photo' => null,
  730.                         'feedbacks' => [$dummyFeedback1$dummyFeedback2]
  731.                     ],
  732.                     'locale' => 'fr',
  733.                     'defaultAvatar' => $defaultAvatarUrl
  734.                 ],
  735.                 'attachments' => []
  736.             ],
  737.             'bad_rating_email' => [
  738.                 'name' => '11. Alerte mauvaise note (< 2)',
  739.                 'subject' => '[BO-COSS] Moyenne inférieure à 2 - Bertrand Pon',
  740.                 'template' => 'ApiBundle/Mail/bad_rating_email.html.twig',
  741.                 'params' => ['feedback' => $dummyFeedback1],
  742.                 'attachments' => []
  743.             ],
  744.             // --- GROUP 3: Validation des répondants (Recipients) ---
  745.             'recipient_list_changed' => [
  746.                 'name' => '12. Demande validation liste répondants (Manager)',
  747.                 'subject' => 'Pour validation – Certification ESSEC & PwC de Bertrand Pon',
  748.                 'template' => 'ApiBundle/Mail/recipient_list_changed.html.twig',
  749.                 'params' => ['user' => $dummyUser'guestUrl' => 'https://manager.coss-certification.com/recipients/list/validation/sample-token/1'],
  750.                 'attachments' => []
  751.             ],
  752.             'recipient_list_changed_recall' => [
  753.                 'name' => '13. Relance validation liste répondants (Manager)',
  754.                 'subject' => 'Rappel — Validation en attente pour la certification de Bertrand Pon',
  755.                 'template' => 'ApiBundle/Mail/recipient_list_changed_recall.html.twig',
  756.                 'params' => ['user' => $dummyUser'guestUrl' => 'https://manager.coss-certification.com/recipients/list/validation/sample-token/1'],
  757.                 'attachments' => []
  758.             ],
  759.             'all_recipients_validated' => [
  760.                 'name' => '14. Contacts tous validés',
  761.                 'subject' => 'Vos contacts pour votre certification COSS by ESSEC & PwC ont été validés',
  762.                 'template' => 'ApiBundle/Mail/all_recipients_validated.html.twig',
  763.                 'params' => [
  764.                     'user' => $dummyUser,
  765.                     'recipients' => [
  766.                         'validated' => [
  767.                             (object)['user' => (object)['email' => 'collegue1@entreprise.com']],
  768.                             (object)['user' => (object)['email' => 'manager@entreprise.com']],
  769.                         ]
  770.                     ]
  771.                 ],
  772.                 'attachments' => []
  773.             ],
  774.             'all_recipients_deleted' => [
  775.                 'name' => '15. Contacts tous refusés',
  776.                 'subject' => 'Vos contacts pour votre certification COSS by ESSEC & PwC ont été refusés',
  777.                 'template' => 'ApiBundle/Mail/all_recipients_deleted.html.twig',
  778.                 'params' => [
  779.                     'user' => $dummyUser,
  780.                     'recipients' => [
  781.                         'deleted' => [
  782.                             (object)['user' => (object)['email' => 'fake1@perso.com'], 'comment' => 'Adresse email personnelle non autorisée'],
  783.                             (object)['user' => (object)['email' => 'fake2@gmail.com'], 'comment' => 'Incohérence entreprise'],
  784.                         ]
  785.                     ]
  786.                 ],
  787.                 'attachments' => []
  788.             ],
  789.             'all_recipients_info' => [
  790.                 'name' => '16. Contacts - Complément d\'informations requis',
  791.                 'subject' => 'COSS by ESSEC & PwC – Besoin d’information complémentaires',
  792.                 'template' => 'ApiBundle/Mail/all_recipients_info.html.twig',
  793.                 'params' => [
  794.                     'user' => $dummyUser,
  795.                     'recipients' => [
  796.                         'info' => [
  797.                             (object)['user' => (object)['email' => 'contact1@gmail.com'], 'comment' => 'Merci de fournir une preuve professionnelle']
  798.                         ]
  799.                     ],
  800.                     'link' => 'https://app.coss-certification.com/guest/recipients/proof/1/dummy-proof-token-123456'
  801.                 ],
  802.                 'attachments' => []
  803.             ],
  804.             'recipients_multi_cases' => [
  805.                 'name' => '17. Contacts - Cas mixte (validés / infos / refusés)',
  806.                 'subject' => 'COSS by ESSEC & PwC – Résultat de l’analyse de vos contacts',
  807.                 'template' => 'ApiBundle/Mail/recipients_multi_cases.html.twig',
  808.                 'params' => [
  809.                     'user' => $dummyUser,
  810.                     'recipients' => [
  811.                         'validated' => [
  812.                             (object)['user' => (object)['email' => 'valide@entreprise.com']]
  813.                         ],
  814.                         'info' => [
  815.                             (object)['user' => (object)['email' => 'douteux@gmail.com'], 'comment' => 'Lien LinkedIn requis']
  816.                         ],
  817.                         'deleted' => [
  818.                             (object)['user' => (object)['email' => 'refuse@jetable.fr'], 'comment' => 'Domaine jetable non autorisé']
  819.                         ]
  820.                     ],
  821.                     'link' => 'https://app.coss-certification.com/guest/recipients/proof/1/dummy-proof-token-123456'
  822.                 ],
  823.                 'attachments' => []
  824.             ],
  825.             'coss_new_proof' => [
  826.                 'name' => '18. Alerte admin - Nouvelle preuve ajoutée',
  827.                 'subject' => 'Validation des répondants - Un utilisateur a ajouté une preuve',
  828.                 'template' => 'ApiBundle/Mail/coss_new_proof.html.twig',
  829.                 'params' => ['appUser' => $dummyUser'recipients' => [], 'proofs' => []],
  830.                 'attachments' => []
  831.             ],
  832.             // --- GROUP 4: Badges & Certifications ---
  833.             'badges_email' => [
  834.                 'name' => '19. Envoi manuel Badge certifié',
  835.                 'subject' => 'Votre certification COSS by ESSEC et PwC',
  836.                 'template' => 'ApiBundle/Mail/badges_email.html.twig',
  837.                 'params' => ['user' => $dummyUser'link' => 'https://app.coss-certification.com/badges/sample-token''skill' => 'Écoute active'],
  838.                 'attachments' => $dummyImagePath ? [['path' => $dummyImagePath'name' => 'badge-ecoute-active.png']] : []
  839.             ],
  840.             'badges_automatic_email' => [
  841.                 'name' => '20. Félicitations Badge automatique certifié',
  842.                 'subject' => 'Félicitations, vous avez certifié votre compétence Leadership',
  843.                 'template' => 'ApiBundle/Mail/badges_automatic_email.html.twig',
  844.                 'params' => [
  845.                     'user' => $dummyUser,
  846.                     'link' => 'https://app.coss-certification.com/badges/sample-token',
  847.                     'linkedinUrl' => 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&name=' urlencode('Leadership: 4.5/5') . '&organizationId=79273788&issueYear=' date('Y') . '&issueMonth=' date('n') . '&certUrl=' urlencode('https://app.coss-certification.com/badges/sample-token'),
  848.                     'badge' => ['skill' => 'Leadership''average' => 4.5'year' => date('Y'), 'month' => date('n')]
  849.                 ],
  850.                 'attachments' => $dummyImagePath ? [['path' => $dummyImagePath'name' => 'badge-leadership.png']] : []
  851.             ],
  852.             'badges_recap_email' => [
  853.                 'name' => '21. Récapitulatif Badges certifiés',
  854.                 'subject' => 'Vos certifications COSS',
  855.                 'template' => 'ApiBundle/Mail/badges_recap_email.html.twig',
  856.                 'params' => [
  857.                     'user' => $dummyUser,
  858.                     'links' => [
  859.                         'https://app.coss-certification.com/badges/sample-token-1',
  860.                         'https://app.coss-certification.com/badges/sample-token-2'
  861.                     ]
  862.                 ],
  863.                 'attachments' => $dummyImagePath ? [['path' => $dummyImagePath'name' => 'badge-ecoute-active.png']] : []
  864.             ],
  865.             // --- GROUP 5: Synthèse IA, Quiz IA & Justificatifs Étudiant ---
  866.             'synthesis_unlocked_email' => [
  867.                 'name' => '22. Synthèse IA débloquée',
  868.                 'subject' => 'Découvrez votre synthèse COSS personnalisée – Vos talents et vos axes de progression !',
  869.                 'template' => 'ApiBundle/Mail/synthesis_unlocked_email.html.twig',
  870.                 'params' => ['user' => $dummyUser],
  871.                 'attachments' => []
  872.             ],
  873.             'quiz_skill_recommendation' => [
  874.                 'name' => '23. Recommandations Quiz IA',
  875.                 'subject' => 'Quelles compétences certifier pour mon projet professionnel ?',
  876.                 'template' => 'ApiBundle/Mail/quiz_skill_recommendation.html.twig',
  877.                 'params' => [
  878.                     'quizSkillRecommendation' => [
  879.                         'data' => [
  880.                             'skills' => [
  881.                                 ['title' => 'Leadership & Influence''description' => 'Capacité à mobiliser et inspirer une équipe vers un objectif commun.'],
  882.                                 ['title' => 'Communication interpersonnelle''description' => 'Aptitude à adapter son message et interagir efficacement avec tout profil.'],
  883.                                 ['title' => 'Gestion du stress & Résilience''description' => 'Maintien de la performance et de la lucidité dans les contextes exigeants.'],
  884.                                 ['title' => 'Résolution de problèmes complexes''description' => 'Approche méthodique pour déconstruire les enjeux et trouver des solutions viables.'],
  885.                                 ['title' => 'Esprit critique & Agilité''description' => 'Capacité de remise en question constructive et d\'adaptation rapide aux évolutions.']
  886.                             ]
  887.                         ]
  888.                     ]
  889.                 ],
  890.                 'attachments' => []
  891.             ],
  892.             'student_proof_info' => [
  893.                 'name' => '24. Demande justificatif tarif étudiant',
  894.                 'subject' => 'COSS by ESSEC & PwC - Complément d’information pour valider votre éligibilité au tarif étudiant/jeune diplômé',
  895.                 'template' => 'ApiBundle/Mail/student_proof_info.html.twig',
  896.                 'params' => ['user' => $dummyUser'upload_link' => 'https://app.coss-certification.com/guest/student-proof/1/dummy-student-token-654321'],
  897.                 'attachments' => []
  898.             ],
  899.             'student_proof_declined' => [
  900.                 'name' => '25. Justificatif étudiant refusé',
  901.                 'subject' => 'Retour sur votre éligibilité au tarif étudiant/jeune diplômé – COSS by ESSEC & PwC',
  902.                 'template' => 'ApiBundle/Mail/student_proof_declined.html.twig',
  903.                 'params' => ['user' => $dummyUser'payment_link' => 'https://buy.stripe.com/9B63cvdpL2sCaAKfZEfnO02?prefilled_email=' urlencode($targetEmail) . '&client_reference_id=upgrade_1'],
  904.                 'attachments' => []
  905.             ]
  906.         ];
  907.     }
  908. }