src/Controller/WebController.php line 1044

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Config;
  4. use App\Entity\Candidature;
  5. use App\Entity\CompteRecrutement;
  6. use App\Entity\Enum\FrenchRegion;
  7. use App\Entity\Offer;
  8. use App\Entity\OfferVisit;
  9. use App\Entity\Profile;
  10. use App\Entity\ProfileVisit;
  11. use App\Entity\Report;
  12. use App\Entity\Society;
  13. use App\Entity\TempMail;
  14. use App\Entity\User;
  15. use App\Form\RegistrationFormType;
  16. use App\Form\ReportFormType;
  17. use App\Form\ResetPasswordRequestFormType;
  18. use App\Repository\CandidatureRepository;
  19. use App\Repository\ExternalCompanyRepository;
  20. use App\Repository\OfferFavoriteRepository;
  21. use App\Repository\OfferRepository;
  22. use App\Repository\ProfileRepository;
  23. use App\Repository\ProfileVisitRepository;
  24. use App\Repository\PublicationRepository;
  25. use App\Repository\NewsRepository;
  26. use App\Repository\SocietyRepository;
  27. use App\Repository\UserRepository;
  28. use App\Service\TempMailService;
  29. use App\UseCase\Commande\CreateCommande;
  30. use DateTime;
  31. use DateTimeImmutable;
  32. use Doctrine\ORM\EntityManagerInterface;
  33. use Doctrine\ORM\NonUniqueResultException;
  34. use Knp\Component\Pager\PaginatorInterface;
  35. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  36. use Stripe\Stripe;
  37. use Stripe\Subscription;
  38. use Symfony\Bridge\Twig\Mime\TemplatedEmail;
  39. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  40. use Symfony\Component\HttpFoundation\JsonResponse;
  41. use Symfony\Component\HttpFoundation\Request;
  42. use Symfony\Component\HttpFoundation\Response;
  43. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  44. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  45. use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
  46. use Symfony\Component\Mailer\MailerInterface;
  47. use Symfony\Component\Routing\Annotation\Route;
  48. use App\Repository\BlogRepository;
  49. use Symfony\Contracts\HttpClient\HttpClientInterface;
  50. class WebController extends AbstractController
  51. {
  52.     /**
  53.      * @Route("/", name="homepage")
  54.      */
  55.     public function index(BlogRepository $blogRepositoryOfferRepository $offerRepositoryProfileRepository $profileRepositoryNewsRepository $newsRepository): Response
  56.     {
  57.         $user $this->getUser();
  58.         if (
  59.             $user && !in_array('ROLE_SOCIETY'$user->getRoles()) &&
  60.             !in_array('ROLE_ADMIN'$user->getRoles())
  61.         ) {
  62.             // Si le user n'a pas de profils, ou s'il a des profils mais aucun n'est terminé
  63.             $profile $user->getUniqueProfile();
  64.             if ($profile->getId() == null) {
  65.                 return $this->redirectToRoute('complete_resume_profile');
  66.             }
  67.             if ($profile->getFinished() === || $profile->getFinished() === false) {
  68.                 return $this->redirectToRoute('complete_first_edit');
  69.             }
  70.         }
  71.         $offers $offerRepository->getLatest();
  72.         $profiles $profileRepository->getLatest(3);
  73.         $news $newsRepository->getLatest();
  74.         $blogs $blogRepository->findBy([], ['created_at' => 'DESC'], 3);
  75.         $form $this->createForm(RegistrationFormType::class, new User());
  76.         $resetForm $this->createForm(ResetPasswordRequestFormType::class);
  77.         return $this->render('page/home.html.twig', [
  78.             'offers' => $offers,
  79.             'profiles' => $profiles,
  80.             'regions' => FrenchRegion::REGION_DEPARTEMENT,
  81.             'registrationForm' => $form->createView(),
  82.             'resetForm' => $resetForm->createView(),
  83.             'faqs' => Config::FAQ,
  84.             'cityList' => Config::FEATURED_CITY,
  85.             'news' => $news,
  86.             'isComing' => (bool)$this->getParameter('is_coming'),
  87.             'isNotShow' => (bool)$this->getParameter('is_not_show'),
  88.             'blogs' => $blogs
  89.         ]);
  90.     }
  91.     /**
  92.      * @Route("/societe/{slug}", name="society_profil", methods={"GET"})
  93.      */
  94.     public function society(
  95.         string                     $slug,
  96.         Request                    $request,
  97.         OfferRepository            $offerRepository,
  98.         PaginatorInterface         $paginator,
  99.         ExternalCompanyRepository  $externalCompanyRepository,
  100.         SocietyRepository          $societyRepository
  101.     ): Response {
  102.         /* @var  User $user */
  103.         $user $this->getUser();
  104.         // D'abord chercher dans la table Society locale
  105.         $society $societyRepository->findOneBy(['slug' => $slug]);
  106.         $externalCompanyProfile null;
  107.         $isExternalSociety false;
  108.         if ($society) {
  109.             // Société locale trouvée
  110.             $offers $offerRepository->findBy(['society' => $society], ['id' => 'desc']);
  111.             // Récupérer les informations externes si la société a un SIREN
  112.             if ($society->getSiren()) {
  113.                 $externalCompanyProfile $externalCompanyRepository->findCompanyWithProfileBySiren($society->getSiren());
  114.             }
  115.             $isOwner $user && $user->isSociety() && ($society->getId() === $user->getSociety()->getId());
  116.             $reportedId $society->getId();
  117.         } else {
  118.             // Société locale non trouvée, chercher dans la base externe par slug
  119.             $externalCompanyProfile $externalCompanyRepository->findCompanyWithProfileBySlug($slug);
  120.             if (!$externalCompanyProfile) {
  121.                 // Aucune société trouvée, retourner 404
  122.                 throw $this->createNotFoundException('Société non trouvée');
  123.             }
  124.             // Créer une société virtuelle pour l'affichage
  125.             $society = new Society();
  126.             $society->setName($externalCompanyProfile['name'] ?? 'Centre de formation');
  127.             $society->setSiren($externalCompanyProfile['profile']['siren'] ?? $slug);
  128.             $society->setType('Centre de formation');
  129.             // Récupérer les offres externes (society = null et siren correspond)
  130.             $offers $offerRepository->createQueryBuilder('o')
  131.                 ->where('o.society IS NULL')
  132.                 ->andWhere('o.siren = :siren')
  133.                 ->setParameter('siren'$externalCompanyProfile['profile']['siren'] ?? $slug)
  134.                 ->orderBy('o.id''DESC')
  135.                 ->getQuery()
  136.                 ->getResult();
  137.             $isExternalSociety true;
  138.             $isOwner false;
  139.             $reportedId null;
  140.         }
  141.         $viewData = [
  142.             'society' => $society,
  143.             'offers' => $paginator->paginate($offers$request->query->getInt('page'1), 10),
  144.             'isOwner' => $isOwner,
  145.             'reportedId' => $reportedId,
  146.             'reportedTarget' => 'society',
  147.             'externalCompanyProfile' => $externalCompanyProfile,
  148.             'isExternalSociety' => $isExternalSociety,
  149.         ];
  150.         $viewData['reportForm'] = $this->createForm(ReportFormType::class, new Report())->createView();
  151.         if (!$this->getUser()) {
  152.             $viewData['registrationForm'] = $this->createForm(RegistrationFormType::class, new User())->createView();
  153.         }
  154.         return $this->render('page/society.html.twig'$viewData);
  155.     }
  156.     /**
  157.      * @Route("/societe/formation/{slug}", name="society_formation_profile", methods={"GET"})
  158.      */
  159.     public function societyFormation(
  160.         string                    $slug,
  161.         Request                   $request,
  162.         OfferRepository           $offerRepository,
  163.         PaginatorInterface        $paginator,
  164.         ExternalCompanyRepository $externalCompanyRepository,
  165.         SocietyRepository         $societyRepository,
  166.         \App\Repository\FormationRepository $formationRepository
  167.     ): Response {
  168.         // Ce endpoint est 100% sous-domaine (externe)
  169.         $society null;
  170.         $externalCompanyProfile null;
  171.         $isExternalSociety true;
  172.         $isOwner false;
  173.         $reportedId null;
  174.         // Tenter de trouver le profil entreprise externe par slug ou SIREN
  175.         $externalCompanyProfile $externalCompanyRepository->findCompanyWithProfileBySlug($slug);
  176.         if (!$externalCompanyProfile) {
  177.             $externalCompanyProfile $externalCompanyRepository->findCompanyWithProfileBySiren($slug);
  178.         }
  179.         // Récupérer les formations par user slug externe
  180.         $perPage 6;
  181.         $pageFormations max(1, (int)$request->query->get('pf'1));
  182.         $centreFormations = [];
  183.         $centreFormationsTotalPages 0;
  184.         $activeUserIds $formationRepository->getActiveUserIds();
  185.         $externalUserId $formationRepository->getExternalUserIdBySlug($slug);
  186.         if ($externalUserId) {
  187.             $total $formationRepository->countFormationsByUserId($externalUserId$activeUserIds);
  188.             $centreFormations $formationRepository->getFormationsByUserId(
  189.                 $externalUserId,
  190.                 $perPage,
  191.                 ($pageFormations 1) * $perPage,
  192.                 $activeUserIds
  193.             );
  194.             // Normaliser certains champs JSON en tableaux
  195.             foreach ($centreFormations as &$cf) {
  196.                 foreach (['profilsAcceptes''lieuCours''lieuCoursVilles''certifications'] as $jsonField) {
  197.                     if (isset($cf[$jsonField]) && is_string($cf[$jsonField])) {
  198.                         $trimmed trim($cf[$jsonField]);
  199.                         if ($trimmed !== '' && ($trimmed[0] === '[' || $trimmed[0] === '{')) {
  200.                             $decoded json_decode($cf[$jsonField], true);
  201.                             if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
  202.                                 $cf[$jsonField] = $decoded;
  203.                             }
  204.                         }
  205.                     }
  206.                 }
  207.             }
  208.             unset($cf);
  209.             $centreFormationsTotalPages = (int) ceil($total $perPage);
  210.             // Fallback pour header: si pas d'infos entreprise, déduire name/avatar depuis la première formation
  211.             if ((!$externalCompanyProfile || empty($externalCompanyProfile['name'])) && !empty($centreFormations)) {
  212.                 $first $centreFormations[0];
  213.                 $derivedName trim(((string)($first['user_nom'] ?? '')) . ' ' . ((string)($first['user_prenom'] ?? '')));
  214.                 $derivedAvatar null;
  215.                 if (!empty($first['user_photo'])) {
  216.                     $base $_ENV['FORMATION_URL'] ?? '';
  217.                     $derivedAvatar rtrim($base'/') . '/uploads/avatars/' $first['user_photo'];
  218.                 }
  219.                 $externalCompanyProfile = [
  220.                     'name' => $derivedName ?: 'Centre de formation',
  221.                     'avatar' => $derivedAvatar,
  222.                     'profile' => [
  223.                         'siren' => $slug
  224.                     ]
  225.                 ];
  226.             }
  227.         }
  228.         $viewData = [
  229.             'society' => $society,
  230.             'externalCompanyProfile' => $externalCompanyProfile,
  231.             'isExternalSociety' => $isExternalSociety,
  232.             'isOwner' => $isOwner,
  233.             'reportedId' => $reportedId,
  234.             'reportedTarget' => 'society',
  235.             'centreFormations' => $centreFormations,
  236.             'centreFormationsCurrentPage' => $pageFormations,
  237.             'centreFormationsTotalPages' => $centreFormationsTotalPages,
  238.         ];
  239.         $viewData['reportForm'] = $this->createForm(ReportFormType::class, new Report())->createView();
  240.         if (!$this->getUser()) {
  241.             $viewData['registrationForm'] = $this->createForm(RegistrationFormType::class, new User())->createView();
  242.         }
  243.         return $this->render('page/society_formation.html.twig'$viewData);
  244.     }
  245.     /**
  246.      * @Route("/job/{slug}", name="public_mission", methods={"GET","POST"})
  247.      *
  248.      * @param Offer $offer
  249.      * @param OfferRepository $offerRepository
  250.      * @param Request $request
  251.      * @param EntityManagerInterface $entityManager
  252.      * @param CandidatureRepository $candidatureRepository
  253.      * @param ProfileRepository $profileRepository
  254.      * @param OfferFavoriteRepository $offerFavoriteRepository
  255.      * @param MailerInterface $mailer
  256.      * @return Response
  257.      * @throws TransportExceptionInterface
  258.      */
  259.     public function offer(
  260.         Offer                   $offer,
  261.         OfferRepository         $offerRepository,
  262.         Request                 $request,
  263.         EntityManagerInterface  $entityManager,
  264.         CandidatureRepository   $candidatureRepository,
  265.         ProfileRepository       $profileRepository,
  266.         OfferFavoriteRepository $offerFavoriteRepository,
  267.         MailerInterface         $mailer,
  268.         TempMailService         $tempMailService,
  269.         SessionInterface        $session,
  270.         \App\Repository\ExternalCompanyRepository $externalCompanyRepository,
  271.         \App\Repository\FormationRepository $formationRepository,
  272.         \App\Service\LinkCheckerService $linkCheckerService
  273.     ): Response {
  274.         // Vérifier si l'URL précédente est déjà présente dans la session
  275.         if (!$session->has('previous_url')) {
  276.             // Récupérer l'URL précédente (Referer)
  277.             $referer $request->headers->get('referer');
  278.             // Sauvegarder l'URL dans la session si elle n'existe pas déjà
  279.             if ($referer) {
  280.                 $session->set('previous_url'$referer);
  281.             }
  282.         }
  283.         $user $this->getUser();
  284.         if ($user && !in_array('ROLE_ADMIN'$user->getRoles()) && $offer->getUser() != $user && ($offer->getInvalid() == || $offer->getInvalid() == 2)) {
  285.             return $this->redirectToRoute('homepage');
  286.         }
  287.         if (!$user && ($offer->getInvalid() == || $offer->getInvalid() == 2)) {
  288.             return $this->redirectToRoute('homepage');
  289.         }
  290.         $currentDate = new DateTime();
  291.         // Check if offer is expired or 404 manually before displaying it
  292.         if ($offer->getExpireAt() !== null && $offer->getExpireAt() < $currentDate) {
  293.             $this->addFlash('package_error'"Cette offre a expiré et n'est plus disponible.");
  294.             return $this->redirectToRoute('homepage');
  295.         }
  296.         if ($offer->getLinkExtern()) {
  297.             if ($linkCheckerService->is404($offer->getLinkExtern())) {
  298.                 // Ne pas modifier la base ici : l'expiration est gérée par la
  299.                 // commande cron app:offers:check-links pour éviter les faux positifs.
  300.                 $this->addFlash('package_error'"L'offre n'est plus disponible.");
  301.                 return $this->redirectToRoute('homepage');
  302.             }
  303.         }
  304.         /* @var User $user */
  305.         $user $this->getUser();
  306.         $candidatures = [];
  307.         $isOwner false;
  308.         if ($user) {
  309.             $currentOwnerID null;
  310.             if ($offer->getSociety()) {
  311.                 $currentOwnerID $offer->getSociety()->getId();
  312.             }
  313.             $isOwner $user->isSociety() && ($user->getSociety()->getId() == $currentOwnerID);
  314.             $offerVisit = new OfferVisit();
  315.             if (!$isOwner) {
  316.                 $offerVisit
  317.                     ->setUser($user)
  318.                     ->setOffer($offer)
  319.                     ->setCreatedAt(new DateTime());
  320.                 $entityManager->persist($offerVisit);
  321.                 $entityManager->flush();
  322.             }
  323.             // REDIRECT IF PROFIL NOT COMPLETE
  324.             if ($user->isFreelance()) {
  325.                 $currentProfil $user->getUniqueProfile();
  326.                 if (!$currentProfil->getId()) {
  327.                     return $this->redirectToRoute('complete_resume_profile');
  328.                 }
  329.             }
  330.         }
  331.         $alreadyPostulate null;
  332.         $similarOffersExact $offerRepository->findSimilarExact($offer->getId(), $offer->getContrats(), 2);
  333.         if (count($similarOffersExact) > 0) {
  334.             $similarOffers $similarOffersExact;
  335.         } else {
  336.             $similarOffersPartiel $offerRepository->findSimilarPartiel($offer->getId(), $offer->getContrats(), 2);
  337.             if (count($similarOffersPartiel) > 0) {
  338.                 $similarOffers $similarOffersExact;
  339.             } else {
  340.                 $similarOffers $offerRepository->findSimilar($offer->getId(), 2);
  341.             }
  342.         }
  343.         if ($user) {
  344.             $candidature = new Candidature();
  345.             $candidature->setOffer($offer);
  346.             $candidature->setPostulateAt(new DateTimeImmutable());
  347.             if ($user->isFreelance()) {
  348.                 $candidature->setProfile($user->getUniqueProfile());
  349.                 $alreadyPostulate $candidatureRepository->findOneBy([
  350.                     'profile' => $user->getUniqueProfile(),
  351.                     'offer' => $offer
  352.                 ]);
  353.             } else {
  354.                 $profiles $user->getProfiles();
  355.                 $allCandidatures $candidatureRepository->findByOfferAndSociety($offer$profiles);
  356.                 foreach ($allCandidatures as $c) {
  357.                     $candidatures[] = $c->getProfile()->getId();
  358.                 }
  359.             }
  360.         }
  361.         if ($request->isMethod('POST')) {
  362.             if ($this->getUser()) {
  363.                 if ($user->isSociety()) {
  364.                     $interContrat $profileRepository->find($request->get('intercontrat'));
  365.                     $candidature->setProfile($interContrat);
  366.                 }
  367.                 $offer->incrementUnseenCandidature();
  368.                 $candidature->setAnswers($request->get('answers'));
  369.                 if ($candidature->getProfile()) {
  370.                     $entityManager->persist($candidature);
  371.                     $entityManager->flush();
  372.                     // --- SEND MAIL SERVICE ----
  373.                     // MAIL TO RECRUTEUR
  374.                     $emailTo null;
  375.                     if ($offer->getUser()) {
  376.                         $emailTo $offer->getUser()->getEmail();
  377.                     } elseif ($offer->getSociety()) {
  378.                         $emailTo $offer->getSociety()->getUser()->getEmail();
  379.                     } elseif ($offer->getUserRecruteur()) {
  380.                         $emailTo $offer->getUserRecruteur()->getEmail();
  381.                     } else {
  382.                         // External offer without linked society: fetch contact email from external repository using SIREN
  383.                         if (method_exists($offer'getSiren')) {
  384.                             $siren $offer->getSiren();
  385.                             if ($siren) {
  386.                                 $ext $externalCompanyRepository->findCompanyBySiren($siren);
  387.                                 if ($ext && !empty($ext['email'])) {
  388.                                     $emailTo $ext['email'];
  389.                                 }
  390.                             }
  391.                         }
  392.                     }
  393.                     // Fallback: use contact_mail from the offer if available (for Formateur offers)
  394.                     if (empty($emailTo) && $offer->getContactMail()) {
  395.                         $emailTo $offer->getContactMail();
  396.                     }
  397.                     // Only send email if we have a valid email address
  398.                     if (!empty($emailTo)) {
  399.                         $tempMail = new TempMail();
  400.                         $tempMail->setAdress($emailTo);
  401.                         $tempMail->setType_OfferCandidature();
  402.                         $tempMail->setOffer($offer);
  403.                         $tempMail->setCandidature($candidature);
  404.                         $tempMailService->sendingMail($tempMail);
  405.                     }
  406.                 }
  407.             }
  408.             return $this->redirectToRoute('public_mission', ['slug' => $offer->getSlug()]);
  409.         }
  410.         /** @var DateTime $createdDate */
  411.         $createdDate $offer->getCreatedAt();
  412.         $endDate = clone $offer->getExpireAt();
  413.         // $endDate->add(new \DateInterval('P6M'));
  414.         // if ($offer->getExpireAt()) {
  415.         //     $endDate = $offer->getExpireAt();
  416.         // }
  417.         $offerFavorite $offerFavoriteRepository->findOneBy(['user' => $user'offer' => $offer]);
  418.         // External company info for this offer if no linked society
  419.         $externalCompanies = [];
  420.         if ($offer->getSociety() === null && method_exists($offer'getSiren')) {
  421.             $siren $offer->getSiren();
  422.             if ($siren) {
  423.                 $info $externalCompanyRepository->findCompanyBySiren($siren);
  424.                 if ($info) {
  425.                     $externalCompanies[$offer->getId()] = $info;
  426.                 }
  427.             }
  428.         }
  429.         // Récupérer les formations pertinentes basées sur le titre de l'offre
  430.         $relevantFormations = [];
  431.         try {
  432.             $activeUserIds $formationRepository->getActiveUserIds();
  433.             if (!empty($activeUserIds)) {
  434.                 $relevantFormations $formationRepository->findFormationsByOfferTitle(
  435.                     $offer->getTitle(),
  436.                     3,
  437.                     $activeUserIds
  438.                 );
  439.             }
  440.         } catch (\Throwable $e) {
  441.             error_log("Erreur lors de la récupération des formations pertinentes: " $e->getMessage());
  442.         }
  443.         return $this->render('page/offer.html.twig', [
  444.             'offer' => $offer,
  445.             'similarOffers' => $similarOffers,
  446.             'isOwner' => $isOwner,
  447.             'alreadyPostulate' => $alreadyPostulate,
  448.             'isInFavorite' => !empty($offerFavorite),
  449.             'candidatures' => $candidatures,
  450.             'offerEndDate' => $endDate,
  451.             'currentDate' => $currentDate,
  452.             'externalCompanies' => $externalCompanies,
  453.             'relevantFormations' => $relevantFormations,
  454.         ]);
  455.     }
  456.     /**
  457.      * @Route("/ajax/submit-candidature", name="ajax_submit_candidature", methods={"POST"})
  458.      */
  459.     public function ajaxSubmitCandidature(
  460.         Request $request,
  461.         ProfileRepository $profileRepository,
  462.         OfferRepository $offerRepository,
  463.         EntityManagerInterface $entityManager,
  464.         CandidatureRepository $candidatureRepository
  465.     ): JsonResponse {
  466.         $user $this->getUser();
  467.         if ($user) {
  468.             $candidature = new Candidature();
  469.             $offer $offerRepository->find($request->get('offer'));
  470.             $candidature->setOffer($offer);
  471.             $candidature->setPostulateAt(new DateTimeImmutable());
  472.             if ($user->isFreelance()) {
  473.                 $candidature->setProfile($user->getUniqueProfile());
  474.                 $alreadyPostulate $candidatureRepository->findOneBy([
  475.                     'profile' => $user->getUniqueProfile(),
  476.                     'offer' => $offer
  477.                 ]);
  478.             } else {
  479.                 $profiles $user->getProfiles();
  480.                 $allCandidatures $candidatureRepository->findByOfferAndSociety($offer$profiles);
  481.                 foreach ($allCandidatures as $c) {
  482.                     $candidatures[] = $c->getProfile()->getId();
  483.                 }
  484.             }
  485.             return new JsonResponse(['status' => 'success']);
  486.         }
  487.         return new JsonResponse(['status' => 'error'], Response::HTTP_BAD_REQUEST);
  488.     }
  489.     /**
  490.      * @Route("/profil/{slug}", name="public_profile", methods={"GET"})
  491.      */
  492.     public function profile(
  493.         Profile                $profile,
  494.         EntityManagerInterface $entityManager,
  495.         ProfileRepository      $profileRepository,
  496.         ProfileVisitRepository $profileVisitRepository,
  497.         CandidatureRepository  $candidatureRepository,
  498.         Request                $request
  499.     ): Response {
  500.         /* @var User $user */
  501.         $user $this->getUser();
  502.         $siren $request->query->get('s');
  503.         // Si un SIREN est fourni, c'est une offre externe - vérifier l'accès via SIREN
  504.         if ($siren) {
  505.             // Vérifier que le profil a candidaté sur au moins une offre avec ce SIREN
  506.             $candidatures $candidatureRepository->createQueryBuilder('c')
  507.                 ->join('c.offer''o')
  508.                 ->where('c.profile = :profile')
  509.                 ->andWhere('o.siren = :siren')
  510.                 ->andWhere('o.society IS NULL'// Offre externe (sans société liée)
  511.                 ->setParameter('profile'$profile)
  512.                 ->setParameter('siren'$siren)
  513.                 ->getQuery()
  514.                 ->getResult();
  515.             // Si aucune candidature trouvée avec ce SIREN, refuser l'accès
  516.             if (empty($candidatures)) {
  517.                 throw $this->createNotFoundException('Accès non autorisé');
  518.             }
  519.         } else {
  520.             // Pas de SIREN fourni - c'est une offre de société locale, appliquer les restrictions d'origine
  521.             if (!$user) {
  522.                 throw $this->createNotFoundException('Accès non autorisé');
  523.             }
  524.             // Vérifier si l'utilisateur est freelance et essaie de voir un profil qui n'est pas le sien
  525.             if ($user->isFreelance() && $profile->getId() !== $user->getUniqueProfile()->getId()) {
  526.                 return $this->redirectToRoute('dashboard');
  527.             }
  528.         }
  529.         $isOwner false;
  530.         if ($user) {
  531.             $isOwner $profile->getUser()->getId() === $user->getId();
  532.             if ($user->getCompteSociety()) {
  533.                 if ($user->getSociety()->getUser()->getId() === $profile->getUser()->getId()) {
  534.                     $isOwner true;
  535.                 }
  536.             }
  537.         }
  538.         // if ($user->isSociety()) {
  539.         //     $society = $user->getSociety();
  540.         //
  541.         //     $candidatures = $candidatureRepository->findBySocietyAndProfile($society, $profile);
  542.         //
  543.         //     $ProfilesVisits = $profileVisitRepository->findBySociety($society);
  544.         //     $profileIds = [];
  545.         //     foreach ($ProfilesVisits as $visit) {
  546.         //         $profileIds[] = $visit['id'];
  547.         //     }
  548.         //
  549.         //     if (!in_array($profile->getId(), $profileIds)) {
  550.         //         if (!$candidatures && !$society->canViewProfile()) {
  551.         //             $this->addFlash('package_error', "Acheter un package pour voir le profil");
  552.         //
  553.         //             return $this->redirectToRoute('dashboard');
  554.         //         }
  555.         //     }
  556.         //
  557.         //
  558.         //     if ($profile->getUser()->isFreelance() ||
  559.         //         ($profile->getUser()->isSociety() && $society->getId() != $profile->getUser()->getSociety()->getId())
  560.         //     ) {
  561.         //         $profileVisit = new ProfileVisit();
  562.         //         $profileVisit->setSociety($society);
  563.         //         $profileVisit->setProfile($profile);
  564.         //         $profileVisit->setUser($user);
  565.         //         $profileVisit->setViewAt(new DateTimeImmutable());
  566.         //
  567.         //         $entityManager->persist($profileVisit);
  568.         //
  569.         //         if (!$candidatures && !in_array($profile->getId(), $profileIds)) {
  570.         //             $society->decreaseProfileView();
  571.         //         }
  572.         //
  573.         //         $entityManager->flush();
  574.         //     }
  575.         // }
  576.         $experiences $profile->getExperiences()->toArray();
  577.         // Tri par endAt descendant, puis startAt descendant
  578.         usort($experiences, function ($a$b) {
  579.             $aEnd $a->getEndAt();
  580.             $bEnd $b->getEndAt();
  581.             // Si les deux sont null
  582.             if ($aEnd === null && $bEnd === null) {
  583.                 return $b->getStartAt() <=> $a->getStartAt(); // plus récent d'abord
  584.             }
  585.             // Si a est en poste (null), il passe avant b
  586.             if ($aEnd === null) {
  587.                 return -1;
  588.             }
  589.             // Si b est en poste (null), il passe avant a
  590.             if ($bEnd === null) {
  591.                 return 1;
  592.             }
  593.             // Sinon on compare normalement
  594.             if ($aEnd == $bEnd) {
  595.                 return $b->getStartAt() <=> $a->getStartAt();
  596.             }
  597.             return $bEnd <=> $aEnd;
  598.         });
  599.         // maj pour que le profil soit vu (seulement si l'utilisateur est connecté)
  600.         if ($user) {
  601.             $profileVisit = new ProfileVisit();
  602.             $profileVisit->setProfile($profile);
  603.             $profileVisit->setUser($user);
  604.             $profileVisit->setViewAt(new DateTimeImmutable());
  605.             if ($user->getSociety()) {
  606.                 $profileVisit->setSociety($user->getSociety());
  607.             }
  608.             $entityManager->persist($profileVisit);
  609.             $entityManager->flush();
  610.         }
  611.         return $this->render('page/profile.html.twig', [
  612.             'profile' => $profile,
  613.             'experiencesSorted' => $experiences,
  614.             'reportForm' => $this->createForm(ReportFormType::class, new Report())->createView(),
  615.             'reportedId' => $profile->getId(),
  616.             'reportedTarget' => 'profile',
  617.             'isOwner' => $isOwner,
  618.         ]);
  619.     }
  620.     /**
  621.      * @Route("/profil_freelancer/{slug}", name="public_profile_freelancer", methods={"GET"})
  622.      * @IsGranted("ROLE_FREELANCE")
  623.      */
  624.     public function profile_by_freelancer(
  625.         Profile                $profile,
  626.         EntityManagerInterface $entityManager,
  627.         ProfileRepository      $profileRepository,
  628.         ProfileVisitRepository $profileVisitRepository,
  629.         CandidatureRepository  $candidatureRepository,
  630.         OfferRepository        $offerRepository
  631.     ): Response {
  632.         /* @var User $user */
  633.         $user $this->getUser();
  634.         $offers $offerRepository->findBy(['user' => $user]);
  635.         $candidatures_offer $candidatureRepository->findBy(['offer' => $offers]);
  636.         // Récupérer les IDs des profiles dans un tableau
  637.         $profileIds array_map(function ($candidature) {
  638.             return $candidature->getProfile()->getId();  // Récupérer l'ID du profil
  639.         }, $candidatures_offer);
  640.         if ($user->isFreelance() && !in_array($profile->getId(), $profileIds)) {
  641.             return $this->redirectToRoute('dashboard');
  642.         }
  643.         return $this->render('page/profile.html.twig', [
  644.             'profile' => $profile,
  645.             'reportForm' => $this->createForm(ReportFormType::class, new Report())->createView(),
  646.             'reportedId' => $profile->getId(),
  647.             'reportedTarget' => 'profile',
  648.             'isOwner' => false
  649.         ]);
  650.     }
  651.     /**
  652.      * @Route("/email-non-verifie", name="email_unverified", methods={"GET"})
  653.      */
  654.     public function userUnverified(): Response
  655.     {
  656.         $hasVerifiedUser false;
  657.         if ($this->getUser()) {
  658.             $hasVerifiedUser $this->getUser()->isVerified();
  659.         }
  660.         if ($hasVerifiedUser) {
  661.             return $this->redirectToRoute('dashboard');
  662.         } else {
  663.             return $this->render('page/email_must_verified.html.twig');
  664.         }
  665.     }
  666.     /**
  667.      * @Route("/pourquoi-s-inscrire", name="why_subscribe", methods={"GET"})
  668.      */
  669.     public function whySubscribe(): Response
  670.     {
  671.         return $this->render('page/why-subscribe.html.twig');
  672.     }
  673.     /**
  674.      * @Route("/package/checkout", name="payment_validation", methods={"GET"})
  675.      */
  676.     public function paymentValidation(
  677.         Request                $request,
  678.         CreateCommande         $createCommande,
  679.         EntityManagerInterface $entityManager,
  680.         UserRepository         $userRepository
  681.     ): Response {
  682.         if ($request->query->get('stripe') == 'success') {
  683.             $society $this->getUser()->getSociety();
  684.             if (!empty($society) && $request->get('package') == 'year') {
  685.                 $retour_commande $createCommande->execute($request->get('package'), $society, ($request->get('multiple') == 0));
  686.                 if ($retour_commande->getIsPrecommand()) {
  687.                     $user $userRepository->createQueryBuilder('u')
  688.                         ->where('u.subscriptionId IS NOT NULL')
  689.                         ->andWhere('u.email = :email')
  690.                         ->setParameter('email'$this->getUser()->getUserIdentifier()) // Replace $someId with the actual id you want to search for
  691.                         ->setMaxResults(1)
  692.                         ->getQuery()
  693.                         ->getOneOrNullResult();
  694.                     // dd($stripeSubscriptionId);
  695.                     // Configurer l'API Stripe
  696.                     Stripe::setApiKey($_ENV["STRIPE_SECRET"]);
  697.                     if ($user !== null && $user !== '') {
  698.                         $stripeSubscriptionId $user->getSubscriptionId();
  699.                         try {
  700.                             // Annuler l'abonnement
  701.                             $subscription Subscription::retrieve($stripeSubscriptionId);
  702.                             if (!empty($subscription)) {
  703.                                 $subscription->cancel();
  704.                                 // Mettez à jour votre base de données pour refléter l'annulation
  705.                                 $user->setSubscriptionId(null);
  706.                                 $entityManager->flush();
  707.                                 $this->addFlash('success''Subscription cancelled successfully.');
  708.                             }
  709.                         } catch (\Stripe\Exception\ApiErrorException $e) {
  710.                             // Gérer les erreurs de l'API Stripe
  711.                             $this->addFlash('error''Une erreur est survenue lors de l\'annulation.');
  712.                         } catch (\Exception $e) {
  713.                             // Gérer d'autres erreurs possibles
  714.                             $this->addFlash('error''Une erreur est survenue.');
  715.                         }
  716.                     }
  717.                 }
  718.                 /*  Set compte recrutement  */
  719.                 $nbMois $request->query->get("nbMois");
  720.                 if ($nbMois) {
  721.                     $nbMois intval($nbMois);
  722.                     if ($nbMois 0) {
  723.                         $retour_commande->setNbMoisRecruit($nbMois);
  724.                         $entityManager->persist($retour_commande);
  725.                         for ($i 1$i <= $nbMois$i++) {
  726.                             $compteRecrutement = new CompteRecrutement();
  727.                             $compteRecrutement->setSociete($society);
  728.                             $compteRecrutement->setNbMois($nbMois);
  729.                             $compteRecrutement->setCommande($retour_commande);
  730.                             $compteRecrutement->setIsJeton(true);
  731.                             $entityManager->persist($compteRecrutement);
  732.                             $entityManager->flush();
  733.                         }
  734.                     }
  735.                 }
  736.             }
  737.             // Handle success return for monthly subscription checkout
  738.             if (!empty($society) && $request->get('package') == 'month') {
  739.                 // Optionally verify the Checkout Session if session_id was provided in the success URL
  740.                 $sessionId $request->query->get('session_id');
  741.                 if ($sessionId) {
  742.                     try {
  743.                         Stripe::setApiKey($_ENV["STRIPE_SECRET"]);
  744.                         $session \Stripe\Checkout\Session::retrieve($sessionId);
  745.                     } catch (\Exception $e) {
  746.                         // Do not block user if verification fails
  747.                     }
  748.                 }
  749.                 // Avoid duplicate creation if webhook already created it
  750.                 $lastMonthCommande $entityManager->getRepository(\App\Entity\Commande::class)
  751.                     ->findOneBy(['society' => $society'package' => 'month'], ['id' => 'DESC']);
  752.                 $shouldCreate true;
  753.                 if ($lastMonthCommande && $lastMonthCommande->getCreatedAt()) {
  754.                     $tenMinutesAgo = new DateTimeImmutable('-10 minutes');
  755.                     if ($lastMonthCommande->getCreatedAt() >= $tenMinutesAgo) {
  756.                         $shouldCreate false;
  757.                     }
  758.                 }
  759.                 if ($shouldCreate) {
  760.                     $createCommande->execute('month'$society, ($request->get('multiple') == 0));
  761.                 }
  762.             }
  763.             $this->addFlash('success_payement''Paiement validé.');
  764.             return $this->redirectToRoute('commande_list');
  765.         } else {
  766.             $this->addFlash('error''Une erreur s\'est produite lors de votre paiement.');
  767.             return $this->redirectToRoute('checkout_package', ['name' => $request->query->get('package')]);
  768.         }
  769.     }
  770.     /**
  771.      * @Route("/mail/send/reactivation", name="mail_s_sendReactivation")
  772.      *
  773.      *
  774.      *  ENVOYE DE MAIL DE REACTIVATION
  775.      */
  776.     public function sendReactivation(
  777.         EntityManagerInterface $entityManager,
  778.         SocietyRepository      $societyRepository,
  779.         MailerInterface        $mailer,
  780.         TempMailService        $tempMailService
  781.     ): Response {
  782.         $societies $societyRepository->findExpiredPauseDate();
  783.         $i 0;
  784.         foreach ($societies as $society) {
  785.             $society->setPauseDate(null);
  786.             $entityManager->persist($society);
  787.             $i++;
  788.             $entityManager->flush();
  789.             // SEND MAIL
  790.             $tempMail = new TempMail();
  791.             $tempMail->setType_SocietyReactivation();
  792.             $tempMail->setAdress($society->getUser()->getEmail());
  793.             $tempMail->setSociety($society);
  794.             $tempMailService->sendingMail($tempMail);
  795.         }
  796.         return new Response($i ' sociétés réactivées');
  797.     }
  798.     /**
  799.      * @Route("/stripe/webhook", name="webhook_stripe")
  800.      */
  801.     public function webhook(
  802.         Request                     $request,
  803.         CreateCommande              $createCommande,
  804.         \App\UseCase\Package\Config $config,
  805.         HttpClientInterface         $client,
  806.         SocietyRepository           $societyRepository,
  807.         UserRepository              $userRepository,
  808.         EntityManagerInterface      $em
  809.     ) {
  810.         // Debug log temporaire
  811.         file_put_contents('webhook_debug.log'date('Y-m-d H:i:s') . ' - Webhook called' PHP_EOLFILE_APPEND);
  812.         \Stripe\Stripe::setApiKey($_ENV["STRIPE_SECRET"]);
  813.         // This is your Stripe CLI webhook secret for testing your endpoint locally.
  814.         $endpoint_secret $_ENV["STRIPE_WEBHOOK_SIGNATURE"];
  815.         // Debug: log le secret utilisé
  816.         file_put_contents('webhook_debug.log'date('Y-m-d H:i:s') . ' - Secret: ' substr($endpoint_secret010) . '...' PHP_EOLFILE_APPEND);
  817.         $payload = @file_get_contents('php://input');
  818.         $sig_header $request->headers->get('stripe-signature');
  819.         // Debug: log les headers
  820.         file_put_contents('webhook_debug.log'date('Y-m-d H:i:s') . ' - Sig header: ' . ($sig_header 'present' 'missing') . PHP_EOLFILE_APPEND);
  821.         $event null;
  822.         try {
  823.             $event \Stripe\Webhook::constructEvent(
  824.                 $payload,
  825.                 $sig_header,
  826.                 $endpoint_secret
  827.             );
  828.             file_put_contents('webhook_debug.log'date('Y-m-d H:i:s') . ' - Event constructed successfully: ' $event->type PHP_EOLFILE_APPEND);
  829.         } catch (\UnexpectedValueException $e) {
  830.             // Invalid payload
  831.             file_put_contents('webhook_debug.log'date('Y-m-d H:i:s') . ' - UnexpectedValueException: ' $e->getMessage() . PHP_EOLFILE_APPEND);
  832.             http_response_code(400);
  833.             exit();
  834.         } catch (\Stripe\Exception\SignatureVerificationException $e) {
  835.             // Invalid signature
  836.             file_put_contents('webhook_debug.log'date('Y-m-d H:i:s') . ' - SignatureVerificationException: ' $e->getMessage() . PHP_EOLFILE_APPEND);
  837.             http_response_code(400);
  838.             exit();
  839.         }
  840.         switch ($event->type) {
  841.             case 'checkout.session.completed':
  842.                 $invoice $event->data->object// contains a \Stripe\Invoice
  843.                 $metadata $invoice->metadata;
  844.                 $userId $metadata->user_id;
  845.                 $subscriptionId $invoice->subscription;
  846.                 if ($subscriptionId == null or $subscriptionId == '') {
  847.                     return $this->json(['error' => 'subscription not found']);
  848.                 }
  849.                 $user $userRepository->find($userId);
  850.                 $society $user->getSociety();
  851.                 if (!empty($society)) {
  852.                     $user->setSubscriptionId($subscriptionId);
  853.                     $em->persist($user);
  854.                     $em->flush();
  855.                     $retour_commande $createCommande->execute($metadata->package$society0);
  856.                     return new Response('Success'200);
  857.                 }
  858.                 break;
  859.             case 'invoice.payment_succeeded':
  860.                 $invoice $event->data->object// contains a \Stripe\Invoice
  861.                 try {
  862.                     $subscription \Stripe\Subscription::retrieve($invoice->subscription);
  863.                     if ($subscription->subscription == null or $subscription->subscription == '') {
  864.                         return $this->json(['error' => 'subscription not found']);
  865.                     }
  866.                     // Vérifiez que les métadonnées existent et ne sont pas vides
  867.                     $user $userRepository->findOneBy(['subscriptionId' => $subscription->subscription]);
  868.                     if ($user !== null && $user !== '') {
  869.                         $society $user->getSociety();
  870.                         if (!empty($society)) {
  871.                             $retour_commande $createCommande->execute($society->getPackage(), $society0);
  872.                             // Mettez à jour votre base de données ici selon vos besoins
  873.                             return new Response('Success'200);
  874.                         }
  875.                     } else {
  876.                         // Gérer le cas où les métadonnées sont absentes ou invalides
  877.                         return $this->json(['error' => 'User not found']);
  878.                     }
  879.                 } catch (\Stripe\Exception\ApiErrorException $e) {
  880.                     // Handle Stripe API error
  881.                     return $this->json(['error' => 'Stripe API error'], 404);
  882.                 } catch (\Exception $e) {
  883.                     // Handle other errors
  884.                     return $this->json(['error' => 'An unexpected error occurred'], 404);
  885.                 }
  886.                 // // Récupérer le répertoire d'images depuis les paramètres
  887.                 // $directory = $this->getParameter('img_directory');
  888.                 //
  889.                 // // Définir le nom du fichier
  890.                 // $filename = $directory . '/test2.completed';
  891.                 // file_put_contents($filename, $subscription);
  892.                 break;
  893.             // Ajouter d'autres cas selon vos besoins
  894.             default:
  895.                 return new Response('Unhandled event type'400);
  896.         }
  897.     }
  898.     /**
  899.      * @Route("/{slug}{trailingSlash}", name="public_page", methods={"GET"}, requirements={"slug"="^(?!formation$|formation/).+?", "trailingSlash"="/?"})
  900.      */
  901.     public function page(string $slugPublicationRepository $publicationRepositoryEntityManagerInterface $entityManager): Response
  902.     {
  903.         if ($slug == 'societe' || $slug == 'formation') {
  904.             return $this->render('page/error.html.twig');
  905.         }
  906.         $publication $publicationRepository->findOneBy(['slug' => $slug]);
  907.         if (null === $publication) {
  908.             throw $this->createNotFoundException();
  909.         }
  910.         $publication->setView($publication->getView() + 1);
  911.         $entityManager->persist($publication);
  912.         $entityManager->flush();
  913.         return $this->render('page/page.html.twig', [
  914.             'publication' => $publication,
  915.         ]);
  916.     }
  917. }