<?phpnamespace App\Controller\ThemesWebsite\Whileresume\Application;use App\Entity\Core\Users;use App\Entity\Cvs\Candidates;use App\Entity\Cvs\CandidatesHasExperiences;use App\Entity\Cvs\CandidatesHasProjects;use App\Entity\Cvs\CandidatesHasServices;use App\Entity\Cvs\CandidatesHasSkills;use App\Entity\Cvs\Jobs;use App\Entity\Cvs\JobsHasLikes;use App\Form\Core\UsersType;use App\Form\Cvs\JobsForm;use App\Form\Cvs\JobsFrForm;use App\Services\Core\RequestData;use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;use Symfony\Component\EventDispatcher\EventDispatcherInterface;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Annotation\Route;use Symfony\Component\HttpFoundation\Cookie;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;use Doctrine\ORM\EntityManagerInterface;class JobsController extends AbstractController{ private $rd; private $em; public function __construct(RequestData $rd, EntityManagerInterface $em ) { $this->rd = $rd; $this->em = $em; } public function new(Request $request): Response { $locale = $request->getLocale(); $user = $this->getUser(); $jobClass = JobsForm::class; if($locale == "fr") { $jobClass = JobsFrForm::class; } $job = new Jobs(); $form = $this->createForm($jobClass, $job); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $job->setSlug("0"); $job->setLocale($locale); $job->setValidation(false); $job->setVerification(false); $job->setOnline(false); $job->setEnterprise(null); $job->setUser($user); $this->em->persist($job); $this->em->flush(); if($job->getSlug() != "0") { $job->setSlug($this->generateSlug($job->getId())); $this->em->persist($job); $this->em->flush(); } if($locale !== "en") { return $this->redirectToRoute('locale_cvs_application_job_new_confirm',['_locale' => $locale]); } return $this->redirectToRoute('cvs_application_job_new_confirm'); } return $this->render('application/whileresume/application/jobs/new_'.$locale.'.html.twig',[ 'form' => $form->createView(), ]); } private function generateSlug(int $id): string { $letters = 'abcdefghijklmnopqrstuvwxyz'; $random = ''; for ($i = 0; $i < 6; $i++) { $random .= $letters[random_int(0, 25)]; } return $random . '-' . $id; } public function show(Request $request, $slug): Response { $locale = $request->getLocale(); $jobsRepo = $this->em->getRepository(Jobs::class); $job = $jobsRepo->findOneBy(['locale' => $locale, 'slug' => $slug, 'online' => true, 'validation' => true]); if($job === null) { throw $this->createNotFoundException('Page non trouvée'); } $othersJobs = null; if($job->getEnterprise() != null) { $othersJobs = $jobsRepo->findBy(['enterprise' => $job->getEnterprise(), 'online' => true]); } // Offres similaires basées sur catégorie, compétences, ville, titre $similarJobs = $jobsRepo->findSimilarJobs($job, 5); // Filtres sidebar : tags polymorphes (villes + métiers) configurés en BDD $filtersRepo = $this->em->getRepository(\App\Entity\Cvs\JobsFilters::class); $sidebarFilters = $filtersRepo->findActiveByLocale($locale, 30); // Séparer par type pour l'affichage (city vs category) $sidebarFiltersCities = []; $sidebarFiltersCategories = []; foreach ($sidebarFilters as $f) { if ($f->getType() === 'city') { $sidebarFiltersCities[] = $f; } else { $sidebarFiltersCategories[] = $f; } } // Keywords cliquables sous la barre de recherche (homepage de la locale) $keywordsRepo = $this->em->getRepository(\App\Entity\Cvs\KeywordsLandingJobs::class); $searchKeywords = $keywordsRepo->findByLocaleForHomepage($locale); $count = 0; if($job->getViews() != null) { $count = $job->getViews() + 1; } $job->setViews($count); $this->em->persist($job); $this->em->flush(); return $this->render('application/whileresume/application/jobs/show.html.twig',[ 'slug' => $slug, 'job' => $job, 'othersJobs' => $othersJobs, 'similarJobs' => $similarJobs, 'sidebarFiltersCities' => $sidebarFiltersCities, 'sidebarFiltersCategories' => $sidebarFiltersCategories, 'searchKeywords' => $searchKeywords, ]); } /** * Retourne les détails d'une offre en JSON pour la fiche modale (AJAX). * Si l'utilisateur n'est pas connecté, retourne uniquement les champs publics * (résumé + meta) avec un flag "locked" sur les détails. */ public function ajaxShow(Request $request, $slug): JsonResponse { $locale = $request->getLocale(); $user = $this->getUser(); $job = $this->em->getRepository(Jobs::class)->findOneBy([ 'locale' => $locale, 'slug' => $slug, 'online' => true, 'validation' => true ]); if ($job === null) { return new JsonResponse(['error' => 'not_found'], 404); } $isAuthenticated = $user !== null; $isCandidate = $isAuthenticated && $user->getCandidate() !== null; $enterpriseData = null; if ($job->getEnterprise() !== null) { $enterpriseData = [ 'id' => $job->getEnterprise()->getId(), 'slug' => $job->getEnterprise()->getSlug(), 'name' => $job->getEnterprise()->getCompanyName(), 'logo' => method_exists($job->getEnterprise(), 'getLogo') ? $job->getEnterprise()->getLogo() : null, 'city' => method_exists($job->getEnterprise(), 'getCity') ? $job->getEnterprise()->getCity() : null, 'country' => method_exists($job->getEnterprise(), 'getCountry') ? $job->getEnterprise()->getCountry() : null, ]; } // Données publiques (toujours visibles) $payload = [ 'id' => $job->getId(), 'slug' => $job->getSlug(), 'title' => $job->getJobTitle(), 'companyName' => $job->getCompanyName(), 'category' => $job->getCategory(), 'city' => $job->getCity(), 'country' => $job->getCountry(), 'employmentType' => $job->getEmploymentType(), 'remoteWork' => $job->getRemoteWork(), 'experienceLevel' => $job->getExperienceLevel(), 'salaryMin' => $job->getSalaryMin(), 'salaryMax' => $job->getSalaryMax(), 'salaryPeriod' => $job->getSalaryPeriod(), 'devise' => $job->getDevise(), 'jobSummary' => $job->getJobSummary(), 'verification' => $job->getVerification(), 'website' => method_exists($job, 'getWebsite') ? $job->getWebsite() : null, 'websearch' => method_exists($job, 'getWebsearch') ? $job->getWebsearch() : null, 'enterprise' => $enterpriseData, 'isAuthenticated' => $isAuthenticated, 'isCandidate' => $isCandidate, 'locked' => !$isAuthenticated, 'showUrl' => $this->generateUrl('locale_cvs_application_job_show', [ '_locale' => $locale, 'slug' => $slug ]), 'likeUrl' => null, ]; // Données détaillées (uniquement si connecté) if ($isAuthenticated) { $payload['keyResponsabilities'] = $job->getKeyResponsabilities(); $payload['requirements'] = $job->getRequirements(); $payload['benefits'] = $job->getBenefits(); if ($isCandidate) { $payload['likeUrl'] = $this->generateUrl('locale_cvs_application_job_like', [ '_locale' => $locale, 'slug' => $slug ]); } } return new JsonResponse($payload); } /** * Page landing pour un filtre (ville/métier/etc.) configuré dans cvs_jobsfilters. * URL : /{locale}/jobs/{slug} → ex: /fr/jobs/paris ou /fr/jobs/developpement-web * * Affiche la 1ère offre matchant le filtre via la page show classique, ou un 404 * si le slug n'existe pas dans cvs_jobsfilters. */ public function filterLanding(Request $request, $slug): Response { $locale = $request->getLocale(); $filtersRepo = $this->em->getRepository(\App\Entity\Cvs\JobsFilters::class); $filter = $filtersRepo->findOneBySlugAndLocale($slug, $locale); if ($filter === null) { throw $this->createNotFoundException('Filtre introuvable'); } // Recherche : on prend le 1er job qui matche le searchKeyword $jobsRepo = $this->em->getRepository(Jobs::class); $keyword = $filter->getSearchKeyword() ?: $filter->getLabel(); // searchJobs cherche dans jobTitle / companyName / category / city / requiredSkills $matchedJobs = $jobsRepo->searchJobs($keyword, $locale, 0, 6); if (empty($matchedJobs)) { // Aucune offre : on rend la page filterLanding "vide" return $this->render('application/whileresume/application/jobs/filter_landing.html.twig', [ 'filter' => $filter, 'job' => null, 'similarJobs' => [], ]); } // On prend la 1ère offre comme offre "principale" et les suivantes comme similaires $job = array_shift($matchedJobs); $similarJobs = $matchedJobs; // Filtres sidebar $sidebarFilters = $filtersRepo->findActiveByLocale($locale, 30); $sidebarFiltersCities = []; $sidebarFiltersCategories = []; foreach ($sidebarFilters as $f) { if ($f->getType() === 'city') { $sidebarFiltersCities[] = $f; } else { $sidebarFiltersCategories[] = $f; } } $keywordsRepo = $this->em->getRepository(\App\Entity\Cvs\KeywordsLandingJobs::class); $searchKeywords = $keywordsRepo->findByLocaleForHomepage($locale); return $this->render('application/whileresume/application/jobs/show.html.twig', [ 'slug' => $job->getSlug(), 'job' => $job, 'othersJobs' => null, 'similarJobs' => $similarJobs, 'sidebarFiltersCities' => $sidebarFiltersCities, 'sidebarFiltersCategories' => $sidebarFiltersCategories, 'searchKeywords' => $searchKeywords, 'currentFilter' => $filter, // pour le SEO / titre / breadcrumb ]); } /** * API publique de recherche d'offres pour l'autocomplétion. * GET /api/jobs/search?q=...&locale=...&limit=... */ public function apiSearch(Request $request): JsonResponse { $query = trim((string) $request->query->get('q', '')); $locale = $request->query->get('locale', $request->getLocale()); $limit = max(1, min(20, (int) $request->query->get('limit', 6))); $offset = max(0, (int) $request->query->get('offset', 0)); if (mb_strlen($query) < 2) { return new JsonResponse([ 'items' => [], 'total' => 0, 'query' => $query, ]); } $repo = $this->em->getRepository(Jobs::class); $jobs = $repo->searchJobs($query, $locale, $offset, $limit); $total = $repo->countSearchJobs($query, $locale); $items = []; foreach ($jobs as $job) { $logo = null; if ($job->getEnterprise() !== null && method_exists($job->getEnterprise(), 'getLogo')) { $logo = $job->getEnterprise()->getLogo(); } $items[] = [ 'id' => $job->getId(), 'slug' => $job->getSlug(), 'title' => $job->getJobTitle(), 'companyName' => $job->getCompanyName(), 'category' => $job->getCategory(), 'city' => $job->getCity(), 'country' => $job->getCountry(), 'employmentType' => $job->getEmploymentType(), 'salaryMin' => $job->getSalaryMin(), 'salaryMax' => $job->getSalaryMax(), 'devise' => $job->getDevise(), 'logo' => $logo, 'url' => $this->generateUrl('locale_cvs_application_job_show', [ '_locale' => $locale, 'slug' => $job->getSlug(), ]), ]; } return new JsonResponse([ 'items' => $items, 'total' => $total, 'query' => $query, 'limit' => $limit, 'offset' => $offset, ]); } public function likeJob(Request $request, $slug): Response { $locale = $request->getLocale(); $user = $this->getUser(); if($user == null) { if($locale != "en") { return $this->redirectToRoute('locale_cvs_gestion_candidates_dashboard',['_locale' => $locale]); } return $this->redirectToRoute('cvs_gestion_candidates_dashboard'); } $candidate = $user->getCandidate(); $job = $this->em->getRepository(Jobs::class)->findOneBy(['slug' => $slug]); $jhl = $this->em->getRepository(JobsHasLikes::class)->findOneBy(['candidate' => $candidate, 'job' => $job]); if($jhl == null) { $jhl = new JobsHasLikes(); $jhl->setJob($job); $jhl->setCandidate($candidate); $jhl->setCreatedAt(new \DateTime("now")); $jhl->setUpdatedAt(new \DateTime("now")); $this->em->persist($jhl); $this->em->flush(); } if($locale != "en") { return $this->redirectToRoute('locale_cvs_gestion_candidates_dashboard',['_locale' => $locale]); } return $this->redirectToRoute('cvs_gestion_candidates_dashboard'); }}