src/Controller/ThemesWebsite/Whileresume/Website/CandidatesController.php line 85

Open in your IDE?
  1. <?php
  2. namespace App\Controller\ThemesWebsite\Whileresume\Website;
  3. use App\Entity\Articles\Articles;
  4. use App\Entity\Core\Users;
  5. use App\Entity\Cvs\Candidates;
  6. use App\Entity\Cvs\Jobs;
  7. use App\Entity\Cvs\JobsHasLikes;
  8. use App\Entity\Cvs\LandingJobs;
  9. use App\Entity\Pages\Pages;
  10. use App\Form\Core\UsersEmailForm;
  11. use App\Security\LoginFormAuthenticator;
  12. use App\Services\Core\RequestData;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use Knp\Component\Pager\PaginatorInterface;
  15. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
  16. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  17. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  18. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  19. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  20. use Symfony\Component\HttpFoundation\Cookie;
  21. use Symfony\Component\HttpFoundation\JsonResponse;
  22. use Symfony\Component\HttpFoundation\RedirectResponse;
  23. use Symfony\Component\HttpFoundation\Request;
  24. use Symfony\Component\HttpFoundation\Response;
  25. use Symfony\Component\Routing\Annotation\Route;
  26. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  27. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  28. use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
  29. use Symfony\Component\Security\Core\User\UserInterface;
  30. use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
  31. use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
  32. use Symfony\Component\Security\Http\Authentication\AuthenticatorManagerInterface;
  33. use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
  34. use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
  35. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordCredentialsBadge;
  36. use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
  37. use Twig\Environment;
  38. class CandidatesController extends AbstractController
  39. {
  40.     private $rd;
  41.     private $em;
  42.     private $passwordEncoder;
  43.     private $ms;
  44.     private $us;
  45.     private $authenticator;
  46.     private $userAuthenticator;
  47.     private $paginator;
  48.     private $twig;
  49.     public function __construct(RequestData                  $rd,
  50.                                 EntityManagerInterface       $em,
  51.                                 UserPasswordEncoderInterface $passwordEncoder,
  52.                                 \App\Services\Mails          $ms,
  53.                                 \App\Services\Core\Users     $us,
  54.                                 UserAuthenticatorInterface   $userAuthenticator,
  55.                                 LoginFormAuthenticator       $authenticator,
  56.                                 PaginatorInterface           $paginator,
  57.                                 Environment $twig,
  58.     ) {
  59.         $this->rd $rd;
  60.         $this->em $em;
  61.         $this->passwordEncoder $passwordEncoder;
  62.         $this->ms $ms;
  63.         $this->authenticator $authenticator;
  64.         $this->userAuthenticator $userAuthenticator;
  65.         $this->us $us;
  66.         $this->paginator $paginator;
  67.         $this->twig $twig;
  68.     }
  69.     /**
  70.      * Homepage principal
  71.      * @param Request $request
  72.      * @return Response
  73.      */
  74.     public function homepage(Request $request): Response
  75.     {
  76.         $locale $request->getLocale();
  77.         $user $this->getUser();
  78.         $page $this->em->getRepository(Pages::class)->findOneBy(['name' => 'homepage''locale' => $locale]);
  79.         $articles $this->em->getRepository(Articles::class)->getArticles(6$locale);
  80.         // Données pour utilisateur connecté
  81.         $likedJobIds = [];
  82.         $totalJobs 0;
  83.         if ($user !== null) {
  84.             $totalJobs $this->em->getRepository(Jobs::class)->countSearchJobs(''$locale);
  85.             // Récupérer les IDs des offres likées par le candidat
  86.             if (method_exists($user'getCandidate') && $user->getCandidate() !== null) {
  87.                 $likes $this->em->getRepository(JobsHasLikes::class)->findBy(['candidate' => $user->getCandidate()]);
  88.                 foreach ($likes as $like) {
  89.                     if ($like->getJob() !== null) {
  90.                         $likedJobIds[] = $like->getJob()->getId();
  91.                     }
  92.                 }
  93.             }
  94.         }
  95.         return $this->render('application/whileresume/website/candidates/homepage_' $locale '.html.twig', [
  96.             'page' => $page,
  97.             'articles' => $articles,
  98.             'likedJobIds' => $likedJobIds,
  99.             'totalJobs' => $totalJobs,
  100.         ]);
  101.     }
  102.     /**
  103.      * API : Recherche d'offres (AJAX)
  104.      */
  105.     public function searchJobsApi(Request $request): JsonResponse
  106.     {
  107.         $locale $request->getLocale();
  108.         $query trim($request->query->get('q'''));
  109.         $offset max(0, (int) $request->query->get('offset'0));
  110.         $limit min(20max(1, (int) $request->query->get('limit'10)));
  111.         $jobsRepo $this->em->getRepository(Jobs::class);
  112.         $jobs $jobsRepo->searchJobs($query$locale$offset$limit);
  113.         $total $jobsRepo->countSearchJobs($query$locale);
  114.         // Récupérer les likes du candidat connecté
  115.         $likedJobIds = [];
  116.         $user $this->getUser();
  117.         if ($user !== null && method_exists($user'getCandidate') && $user->getCandidate() !== null) {
  118.             $likes $this->em->getRepository(JobsHasLikes::class)->findBy(['candidate' => $user->getCandidate()]);
  119.             foreach ($likes as $like) {
  120.                 if ($like->getJob() !== null) {
  121.                     $likedJobIds[] = $like->getJob()->getId();
  122.                 }
  123.             }
  124.         }
  125.         $results = [];
  126.         foreach ($jobs as $job) {
  127.             $results[] = $this->serializeJob($job$likedJobIds);
  128.         }
  129.         return new JsonResponse([
  130.             'jobs' => $results,
  131.             'total' => $total,
  132.             'offset' => $offset,
  133.             'limit' => $limit,
  134.             'hasMore' => ($offset $limit) < $total,
  135.         ]);
  136.     }
  137.     /**
  138.      * API : Like / Unlike une offre (AJAX)
  139.      */
  140.     public function toggleJobLikeApi(Request $request): JsonResponse
  141.     {
  142.         $user $this->getUser();
  143.         if ($user === null) {
  144.             return new JsonResponse(['error' => 'Vous devez être connecté.'], 401);
  145.         }
  146.         $candidate $user->getCandidate();
  147.         if ($candidate === null) {
  148.             return new JsonResponse(['error' => 'Vous devez être candidat pour liker une offre.'], 403);
  149.         }
  150.         $data json_decode($request->getContent(), true);
  151.         $jobId $data['jobId'] ?? null;
  152.         if ($jobId === null) {
  153.             return new JsonResponse(['error' => 'ID de l\'offre manquant.'], 400);
  154.         }
  155.         $job $this->em->getRepository(Jobs::class)->find($jobId);
  156.         if ($job === null) {
  157.             return new JsonResponse(['error' => 'Offre introuvable.'], 404);
  158.         }
  159.         // Vérifier si le like existe déjà
  160.         $existingLike $this->em->getRepository(JobsHasLikes::class)->findOneBy([
  161.             'job' => $job,
  162.             'candidate' => $candidate,
  163.         ]);
  164.         if ($existingLike !== null) {
  165.             // Unlike
  166.             $this->em->remove($existingLike);
  167.             $this->em->flush();
  168.             return new JsonResponse(['liked' => false'jobId' => $jobId]);
  169.         }
  170.         // Like
  171.         $like = new JobsHasLikes();
  172.         $like->setJob($job);
  173.         $like->setCandidate($candidate);
  174.         $this->em->persist($like);
  175.         $this->em->flush();
  176.         return new JsonResponse(['liked' => true'jobId' => $jobId]);
  177.     }
  178.     // ======================================================================
  179.     // HELPERS
  180.     // ======================================================================
  181.     /**
  182.      * Sérialise un Job en tableau pour la réponse JSON
  183.      */
  184.     private function serializeJob(Jobs $job, array $likedJobIds): array
  185.     {
  186.         $now = new \DateTime();
  187.         $diffDays $job->getCreatedAt() ? $now->diff($job->getCreatedAt())->days 999;
  188.         // Image du job (Vich uploader) — adapter le chemin si nécessaire
  189.         $imageUrl null;
  190.         if ($job->getImage() && $job->getImage()->getName()) {
  191.             $imageUrl '/uploads/cv_files/' $job->getImage()->getName();
  192.         }
  193.         return [
  194.             'id' => $job->getId(),
  195.             'jobTitle' => $job->getJobTitle(),
  196.             'companyName' => $job->getCompanyName() ?? 'Entreprise',
  197.             'city' => $job->getCity() ?? '',
  198.             'employmentType' => $job->getEmploymentType() ?? 'CDI',
  199.             'experienceLevel' => $job->getExperienceLevel() ?? '',
  200.             'remoteWork' => $job->getRemoteWork() ?? '',
  201.             'salary' => $this->formatSalary($job->getSalaryMin(), $job->getSalaryMax(), $job->getDevise()),
  202.             'slug' => $job->getSlug(),
  203.             'isNew' => $diffDays <= 7,
  204.             'isLiked' => in_array($job->getId(), $likedJobIds),
  205.             'image' => $imageUrl,
  206.             'logo' => $job->getCompanyName() ? mb_strtoupper(mb_substr($job->getCompanyName(), 01)) : '?',
  207.             'category' => $job->getCategory() ?? '',
  208.         ];
  209.     }
  210.     /**
  211.      * Formate le salaire en string lisible : "55-70k€"
  212.      */
  213.     private function formatSalary(?int $min, ?int $max, ?string $devise): string
  214.     {
  215.         $symbol = ($devise === 'USD' || $devise === '$') ? '$' '€';
  216.         if ($min && $max) {
  217.             return floor($min 1000) . '-' floor($max 1000) . 'k' $symbol;
  218.         }
  219.         if ($min) {
  220.             return floor($min 1000) . 'k' $symbol '+';
  221.         }
  222.         if ($max) {
  223.             return '< ' floor($max 1000) . 'k' $symbol;
  224.         }
  225.         return '';
  226.     }
  227.     // ======================================================================
  228.     // MÉTHODES EXISTANTES (inchangées)
  229.     // ======================================================================
  230.     public function homepageLanding(Request $request$slug): Response
  231.     {
  232.         $locale $request->getLocale();
  233.         $landing $this->em->getRepository(LandingJobs::class)->findOneBy(['type' => 'candidates''locale' => $locale]);
  234.         if($landing === null) {
  235.             throw new \RuntimeException('No exist');
  236.         }
  237.         $articles $this->em->getRepository(Articles::class)->getArticles(6,$locale);
  238.         return $this->render('application/whileresume/website/candidates/landing_'.$locale.'.html.twig',[
  239.             'landing' => $landing,
  240.             'articles' => $articles
  241.         ]);
  242.     }
  243.     /**
  244.      * Déposer un CV
  245.      * @param Request $request
  246.      * @return Response
  247.      */
  248.     public function resume(Request $request): Response
  249.     {
  250.         $session $request->getSession();
  251.         $locale $request->getLocale();
  252.         $user $this->getUser();
  253.         $page $this->em->getRepository(Pages::class)->findOneBy(['name' => 'resume''locale' => $locale]);
  254.         $form $this->createForm(UsersEmailForm::class);
  255.         $form->handleRequest($request);
  256.         if ($form->isSubmitted() && $form->isValid()) {
  257.             $data $request->request->all();
  258.             $data $data['users_email_form'];
  259.             if($data['acceptTerm'] == "1") {
  260.                 if($data['password']['first'] != $data['password']['second']) {
  261.                     if($locale == "fr") {
  262.                         $session->getFlashBag()->add('danger''Votre second mot de passe n\'est pas identique');
  263.                         return $this->redirectToRoute('whileresume_resume_fr');
  264.                     }
  265.                     $session->getFlashBag()->add('danger''Your second password is not identical');
  266.                     return $this->redirectToRoute('whileresume_resume_en');
  267.                 }
  268.                 $verificationUser $this->em->getRepository(Users::class)->findOneBy(['email' => $data['email']]);
  269.                 if ($verificationUser == null) {
  270.                     $candidate = new Candidates();
  271.                     $candidate->setEmail($data['email']);
  272.                     $candidate->setUpdatedAt(new \DateTime("now"));
  273.                     $candidate->setCreatedAt(new \DateTime("now"));
  274.                     $candidate->setOnline(false);
  275.                     $candidate->setAvailability("offline");
  276.                     $this->em->persist($candidate);
  277.                     $this->em->flush();
  278.                     $us1 $this->generateUniqueSlug($candidate->getId(),10);
  279.                     $us2 $this->generateUniqueSlug($candidate->getId(),10);
  280.                     $candidate->setSlug($us1);
  281.                     $candidate->setSlugAnonyme($us2);
  282.                     $this->em->persist($candidate);
  283.                     $this->em->flush();
  284.                     $newUser = new Users();
  285.                     $newUser->setEmail($data['email']);
  286.                     $newUser->setUsername("");
  287.                     $newUser->setVerification(false);
  288.                     $newUser->setNotificationsMessages(true);
  289.                     $newUser->setNotificationsSuivis(true);
  290.                     $newUser->setPremium(false);
  291.                     $newUser->setFirst(true);
  292.                     $newUser->setEnabled(true);
  293.                     $newUser->setPassword($this->passwordEncoder->encodePassword($newUser,$data['password']['first']));
  294.                     $newUser->setRoles(['ROLE_USER']);
  295.                     $newUser->setTypeAccount("candidate");
  296.                     $newUser->setUpdatedAt(new \DateTime("now"));
  297.                     $newUser->setCreatedAt(new \DateTime("now"));
  298.                     $newUser->setCandidate($candidate);
  299.                     $this->em->persist($newUser);
  300.                     $this->em->flush();
  301.                     $title "Find your next tech talents in just a few minutes!";
  302.                     if($locale == "fr") {
  303.                         $title "🎉 Bienvenue ! Votre profil peut déjà attirer des recruteurs !";
  304.                     }
  305.                     $descriptionHTML $this->twig->render("application/whileresume/gestion/emails/"$locale ."/register_candidate.html.twig",[
  306.                         'title' => $title,
  307.                         'email' => $data['email']
  308.                     ]);
  309.                     $this->ms->webhook($title,$descriptionHTMLnull$data['email'], nullnull);
  310.                     $this->userAuthenticator->authenticateUser($newUser$this->authenticator$request);
  311.                     if($locale == "en") {
  312.                         return $this->redirectToRoute('customer_homepage');
  313.                     }
  314.                     return $this->redirectToRoute('locale_customer_homepage',['_locale' => $locale]);
  315.                 }
  316.                 if($locale == "fr") {
  317.                     return $this->redirectToRoute('whileresume_resume_fr');
  318.                 }
  319.                 return $this->redirectToRoute('whileresume_resume_en');
  320.             }
  321.         }
  322.         return $this->render('application/whileresume/website/candidates/resume_'.$locale.'.html.twig',[
  323.             'form' => $form->createView(),
  324.             'page' => $page
  325.         ]);
  326.     }
  327.     private function generateUniqueSlug(int $userIdint $length 8): string
  328.     {
  329.         $characters '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  330.         $slug '';
  331.         for ($i 0$i $length$i++) {
  332.             $slug .= $characters[random_int(0strlen($characters) - 1)];
  333.         }
  334.         return $userId '-' $slug;
  335.     }
  336. }