<?phpnamespace App\Controller\Api;use App\Entity\Core\AccountDeleted;use App\Entity\Core\Agencies;use App\Entity\Core\AgenciesHasUsers;use App\Entity\Core\MobileIdentifiers;use App\Entity\Cvs\CandidatesHasComments;use App\Entity\Cvs\CandidatesHasLikes;use App\Entity\Cvs\CandidatesHasStatistics;use App\Entity\Messenger\Groups;use App\Entity\Messenger\Messages;use App\Services\Dossiers;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\JsonResponse;use Doctrine\ORM\EntityManagerInterface;use Knp\Component\Pager\PaginatorInterface;use Symfony\Component\Form\Extension\Core\Type\CheckboxType;use Symfony\Component\Form\Extension\Core\Type\ChoiceType;use Symfony\Component\Form\Extension\Core\Type\DateTimeType;use Symfony\Component\Form\Extension\Core\Type\EmailType;use Symfony\Component\Form\Extension\Core\Type\HiddenType;use Symfony\Component\Form\Extension\Core\Type\NumberType;use Symfony\Component\Form\Extension\Core\Type\SubmitType;use Symfony\Component\Form\Extension\Core\Type\TextareaType;use Symfony\Component\Form\Extension\Core\Type\TextType;use Symfony\Component\HttpFoundation\Session\Session;use Symfony\Component\HttpFoundation\File\File;use Symfony\Component\HttpFoundation\File\UploadedFile;use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;class ProfileController extends AbstractController{ private $em; private $paginator; private $dossier; private $passwordEncoder; public function __construct(EntityManagerInterface $em, Dossiers $dossier, PaginatorInterface $paginator, UserPasswordEncoderInterface $passwordEncoder, ) { $this->em = $em; $this->paginator = $paginator; $this->dossier = $dossier; $this->passwordEncoder = $passwordEncoder; } /** * Informations de l'utilisateur. * @param Request $request * @return JsonResponse */ public function info(Request $request) { $user = $this->getUser(); $language = "en"; if($user->getLanguage() !== null) { $language = $user->getLanguage(); } $avatar = ""; if($user->getImage() !== null && $user->getImage()->getName() !== null) { $avatar = $user->getImageBase64(); } // ════════════════════════════════════════════════════════════════ // RECRUTEUR — auto-création / réparation de l'agence et de l'AHU // ════════════════════════════════════════════════════════════════ if($user->getTypeAccount() == "enterprise") { // CAS 1 : pas de currentAgency → on crée tout (agency + AHU) if($user->getCurrentAgency() === null) { $agency = new Agencies(); $agency->setEmail($user->getEmail()); $agency->setFirst(true); $agency->setValide(false); $agency->setPremium(false); $agency->setCreatedAt(new \DateTime("now")); $agency->setUpdatedAt(new \DateTime("now")); $agency->setPourcentCommission(0); $agency->setPourcentCommissionBank(0); $agency->setNoCommission(false); $agency->setCommissionCentimesBank(0); $agency->setLimitedUsers(1); $agency->setLimitedCourses(10); $agency->setLimitedQcm(10); $agency->setLimitedQcmApplication(10); $agency->setMultipleInscription(false); $agency->setStripe(false); $agency->setDemonstration(false); $this->em->persist($agency); $this->em->flush(); $user->setCurrentAgency($agency); $this->em->persist($user); $this->em->flush(); $ahu = new AgenciesHasUsers(); $ahu->setAgency($agency); $ahu->setUser($user); $ahu->setAdmin(false); $this->em->persist($ahu); $this->em->flush(); } // CAS 2 : l'user a une currentAgency mais pas d'AHU → on crée l'AHU manquant // (cas des comptes pré-existants à la refonte RC) else { $existingAhu = $this->em->getRepository(AgenciesHasUsers::class)->findOneBy([ 'user' => $user, 'agency' => $user->getCurrentAgency(), ]); if (!$existingAhu) { $ahu = new AgenciesHasUsers(); $ahu->setAgency($user->getCurrentAgency()); $ahu->setUser($user); $ahu->setAdmin(false); $this->em->persist($ahu); $this->em->flush(); } } } // ── Flags CV (candidats uniquement, sinon false) ── $cvGenerated = false; $cvUploaded = false; if ($user->getTypeAccount() !== 'enterprise') { $candidate = $user->getCandidate(); if ($candidate) { // CV généré : un cv_pdf_path a été enregistré par CvGeneratorController::generate $cvGenerated = !empty($candidate->getCvPdfPath()); // CV uploadé (PDF original envoyé via analyze/upload) : image Vich renseignée $cvUploaded = $candidate->getImage() !== null && $candidate->getImage()->getName() !== null; } } return new JsonResponse([ 'id' => $user->getId(), 'avatar' => $avatar ?? '', 'email' => $user->getEmail(), 'roles' => $user->getRoles(), 'typeAccount' => $user->getTypeAccount() ?? 'candidate', 'name' => $user->getName() ?? '', 'lastname' => $user->getLastname() ?? '', 'fullName' => '', 'language' => $language ?? 'en', 'first' => $user->isFirst() ?? 1, 'enabled' => $user->isEnabled() ?? 0, 'verification' => $user->isVerification() ?? 0, 'motifverification' => $user->getMotif() ?? '', 'premium' => $user->isPremium() ?? 0, 'cvGenerated' => $cvGenerated, 'cvUploaded' => $cvUploaded, ]); } /** * Mettre le profil de l'utilisateur - POST. * @param Request $request * @return JsonResponse */ public function update(Request $request): JsonResponse { if (!$request->isMethod('POST')) { return new JsonResponse(['error' => 'Method not allowed'], 405); } $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Unauthorized'], 401); } $data = $request->toArray(); $user->setName($data['name']); $user->setLastname($data['lastname']); if (!empty($data['avatar64'])) { try { $imageInfo = $this->detectImageTypeFromBase64($data['avatar64']); // Décoder le base64 $decodedImage = base64_decode($imageInfo['data']); if ($decodedImage === false) { return new JsonResponse(['error' => 'Invalid base64 image'], 400); } // Créer un fichier temporaire avec un nom unique $tmpFilePath = sys_get_temp_dir() . '/' . uniqid('avatar_', true) . '.' . $imageInfo['extension']; // Écrire l'image décodée dans le fichier temporaire $bytesWritten = file_put_contents($tmpFilePath, $decodedImage); if ($bytesWritten === false) { return new JsonResponse(['error' => 'Failed to write temporary file'], 500); } // ✅ IMPORTANT : Créer un objet UploadedFile (pas File) pour VichUploader $uploadedFile = new UploadedFile( $tmpFilePath, // Chemin du fichier temporaire 'avatar.' . $imageInfo['extension'], // Nom original du fichier $imageInfo['mimeType'], // Type MIME null, // Erreur (null = pas d'erreur) true // test mode = true (fichier temporaire) ); // Assigner le fichier à l'entité via VichUploader $user->setImageFile($uploadedFile); // ✅ IMPORTANT : Mettre à jour le timestamp pour forcer VichUploader à détecter le changement $user->setUpdatedAt(new \DateTimeImmutable()); } catch (\Exception $e) { // Nettoyer le fichier temporaire en cas d'erreur if (isset($tmpFilePath) && file_exists($tmpFilePath)) { @unlink($tmpFilePath); } return new JsonResponse(['error' => 'Upload failed: ' . $e->getMessage()], 500); } } if($user->getTypeAccount() == "enterprise") { $user->setFirst(false); } $this->em->persist($user); $this->em->flush(); if (isset($tmpFilePath) && file_exists($tmpFilePath)) { @unlink($tmpFilePath); } // Nettoyer le fichier temporaire si créé if (isset($tmpFilePath) && file_exists($tmpFilePath)) { @unlink($tmpFilePath); } return new JsonResponse(['success' => true]); } /** * Mettre à jour le language * @param Request $request * @return JsonResponse */ public function updateLanguage(Request $request): JsonResponse { if (!$request->isMethod('POST')) { return new JsonResponse(['error' => 'Method not allowed'], 405); } $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Unauthorized'], 401); } $data = $request->toArray(); $user->setLanguage($data['language']); $this->em->persist($user); $this->em->flush(); return new JsonResponse(['language' => $user->getLanguage(), 'success' => true]); } /** * Mettre à jour le language * @param Request $request * @return JsonResponse */ public function updateVerification(Request $request): JsonResponse { if (!$request->isMethod('POST')) { return new JsonResponse(['error' => 'Method not allowed'], 405); } $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Unauthorized'], 401); } $data = $request->toArray(); $user->setVerification(false); $user->setMotif($data['motif']); $this->em->persist($user); $this->em->flush(); return new JsonResponse(['success' => true]); } /** * Mettre à jour le token de notifications. * @param Request $request * @return JsonResponse */ public function updateNotifications(Request $request): JsonResponse { if (!$request->isMethod('POST')) { return new JsonResponse(['error' => 'Method not allowed'], 405); } $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Unauthorized'], 401); } $data = $request->toArray(); if (!isset($data['identifiant']) || empty($data['identifiant'])) { return new JsonResponse(['error' => 'Missing identifiant'], 400); } $mobileIdentifiers = $this->em->getRepository(MobileIdentifiers::class)->findOneBy(['user' => $user, 'identifiant' => $data['identifiant']]); if ($mobileIdentifiers === null) { $mobileIdentifiers = new MobileIdentifiers(); $mobileIdentifiers->setUser($user); $mobileIdentifiers->setIdentifiant($data['identifiant']); $mobileIdentifiers->setDescription("expo_token_notifications"); $this->em->persist($mobileIdentifiers); $this->em->flush(); return new JsonResponse([ 'token' => $mobileIdentifiers->getIdentifiant(), 'success' => true, 'created' => true ], 201); } return new JsonResponse([ 'token' => $mobileIdentifiers->getIdentifiant(), 'success' => true, 'created' => false ], 200); } /** * Supprimer les notifications. * @param Request $request * @return JsonResponse */ public function deleteNotifications(Request $request): JsonResponse { if (!$request->isMethod('DELETE')) { return new JsonResponse(['error' => 'Method not allowed'], 405); } $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Unauthorized'], 401); } $data = $request->toArray(); // Vérifier que l'identifiant est présent if (!isset($data['identifiant']) || empty($data['identifiant'])) { return new JsonResponse(['error' => 'Missing identifiant'], 400); } $mobileIdentifiers = $this->em->getRepository(MobileIdentifiers::class) ->findOneBy(['user' => $user, 'identifiant' => $data['identifiant']]); if ($mobileIdentifiers) { $this->em->remove($mobileIdentifiers); $this->em->flush(); return new JsonResponse([ 'success' => true, 'message' => 'Notification token deleted successfully' ], 200); } return new JsonResponse([ 'success' => false, 'message' => 'Token not found' ], 404); } /** * Mettre à jour le mot de passe. * @param Request $request * @return JsonResponse */ public function updatePassword(Request $request): JsonResponse { if (!$request->isMethod('POST')) { return new JsonResponse(['error' => 'Method not allowed'], 405); } $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Unauthorized'], 401); } $data = $request->toArray(); if($data['password'] !== $data['password_confirm']){ return new JsonResponse(['error' => 'Passwords do not match']); } $user->setPassword($this->passwordEncoder->encodePassword($user, $data['password'])); $this->em->persist($user); $this->em->flush(); return new JsonResponse(['success' => true]); } /** * Avatar Base64. * @param string $base64String * @return array|string[] */ private function detectImageTypeFromBase64(string $base64String): array { // Supprimer le préfixe data:image/...;base64, si présent if (preg_match('/^data:image\/(\w+);base64,(.+)$/', $base64String, $matches)) { $extension = $matches[1]; // jpeg, png, gif, etc. $data = $matches[2]; $mimeTypes = [ 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp', ]; return [ 'extension' => $extension === 'jpg' ? 'jpeg' : $extension, 'mimeType' => $mimeTypes[$extension] ?? 'image/jpeg', 'data' => $data ]; } // Si pas de préfixe, supposer que c'est du JPEG par défaut return [ 'extension' => 'jpeg', 'mimeType' => 'image/jpeg', 'data' => $base64String ]; } /** * Supprimer un compte utilisateur. * @param Request $request * @return JsonResponse */ public function deleteAccount(Request $request): JsonResponse { if (!$request->isMethod('POST')) { return new JsonResponse(['error' => 'Method not allowed'], 405); } $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Unauthorized'], 401); } $data = $request->toArray(); if($data['mail'] != $user->getEmail()) { return new JsonResponse(['success' => false]); } $candidate = $user->getCandidate(); if($candidate != null) { $groups = $this->em->getRepository(Groups::class)->findBy(["userOne" => $user]); if($groups !== null){ foreach ($groups as $group) { $messages = $this->em->getRepository(Messages::class)->findBy(['group' => $group->getId()]); foreach ($messages as $message) { $this->em->remove($message); $this->em->flush(); } $this->em->remove($group); } } $groups = $this->em->getRepository(Groups::class)->findBy(["userTwo" => $user]); if($groups !== null){ foreach ($groups as $group) { $messages = $this->em->getRepository(Messages::class)->findBy(['group' => $group->getId()]); foreach ($messages as $message) { $this->em->remove($message); $this->em->flush(); } $this->em->remove($group); } } $queries = $this->em->getRepository(CandidatesHasStatistics::class)->findBy(["candidate" => $candidate]); if($queries !== null){ foreach ($queries as $query) { $this->em->remove($query); } } $queries = $this->em->getRepository(CandidatesHasLikes::class)->findBy(["candidate" => $candidate]); if($queries !== null){ foreach ($queries as $query) { $this->em->remove($query); } } $queries = $this->em->getRepository(CandidatesHasComments::class)->findBy(["candidate" => $candidate]); if($queries !== null){ foreach ($queries as $query) { $this->em->remove($query); } } $this->em->remove($candidate); } $this->em->remove($user); $this->em->flush(); $delAccount = new AccountDeleted(); $delAccount->setEmail($data['mail']); $delAccount->setMotif($data['motif']); $delAccount->setCreatedAt(new \DateTime("now")); $delAccount->setUpdatedAt(new \DateTime("now")); $this->em->persist($delAccount); $this->em->flush(); return new JsonResponse(['success' => true]); }}