src/Service/Commun/UtilisateurService.php line 62

Open in your IDE?
  1. <?php
  2. namespace App\Service\Commun;
  3. use App\Entity\AssistantMaternel\AssistantMaternel;
  4. use App\Entity\Commun\Utilisateur;
  5. use App\Entity\Commun\UtilisateurPreferenceNotif;
  6. use App\Entity\Parametrage\EnumPreferenceNotifs;
  7. use App\Entity\Parametrage\EnumTypeNotification;
  8. use App\Entity\Parametrage\EnumProfil;
  9. use App\Entity\Parametrage\Profil;
  10. use App\Entity\Referentiel\SecteurPmi;
  11. use App\Service\AssistantMaternel\AssistantMaternelService;
  12. use App\Service\Parametrage\ProfilService;
  13. use App\Service\Commun\TraceurService;
  14. use App\Service\Notification\NotificationService;
  15. use App\Entity\Commun\Traceur;
  16. use App\Utils\TraceurUtils;
  17. use DateInterval;
  18. use Doctrine\ORM\EntityManagerInterface;
  19. use Symfony\Component\HttpFoundation\Request;
  20. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  21. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  22. use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
  23. use Symfony\Component\Validator\Validator\ValidatorInterface;
  24. use Psr\Log\LoggerInterface;
  25. class UtilisateurService
  26. {
  27.     // pour avoir accès à la BDD (doctrine)
  28.     private $em;
  29.     //log
  30.     private $logger;
  31.     // validation des entités
  32.     private $validator;
  33.     private $passwordEncoder;
  34.     private $profilService;
  35.     private $assmatService;
  36.     private $mailService;
  37.     private $traceurService;
  38.     private $notificationService;
  39.     public function __construct(
  40.         EntityManagerInterface $em,
  41.         ValidatorInterface $validator,
  42.         UserPasswordEncoderInterface $passwordEncoder,
  43.         ProfilService $profilService,
  44.         AssistantMaternelService $assmatService,
  45.         TokenStorageInterface $tokenStorage,
  46.         MailService $mailService,
  47.         TraceurService $traceurService,
  48.         NotificationService $notificationService,
  49.         LoggerInterface $logger
  50.     ) {
  51.         // injection de dépendance pour avoir accès à la BDD (doctrine)
  52.         $this->em $em;
  53.         $this->validator $validator;
  54.         $this->passwordEncoder $passwordEncoder;
  55.         $this->profilService $profilService;
  56.         $this->assmatService $assmatService;
  57.         $this->tokenStorage $tokenStorage//TODO : à supprimer si on ne gare pas la fonctionnalit de rester connecté après changement de mail
  58.         $this->mailService $mailService;
  59.         $this->traceurService $traceurService;
  60.         $this->notificationService $notificationService;
  61.         $this->logger $logger;
  62.     }
  63.     /**
  64.      * Vérifie si l'email fourni existe déjà en base en tant que login d'une assmat ou d'un utilisateur
  65.      *
  66.      * @param string $email
  67.      * @param Utilisateur $user
  68.      * @return string vide si l'email n'esiste pas, le message d'erreur sinon
  69.      */
  70.     public function checkEmailUnique($emailUtilisateur $user)
  71.     {
  72.         $userMemeMail $this->em->getRepository(Utilisateur::class)->findBy(array('email' => $email));
  73.         if ($userMemeMail != null) {
  74.             foreach ($userMemeMail as $value) {
  75.                 if ($value->getId() != $user->getId()) {
  76.                     return "Cet email est déjà utilisé par un autre utilisateur.";
  77.                 }
  78.             }
  79.         }
  80.         $assmatMemeMail $this->em->getRepository(AssistantMaternel::class)->findBy(array('email' => $email));
  81.         if ($assmatMemeMail != null) {
  82.             foreach ($assmatMemeMail as $value) {
  83.                 if ($value->getUtilisateur()->getId() != $user->getId()) {
  84.                     return "Cet email est déjà utilisé par un autre assistant maternel.";
  85.                 }
  86.             }
  87.         }
  88.         return '';
  89.     }
  90.     /**
  91.      * Enregistre les modifications de caractériques d'une assmat (en tant qu'utilisateur)
  92.      *
  93.      * @param Utilisateur $user
  94.      * @param Utilisateur $currentUser
  95.      * @param Request $request
  96.      * @return void
  97.      */
  98.     public function modifierUtilisateurAssmat(Utilisateur $userUtilisateur $currentUserRequest $request)
  99.     {
  100.         // Récupérer les paramètres de la requête
  101.         $email $request->get('email');
  102.         $actif $request->get('actif');
  103.         $archive $request->get('archive');
  104.         // Mapping sur l'utilisateur
  105.         // L'email est stocké au niveau de l'assmat
  106.         if (!is_null($actif)) {
  107.             $user->setActif($actif);
  108.         }
  109.         // TODO déplacer dans service Assmat ?
  110.         $assmat $user->getAssistantMaternel();
  111.         if (!is_null($archive)) {
  112.             $assmat->setEstArchiveDomicile($archive);
  113.             $assmat->setEstArchiveMam($archive);
  114.         }
  115.         if (!is_null($email)) {
  116.             // Validation spécifique ici car pas de validation au niveau de l'entité (email nullable)
  117.             if (empty($email)) {
  118.                 // Erreur
  119.                 return "Vous devez saisir une adresse email.";
  120.             }
  121.             $oldEmail $assmat->getEmail();
  122.             $assmat->setEmail($email);
  123.         }
  124.         // Validation (pas de formulaire)
  125.         $errorsU $this->validator->validate($user);
  126.         if (count($errorsU) > 0) { //NOSONAR empty n'est pas équivalent à count > 0 ici
  127.             // TODO throw exception de validation + catch + print controller ?
  128.             // Par simplicité on affiche seulement la 1ère erreur
  129.             return $errorsU->get(0)->getMessage();
  130.         }
  131.         // TODO déplacer dans service Assmat ?
  132.         $errorsAM $this->validator->validate($assmat);
  133.         if (count($errorsAM) > 0) { //NOSONAR empty n'est pas équivalent à count > 0 ici
  134.             // TODO throw exception de validation + catch + print controller ?
  135.             // Par simplicité on affiche seulement la 1ère erreur
  136.             return $errorsAM->get(0)->getMessage();
  137.         }
  138.         //verification unicite email
  139.         if ($email != null) {
  140.             $checkUnique $this->checkEmailUnique($email$user);
  141.             if ($checkUnique !== '') {
  142.                 return $checkUnique;
  143.             }
  144.         }
  145.         // Enregistrement
  146.         $laTraceUser $this->tracerUtilisateur($currentUser$user);
  147.         $laTraceAssmat $this->assmatService->tracerAssmat($currentUser$assmat);
  148.         $this->enregistrerUtilisateur($user$currentUser);
  149.         $this->assmatService->enregistrerAssmatSimple($assmat$currentUserfalse);
  150.         if (!is_null($email)) {
  151.             $this->notificationService->ajouterOuMettreAJourNotification($assmat->getId(), EnumTypeNotification::CHANGEMENT_EMAIL''is_null($oldEmail) ? '' $oldEmail);
  152.         }
  153.         // maj statut depassement
  154.         $this->assmatService->computeDepassement($assmatfalse); // (des)archivage d'une assmat
  155.         // ecrire les traces
  156.         $this->traceurService->ecrireTrace($laTraceUsernull);
  157.         $this->traceurService->ecrireTrace($laTraceAssmatnull);
  158.         // Succès
  159.         return true;
  160.     }
  161.     /**
  162.      * 
  163.      * @param Utilisateur $user
  164.      * @param Request $request
  165.      * @return boolean|array
  166.      * @throws Exception
  167.      */
  168.     public function modifierUtilisateurDepartement(Utilisateur $userUtilisateur $currentUserRequest $request)
  169.     {
  170.         // Récupérer les paramètres de la requête
  171.         // Null si non défini
  172.         $email $request->get('email');
  173.         $idProfil $request->get('profil');
  174.         $idSecteurPmi $request->get('secteurPmi');
  175.         $supprime $request->get('supprime');
  176.         $actif $request->get('actif');
  177.         $idAncienProfil $user->getProfil()->getId();
  178.         // Mapping sur l'utilisateur
  179.         if (!is_null($email)) {
  180.             // Validation spécifique ici car pas de validation au niveau de l'entité (email nullable)
  181.             if (empty($email)) {
  182.                 // Erreur
  183.                 return "Vous devez saisir une adresse email.";
  184.             }
  185.             $user->setEmail($email);
  186.         }
  187.         if (!is_null($idProfil)) {
  188.             $profil $this->em->find(Profil::class, $idProfil);
  189.             $user->setProfil($profil);
  190.         }
  191.         if (!is_null($idSecteurPmi)) {
  192.             if ($idSecteurPmi) {
  193.                 // Secteur de PMI (non vide) sélectionné
  194.                 $secteurPmi $this->em->find(SecteurPmi::class, $idSecteurPmi);
  195.             } else {
  196.                 // Pas de secteur de PMI sélectionné ($idSecteurPmi="")
  197.                 $secteurPmi null;
  198.             }
  199.             $user->setSecteurPmi($secteurPmi);
  200.         }
  201.         if (!is_null($actif)) {
  202.             $user->setActif($actif);
  203.         }
  204.         // null est évalué à false
  205.         if ($supprime) {
  206.             // on ne peut pas désupprimer un utilisateur
  207.             $user->setSupprime(true);
  208.         }
  209.         // Validation à la main car pas de formulaire
  210.         $errors $this->validator->validate($user);
  211.         if (count($errors) > 0) { //NOSONAR empty n'est pas équivalent à count > 0 ici
  212.             // TODO throw exception de validation + catch + print controller ?
  213.             // Par simplicité on affiche seulement la 1ère erreur
  214.             return $errors->get(0)->getMessage();
  215.         }
  216.         //verification unicite email
  217.         //unicite entre utilisateurs déjà géré par doctrine (contrainte d'unicité)
  218.         // TODO checkEmailUnique ?
  219.         $assmatMemeMail $this->em->getRepository(AssistantMaternel::class)->findBy(array('email' => $user->getEmail()));
  220.         if ($assmatMemeMail != null) {
  221.             return "Cet email est déjà utilisé par un autre assistant maternel.";
  222.         }
  223.         // initialise les préférences de notification si devient puer, les supprime si était puer
  224.         $this->gererPreferencesNotification($user$idAncienProfil$idProfil);
  225.         // Enregistrement
  226.         $laTraceUser $this->tracerUtilisateur($currentUser$user);
  227.         $this->enregistrerUtilisateur($user$currentUser);
  228.         $this->traceurService->ecrireTrace($laTraceUsernull);
  229.         $this->em->flush();
  230.         // Succès
  231.         return true;
  232.     }
  233.     /**
  234.      * Permet à l'utilisateur de rester connecté après avoir changé de login
  235.      * TODO : setToken(new UsernamePasswordToken) ne fonctionne plus aprsè gestion obso
  236.      * 
  237.      * @param type $newUser
  238.      */
  239.     public function rafraichirTokenUtilisateur($newUser)
  240.     {
  241.         // Récupérer le token actuel pour les données qui ne changent pas
  242.         $oldToken $this->tokenStorage->getToken();
  243.         // Créer un nouveau token avec l'utilisateur mis à jour
  244.         $token = new UsernamePasswordToken(
  245.             $newUser// user object with updated username
  246.             null,
  247.             $oldToken->getProviderKey(),
  248.             $oldToken->getRoles()
  249.         );
  250.         // Mettre à jour le token
  251.         $this->tokenStorage->setToken($token);
  252.     }
  253.     public function enregistrerMotDePasse(Utilisateur $userstring $password)
  254.     {
  255.         // Encoder le mot de passe
  256.         $password_hash $this->passwordEncoder->encodePassword($user$password);
  257.         $user->setPassword($password_hash);
  258.         // Invalider le code d'activation ou de réinitialisation
  259.         $user->setCodeActivation(null);
  260.         // Enregistrement
  261.         $this->em->persist($user);
  262.         $this->em->flush();
  263.     }
  264.     public function creerUtilisateurAssmat($assmat)
  265.     {
  266.         // création de l'utilisateur
  267.         $utilisateur = new Utilisateur();
  268.         $utilisateur->setProfil($this->profilService->getProfil(EnumProfil::ASSMAT));
  269.         $utilisateur->setAssistantMaternel($assmat);
  270.         // Les champs de l'objet Assmat ont déjà été validés par l'orchestrateur
  271.         $utilisateur->setNom($assmat->getNomFamille());
  272.         $utilisateur->setPrenom($assmat->getPrenom());
  273.         // TODO factoriser valeurs par défaut
  274.         $utilisateur->setActif(true);
  275.         $utilisateur->setSupprime(false);
  276.         // L'assmat n'a pas de mot de passe tant que son compte n'est pas activé
  277.         $utilisateur->setPassword('');
  278.         return $utilisateur;
  279.     }
  280.     /**
  281.      * Créer les comptes utilisateurs des assmats qui n'ont pas encore de compte
  282.      *
  283.      * @param int $limiteComptesAssmats Nombre de comptes maximum à créer
  284.      */
  285.     public function creerComptesUtilisateursAssmats($limiteComptesAssmats null)
  286.     {
  287.         $batchSize 500;
  288.         // TODO centralisation log
  289.         echo "[DEBUG] Récupération des assmats sans compte..." PHP_EOL;
  290.         // Structure de données itérable renvoyée par DOctrine qui permet de
  291.         // ne pas charger tous les objets en mémoire d'un seul coup
  292.         $iterableResult $this->em->getRepository(AssistantMaternel::class)->findSansCompteUtilisateur($limiteComptesAssmats);
  293.         $nb_assmat 0;
  294.         echo "[DEBUG] Memory usage before: " . (memory_get_usage() / 1024) . " KB" PHP_EOL;
  295.         $s microtime(true);
  296.         foreach ($iterableResult as $row) {
  297.             $assmat $row[0];
  298.             $nb_assmat++;
  299.             // créer utilisateur pour l'assmat $assmat
  300.             $utilisateur $this->creerUtilisateurAssmat($assmat);
  301.             // association
  302.             $assmat->setUtilisateur($utilisateur);
  303.             // enregistrement en base
  304.             $this->em->persist($utilisateur);
  305.             $this->em->persist($assmat);
  306.             // Traitement par batchs pour libérer de la mémoire au fil de l'exécution
  307.             if ($nb_assmat $batchSize === 0) {
  308.                 $this->em->flush();
  309.                 $this->em->clear();
  310.                 echo "[DEBUG] Memory usage: " . (memory_get_usage() / 1024) . " KB" PHP_EOL;
  311.                 echo "[DEBUG] $nb_assmat assmats traités" PHP_EOL;
  312.             }
  313.             // On limite le nombre d'insertions pour les jeux de tests de dev
  314.             if (!is_null($limiteComptesAssmats) && $nb_assmat >= $limiteComptesAssmats) {
  315.                 break;
  316.             }
  317.         }
  318.         // flush final
  319.         $this->em->flush();
  320.         // fin
  321.         echo "[DEBUG] Memory usage after: " . (memory_get_usage() / 1024) . " KB" PHP_EOL;
  322.         $e microtime(true);
  323.         echo "[DEBUG] Inserted $nb_assmat objects in " . ($e $s) . ' seconds' PHP_EOL;
  324.     }
  325.     /**
  326.      * Génération du code d'activation de compte
  327.      * Le code est alphanumerique sur 15 caractères.
  328.      * Le code est valide 7 jours.
  329.      * Le code remplace tout code préalablement existant.
  330.      * 
  331.      * @param Utilisateur $user utilisateur concerné par la demande de reinit
  332.      * @return string
  333.      */
  334.     private function genererCodeActivation(Utilisateur $user)
  335.     {
  336.         // Générer un nouveau code d'activation
  337.         // On utilise un multiple de 3 car base64_encode encode 3 bytes en 4 caractères
  338.         // (évite d'avoir des caractères "=" en fin de chaîne)
  339.         // Avec 15 bytes on génère un code de 20 caractères
  340.         // On supprime les caractères spéciaux pour que le code passe dans l'URL
  341.         $regex "/[^a-zA-Z0-9]/";
  342.         $code_activation preg_replace($regex''base64_encode(random_bytes(15)));
  343.         // Mettre une date limite de validité au token
  344.         $date_validite = new \DateTime();
  345.         $interval DateInterval::createfromdatestring('+7 day');
  346.         $date_validite->add($interval);
  347.         // Enregistrer le token dans l'utilisateur
  348.         // Si un code existait déjà, le nouveau code annule et remplace l'ancien
  349.         $user->setCodeActivation($code_activation);
  350.         $user->setDateLimiteCodeActivation($date_validite);
  351.         $this->em->persist($user);
  352.         $this->em->flush();
  353.         return $code_activation;
  354.     }
  355.     /**
  356.      * Génère un code d'activation pour l'utilisateur et
  357.      * envoie l'email correspondant à l'activation du compte
  358.      *
  359.      * @param Utilisateur $user utilisateur concerné par la demande de reinit
  360.      */
  361.     public function envoyerCodeActivation(Utilisateur $user)
  362.     {
  363.         // Générer le code
  364.         $code $this->genererCodeActivation($user);
  365.         // Envoyer le code à l'utilisateur par mail
  366.         try {
  367.             return $this->mailService->envoyerMailActivation($user$code);
  368.         } catch (\Error $err) {
  369.             $this->logger->critical('ECHEC envoyerMailActivation : ' $err->getMessage());
  370.             $this->logger->critical($err);
  371.         }
  372.     }
  373.     /**
  374.      * Génère un code d'activation pour l'utilisateur et
  375.      * envoie l'email correspondant à la réinitialisation de mot de passe
  376.      * 
  377.      * @param Utilisateur $user utilisateur concerné par la demande de reinit
  378.      */
  379.     public function demanderReinitialisationMotDePasse(Utilisateur $user)
  380.     {
  381.         if (!$user->getActif() || $user->getSupprime()) {
  382.             // Ne rien faire
  383.             $this->logger->info("Demande de réinitialisation de mot de passe ignorée : utilisateur désactivé ou supprimé"$user);
  384.             return null;
  385.         }
  386.         // Générer un nouveau code
  387.         $code $this->genererCodeActivation($user);
  388.         // Envoyer le code à l'utilisateur par mail
  389.         return $this->mailService->envoyerMailReinitialisation($user$code);
  390.     }
  391.     /**
  392.      * vérifie si le code d'activation est valide par raport à l'email
  393.      *
  394.      * @param string $code
  395.      * @param string $email
  396.      * @return bool
  397.      */
  398.     public function verifierCodeActivation(string $codestring $email)
  399.     {
  400.         $repo $this->em->getRepository(Utilisateur::class);
  401.         // Réutilisation de la fonction utilisée par l'authentification
  402.         // qui permet de retrouver un utilisateur par email utilisateur ou email assmat
  403.         $user $repo->loadUserByUsername($email);
  404.         if (!$user) {
  405.             // Aucun utilisateur n'existe avec cette adresse
  406.             return false;
  407.         }
  408.         if ($user->getCodeActivation() !== $code) {
  409.             // Le code d'activation associé à cet utilisateur n'existe pas
  410.             // (mauvais code, ou le code a été remplacé par un nouveau)
  411.             return false;
  412.         }
  413.         $now = new \DateTime();
  414.         if ($user->getDateLimiteCodeActivation() < $now || !$user->getActif() || $user->getSupprime()) {
  415.             // Code périmé
  416.             return false;
  417.         }
  418.         // Le code d'activation/réinitialisation existe, est bien associé à l'email, et n'est pas périmé
  419.         return true;
  420.     }
  421.     /**
  422.      * Enregistrement de l'utilisateur
  423.      *
  424.      * @param Utilisateur $user utilisateur en cours d'édition
  425.      * @param Utilisateur $currentUser auteur de l'édition
  426.      * @param Traceur $laTrace
  427.      * @return Utilisateur
  428.      */
  429.     public function enregistrerUtilisateur(Utilisateur $userUtilisateur $currentUserTraceur $laTrace null)
  430.     {
  431.         try {
  432.             if ($laTrace == null) {
  433.                 $laTrace $this->tracerUtilisateur($currentUser$user);
  434.             }
  435.             $this->em->persist($user);
  436.         } catch (\Error $err) {
  437.             $this->traceurService->ecrireEchec($laTrace$err->getTraceAsString());
  438.             throw $err;
  439.         } catch (\Exception $ex) {
  440.             $this->traceurService->ecrireEchec($laTrace$ex->getTraceAsString());
  441.             throw $ex;
  442.         }
  443.         return $user;
  444.     }
  445.     /**
  446.      * Trace les modifications effectuées sur l'utlisateur
  447.      *
  448.      * @param Utilisateur $currentUser auteur de la modification
  449.      * @param Utilisateur $user en cours d'édition (ajout ou modification)
  450.      */
  451.     public function tracerUtilisateur(Utilisateur $currentUserUtilisateur $user)
  452.     {
  453.         try {
  454.             $json '';
  455.             if ($user->getId() > 0) {
  456.                 //delta de modification
  457.                 $originalData $this->em->getUnitOfWork()->getOriginalEntityData($user);
  458.                 $originalEntityArray Utilisateur::createFromArray($originalData)->jsonSerialize();
  459.                 $toArrayEntity $user->jsonSerialize();
  460.                 $changes TraceurUtils::array_diff_assoc_recursive($toArrayEntity$originalEntityArray);
  461.                 $json json_encode($changes);
  462.             } else {
  463.                 //création
  464.                 $json json_encode($user->jsonSerialize());
  465.             }
  466.             return $this->traceurService->genereTrace(
  467.                 $currentUser,
  468.                 null,
  469.                 $user->getId(),
  470.                 "utilisateur",
  471.                 "modification utilisateur [" $user->getIdFonctionnel() . " - " strtoupper($user->getNom()) . " " $user->getPrenom() . " - " $user->getEmail() . "][" $user->getProfil()->getLibelle() . "]",
  472.                 $json
  473.             );
  474.         } catch (\Exception $e) {
  475.             $this->logger->critical('Exception reçue : ' $e->getMessage());
  476.             $this->logger->critical($e);
  477.         }
  478.     }
  479.     /**
  480.      * Gère le changemnt de profil et son impact sur les préférences interveant
  481.      * si $user devient puer, activer les notifs
  482.      * si était puer et ne l'est plus, supprimer les préférences de notif
  483.      * @param Utilisateur $user
  484.      * @param type $idAncienProfil
  485.      * @param type $idProfil
  486.      * @return Utilisateur
  487.      */
  488.     public function gererPreferencesNotification(Utilisateur $user$idAncienProfil$idProfil)
  489.     {
  490.         if ($idAncienProfil == $idProfil) {
  491.             return $user;
  492.         }
  493.         if ($idProfil == EnumProfil::PUER) {
  494.             //initialiser les préférences
  495.             foreach (EnumTypeNotification::getListeTypeNotification() as $idTypeNotif) {
  496.                 $initPref = new UtilisateurPreferenceNotif();
  497.                 $initPref->setUtilisateur($user);
  498.                 $initPref->setTypeNotification($idTypeNotif);
  499.                 $initPref->setPreferenceNotification(EnumPreferenceNotifs::QUODITIENNE);
  500.                 $this->em->persist($initPref);
  501.                 $this->em->flush();
  502.             }
  503.         }
  504.         if ($idAncienProfil == EnumProfil::PUER) {
  505.             //supprimer les préférences
  506.             foreach ($user->getPreferencesNotification() as $pref) {
  507.                 $this->em->remove($pref);
  508.                 $this->em->flush();
  509.             }
  510.         }
  511.         return $user;
  512.     }
  513. }