<?php
namespace App\Entity\Commun;
use App\Entity\AssistantMaternel\AssistantMaternel;
use App\Entity\Parametrage\Profil;
use App\Entity\Commun\UtilisateurPreferenceNotif;
use Doctrine\ORM\Mapping as ORM;
use JsonSerializable;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\GroupSequenceProviderInterface;
//TODO : https://github.com/symfony/symfony/issues/16833
// ajouter un UniqueEntity("email", repositoryMethod="uniqueEmailFromAssmat")
/**
* @see https://blog.dev-web.io/2017/12/16/symfony-4-gestion-utilisateurs-sans-fosuserbundle/
* @ORM\Entity(repositoryClass="App\Repository\Commun\UtilisateurRepository")
* @ORM\Table(name="efc.utilisateur",indexes={@ORM\Index(name="utilisateur_id_fonctionnel_idx", columns={"id_fonctionnel"}),@ORM\Index(name="utilisateur_email_idx", columns={"email"})})
*
* @UniqueEntity(
* fields={"email"},
* message="Cette adresse email est déjà utilisée par un utilisateur actif ou supprimé.",
* ignoreNull=true
* )
* @UniqueEntity(
* fields={"id_fonctionnel"},
* message="Cet identifiant est déjà utilisé par un utilisateur actif ou supprimé.",
* repositoryMethod="uniqueIdFonctionnel",
* ignoreNull=true
* )
* @see https://pehapkari.cz/blog/2017/02/18/symfony-validator-conditional-constraints/
* @see https://symfony.com/doc/current/validation/sequence_provider.html
* @Assert\GroupSequenceProvider()
*/
class Utilisateur implements UserInterface, JsonSerializable, EquatableInterface, GroupSequenceProviderInterface
{
use IdTrait;
/**
* @var string
*
* @ORM\Column(type="string", length=50)
*/
private $nom;
/**
* @var string
*
* @ORM\Column(type="string", length=50)
*/
private $prenom;
/**
* Pas de contrainte d'unicité en base car on peut avoir plusieurs email=null
* (pour les assmats)
* La contrainte est gérée avec UniqueEntity qui gère les null multiples
*
* @var string
*
* @Assert\Email(
* message = "L'adresse mail '{{ value }}' n'est pas valide.",
* )
*
* La regex permet de transmettre une contrainte de validation avec un format plus strict au niveau du navigateur
* @Assert\Regex(
* pattern="/^([a-zA-Z0-9'_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/",
* match=true,
* message="L'adresse mail '{{ value }}' n'est pas valide."
* )
* @ORM\Column(type="string", nullable=true)
*/
private $email;
/**
* @ORM\OneToOne(targetEntity="App\Entity\Commun\CodeConfirmationEmail", inversedBy="utilisateur")
* @ORM\JoinColumn(name="id_code_confirmation_email", referencedColumnName="id")
*/
private $code_confirmation_email;
/**
* @var string
*
* @ORM\Column(type="string")
*/
private $password;
/**
* @var bool
*
* @ORM\Column(type="boolean", nullable=false, options={"default":true})
*/
private $actif = true;
/**
* @ORM\Column(type="boolean", nullable=false, options={"default":false})
*/
private $supprime = false;
/**
* @var string
*
* @ORM\Column(type="string", nullable=true)
*/
private $id_fonctionnel;
/**
* Profil (ensemble de droits) de l'utilisateurs
* Relation unidirectionnelle
* (rarement besoin de trouver les utilisateurs associés à un profil)
*
* @ORM\ManyToOne(targetEntity="App\Entity\Parametrage\Profil")
* @ORM\JoinColumn(name="id_profil")
*/
private $profil;
/**
* Assistant maternel optionnel
*
* @ORM\OneToOne(targetEntity="App\Entity\AssistantMaternel\AssistantMaternel", mappedBy="utilisateur")
*/
private $assistant_maternel;
/**
* Secteur de PMI pour les puéricultrices
*
* @Assert\NotNull(message="Les utilisateurs ayant le profil Puéricultrice doivent être associés à un secteur", groups={"puericultrice"})
*
* @ORM\ManyToOne(targetEntity="App\Entity\Referentiel\SecteurPmi")
* @ORM\JoinColumn(name="id_secteur_pmi")
*/
private $secteur_pmi;
/**
* Préférences de notifications, pour les utilisateurs qui ont le role ALERTES
*
* @ORM\OneToMany(targetEntity="App\Entity\Commun\UtilisateurPreferenceNotif", mappedBy="utilisateur")
* @ORM\OrderBy({"type_notification" = "ASC"})
*/
private $preferences_notification;
/**
* Token pour l'activation ou la réinitialisation de mot de passe
*
* @ORM\Column(type="string", nullable=true)
*/
private $code_activation;
/**
* Date limite de validité du lien d'activation
*
* @ORM\Column(type="date", nullable=true)
*/
private $date_limite_code_activation;
/**
* Timestamp du dernier token d'authent SSO utilisé
* Vérifié afin de garantir que chaque token est utilisé seulement 1 fois
*
* @ORM\Column(type="datetime", nullable=true)
*/
private $sso_date_dernier_token;
/**
* Liste des communes consultables, dans le cas des profils partenaire
* Non applicable si autre profil
*
* @ORM\ManyToMany(targetEntity="App\Entity\Referentiel\Commune")
* @ORM\JoinTable(name="efc.utilisateur_commune")
*/
private $communes;
public function __construct()
{
$this->communes = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* {@inheritdoc}
*/
public function getUsername()
{
return $this->getEmail();
}
/**
* {@inheritdoc}
*/
public function getAuteurModification()
{
if ($this->supprime || ($this->estAssistantMaternel() && $this->getAssistantMaternel()->getSupprime())) {
return $this->getNom() . " " . $this->getPrenom() . " (supprimé)";
}
return $this->getNom() . " " . $this->getPrenom();
}
/**
* {@inheritdoc}
*/
public function getEmail()
{
// pour les assmats, l'email est porté par les infos assmat
if ($this->estAssistantMaternel()) {
return $this->getAssistantMaternel()->getEmail();
}
// sinon, l'email est porté par l'utilisateur
return $this->email;
}
/**
* Get code_confirmation_email
*
* @return CodeConfirmationEmail
*/
public function getCodeConfirmationEmail()
{
return $this->code_confirmation_email;
}
/**
* {@inheritdoc}
*/
public function setEmail(string $email)
{
// pour les assmats, l'email est porté par les infos assmat
if ($this->estAssistantMaternel()) {
$this->getAssistantMaternel()->setEmail($email);
} else {
// sinon, l'email est porté par l'utilisateur
$this->email = $email;
}
return $this;
}
/**
* Set code_confirmation_email
*
* @param CodeConfirmationEmail code_confirmation_email
*
* @return AssistantMaternel
*/
public function setCodeConfirmationEmail($code_confirmation_email)
{
$this->code_confirmation_email = $code_confirmation_email;
return $this;
}
public function getNom()
{
return $this->nom;
}
public function setNom($nom)
{
$this->nom = $nom;
return $this;
}
public function getPrenom()
{
return $this->prenom;
}
public function setPrenom($prenom)
{
$this->prenom = $prenom;
return $this;
}
public function getIdFonctionnel()
{
return $this->id_fonctionnel;
}
public function setIdFonctionnel($id_fonctionnel)
{
$this->id_fonctionnel = $id_fonctionnel;
return $this;
}
public function getActif()
{
return $this->actif;
}
public function setActif($actif)
{
// https://stackoverflow.com/questions/4775294/parsing-a-string-into-a-boolean-value-in-php
$this->actif = filter_var($actif, FILTER_VALIDATE_BOOLEAN);
return $this;
}
public function getSupprime()
{
return $this->supprime;
}
public function setSupprime($supprime)
{
// https://stackoverflow.com/questions/4775294/parsing-a-string-into-a-boolean-value-in-php
$this->supprime = filter_var($supprime, FILTER_VALIDATE_BOOLEAN);
return $this;
}
/**
* Les communes consultables par l'utilisateur si c'est un profil Partenaire
* @return App\Entity\Referentiel\Commune[]
*/
public function getCommunes(): \Doctrine\Common\Collections\Collection
{
return $this->communes;
}
/**
* La liste des communes en texte, limité à 3
* @return string
*/
public function getCommunesAsString()
{
$communesU = $this->getCommunes();
switch (count($communesU)) {
case 0:
// Si aucune commune sélectionnée, toutes sont autorisées
return "toutes";
case 1:
return $communesU->first()->getLibelle();
case 2:
return $communesU->first()->getLibelle() . ", " . $communesU->next()->getLibelle();
case 3:
return $communesU->first()->getLibelle() . ", " . $communesU->next()->getLibelle() . ", " . $communesU->next()->getLibelle();
default:
// + de 3 communes
$nb = count($communesU) - 3;
return $communesU->first()->getLibelle() . ", " . $communesU->next()->getLibelle() . ", " . $communesU->next()->getLibelle() . " et $nb autre(s)";
}
}
public function getProfil()
{
return $this->profil;
}
public function setProfil(Profil $profil)
{
$this->profil = $profil;
return $this;
}
/**
* {@inheritdoc}
*
* @return string
*/
public function getPassword()
{
// Méthode à implémenter de UserInterface, donc j'ai gardé password en nom de champ.
return $this->password;
}
/**
* {@inheritdoc}
*/
public function setPassword(string $password)
{
$this->password = $password;
return $this;
}
/**
* {@inheritdoc}
*/
public function getRoles(): array
{
$roles = $this->profil->getRoles();
return array_unique($roles);
}
/**
* Retourne le salt qui a servi à coder le mot de passe.
*
* {@inheritdoc}
*/
public function getSalt()
{
// See "Do you need to use a Salt?" at https://symfony.com/doc/current/cookbook/security/entity_provider.html
// we're using bcrypt in security.yml to encode the password, so
// the salt value is built-in and you don't have to generate one
return null;
}
/**
* Removes sensitive data from the user.
*
* {@inheritdoc}
*/
public function eraseCredentials(): void
{
// Nous n'avons pas besoin de cette methode car nous n'utilions pas de plainPassword
// Mais elle est obligatoire car comprise dans l'interface UserInterface
}
/**
* {@inheritdoc}
*
* @param UserInterface $user
* @return bool
*/
public function isEqualTo(UserInterface $user)
{
// $user est l'utilisateur qu'on vient de récupérer dans la BDD à partir de la clé primaire
// Si $user est supprimé ou désactivé, on renvoie toujours false
// Cela a pour effet de déconnecter l'utilisateur
if ($user instanceof Utilisateur) {
if ($user->getSupprime() || !$user->getActif()) {
return false;
}
}
// Comportement par défaut
return
$this->getUsername() === $user->getUsername() &&
$this->getPassword() === $user->getPassword() &&
$this->getSalt() === $user->getSalt();
}
/**
* Set assistant_maternel
*
* @param AssistantMaternel $assistant_maternel
*
* @return Utilisateur
*/
public function setAssistantMaternel($assistant_maternel)
{
$this->assistant_maternel = $assistant_maternel;
$this->email = null;
return $this;
}
/**
* Get assistant_maternel
*
* @return AssistantMaternel
*/
public function getAssistantMaternel()
{
return $this->assistant_maternel;
}
/**
* Set secteur_pmi
*
* @param SecteurPmi $secteur_pmi
*
* @return Utilisateur
*/
public function setSecteurPmi($secteur_pmi)
{
$this->secteur_pmi = $secteur_pmi;
return $this;
}
/**
* Get secteur_pmi
*
* @return SecteurPmi
*/
public function getSecteurPmi()
{
return $this->secteur_pmi;
}
/**
* Set preferences_notification
*
* @param $preference_notifs liste des prefernces de notification de l'utilisateur
*
* @return Utilisateur
*/
public function setPreferencesNotification($preference_notifs)
{
$this->preferences_notification = $preference_notifs;
return $this;
}
/**
* Get preferences_notification
*
* @return liste des preferences de notification de l'utilisateur
*/
public function getPreferencesNotification()
{
return $this->preferences_notification;
}
/**
* Set code_activation
*
* @param string $code_activation
*
* @return Utilisateur
*/
public function setCodeActivation($code_activation)
{
$this->code_activation = $code_activation;
return $this;
}
/**
* Get code_activation
*
* @return string
*/
public function getCodeActivation()
{
return $this->code_activation;
}
/**
* Set date_limite_code_activation
*
* @param DateTime $date_limite_code_activation
*
* @return Utilisateur
*/
public function setDateLimiteCodeActivation($date_limite_code_activation)
{
$this->date_limite_code_activation = $date_limite_code_activation;
return $this;
}
/**
* Get date_limite_code_activation
*
* @return DateTime
*/
public function getDateLimiteCodeActivation()
{
return $this->date_limite_code_activation;
}
public function getTimestampDernierToken()
{
if ($this->sso_date_dernier_token) {
return $this->sso_date_dernier_token->getTimestamp();
}
return null;
}
public function setTimestampDernierToken(int $timestamp)
{
$date = new \DateTime();
$date->setTimestamp($timestamp);
$this->sso_date_dernier_token = $date;
}
/**
* L'utilisateur est assmat ?
*
* @return bool
*/
public function estAssistantMaternel()
{
// s'il existe un assmat lié à l'utilisateur, l'utilisateur est assmat
return !is_null($this->getAssistantMaternel());
}
/*
* Implémentation de JsonSerializable
* Ne liste que les champs modifiables par les utilisateurs
*/
public function jsonSerialize()
{
return [
'id' => $this->id,
'email' => $this->email,
'nom' => $this->nom,
'prenom' => $this->prenom,
'supprime' => $this->supprime,
'actif' => $this->actif,
'profil' => $this->profil === null ? null : $this->profil->getLibelle(),
'code_activation' => $this->code_activation,
'date_limite_code_activation' => $this->date_limite_code_activation,
'secteur' => $this->secteur_pmi === null ? null : $this->secteur_pmi->getLibelle(),
'notification' => $this->getPreferencesNotification(),
'password' => $this->password,
];
}
/**
* Obtient une nouvelle instance d'Utilisateur, initialisée à partir du tableau associatif fourni en paramètre
*
* @param array $data
* @return Utilisateur
*/
public static function createFromArray(array $data)
{
$newInstance = new Utilisateur();
return $newInstance->initFromArray($data);
}
/**
* mappe les propriétés sur l'instance à partir d'un tableau associatif
*
* @param array $data
* @return void
*/
public function initFromArray(array $data)
{
foreach ($data as $prop => $value) {
$this->{$prop} = $value;
}
return $this;
}
/**
* {@inheritdoc}
* @see https://pehapkari.cz/blog/2017/02/18/symfony-validator-conditional-constraints/
* @see https://symfony.com/doc/current/validation/sequence_provider.html
* @return type
*/
public function getGroupSequence()
{
// La liste des validation groups à appliquer
$groups = array();
// Dans tous les cas, on valide le validation group "Utilisateur"
// Ce groupe existe par défaut et regroupe toutes les contraintes dont le validation group n'est pas précisé
$groups[] = 'Utilisateur';
// Dans le cas où l'utilisateur est puéricultrice, on valide aussi le groupe "puericultrice"
if ($this->getProfil()->getId() === \App\Entity\Parametrage\EnumProfil::PUER) {
$groups[] = 'puericultrice';
}
// Tableau imbriqué : valider les 2 groupes en même temps (=remonter toutes les erreurs)
return array($groups);
}
}