<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Controller\ApiUserController;
use App\Repository\UserRepository;
use App\Entity\GroupMatchSchedule;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(
normalizationContext: [
'groups' => ['read:user:collection']
],
denormalizationContext: [
'groups' => ['write:user:admin']
],
collectionOperations: [
'get',
'post' => [
'security' => "is_granted('ROLE_ADMIN')",
'security_message' => 'Seul un administrateur peut créer un utilisateur via l’API.',
],
'current_user_infos' => [
'method' => 'GET',
'path' => '/users/me',
'controller' => ApiUserController::class,
],
],
itemOperations: [
'get',
]
)]
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
#[UniqueEntity(fields: ['username'], message: 'There is already an account with this username')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
private const AVAILABILITY_SLOTS = ['now', 'tonight', 'weekend'];
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
#[Groups([
'read:user:collection',
'read:message:collection',
'read:relation:collection',
'read:notification:collection',
'read:post:collection',
'read:placeComment:collection',
'read:groupUser:collection',
'read:event:collection',
'read:reservation:collection',
'read:conversation:collection',
])]
private $id;
#[Groups([
'read:user:collection',
'read:message:collection',
'read:post:collection',
'read:placeComment:collection',
'read:relation:collection',
'read:notification:collection',
'read:groupUser:collection',
'read:event:collection',
'read:reservation:collection',
'read:conversation:collection',
'write:user:admin',
])]
#[Assert\Length(min: 10, max: 50)]
#[ORM\Column(type: 'string', length: 180, unique: true)]
private $email;
#[Groups([
'read:user:collection',
'read:post:collection',
'read:message:collection',
'read:relation:collection',
'read:notification:collection',
'read:event:collection',
'read:reservation:collection',
])]
#[ORM\Column(type: 'string', length: 500, nullable: true)]
private $image;
#[Groups([
'read:user:collection',
'read:post:collection',
'read:message:collection',
'read:relation:collection',
'read:notification:collection',
'read:event:collection',
'read:reservation:collection',
])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $backgroundImage = null;
#[Groups([
'read:user:collection',
'read:message:collection',
'read:post:collection',
'read:placeComment:collection',
'read:relation:collection',
'read:notification:collection',
'read:groupUser:collection',
'read:event:collection',
'read:reservation:collection',
'write:user:admin',
])]
#[ORM\Column(type: 'string', length: 50)]
private $username;
#[ORM\Column(type: 'json')]
private $roles = [];
#[Assert\Length(min: 6, max: 50)]
#[ORM\Column(type: 'string')]
#[Groups(['write:user:admin'])]
private $password;
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $createdAt = null;
#[Groups([
'read:user:collection',
'read:message:collection',
'read:relation:collection',
])]
#[ORM\Column(type: 'string', length: 500, nullable: true)]
private ?string $bioShort = null;
#[ORM\OneToMany(mappedBy: 'sourceUser', targetEntity: Message::class)]
private $sourceMessages;
#[ORM\ManyToMany(targetEntity: GroupUser::class, mappedBy: 'concernedUsers', cascade: ['persist', 'remove'])]
private $groupUsers;
#[ORM\OneToOne(mappedBy: 'user', targetEntity: Flux::class, cascade: ['persist', 'remove'])]
private $flux;
#[ORM\OneToMany(mappedBy: 'initialUser', targetEntity: Activity::class)]
private $sourceAtivities;
#[ORM\ManyToMany(targetEntity: Activity::class)]
private $targetActivities;
#[ORM\OneToMany(mappedBy: 'sourceUser', targetEntity: Notification::class)]
private $sourceNotifications;
#[ORM\OneToMany(mappedBy: 'targetUser', targetEntity: Notification::class)]
private $targetNotifications;
#[ORM\Column(type: 'string', length: 500, nullable: true)]
private $authToken;
#[ORM\OneToOne(targetEntity: GroupUser::class, cascade: ['persist', 'remove'])]
private $relationGroupUser;
#[ORM\Column(type: 'text', nullable: true)]
private $payData;
#[ORM\Column(nullable: true)]
private ?bool $isActif = null;
#[ORM\Column(type: 'json', nullable: true)]
private ?array $availabilitySlots = [];
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $availabilityUpdatedAt = null;
#[ORM\OneToMany(mappedBy: 'sourceUser', targetEntity: Payment::class)]
private Collection $payments;
#[ORM\Column(nullable: true)]
private ?bool $isConfirmed = null;
#[Groups([
'read:user:collection',
'read:message:collection',
'read:relation:collection',
'read:notification:collection',
'read:conversation:collection',
])]
#[ORM\Column(type: 'json', nullable: true)]
private ?array $requirements = [];
#[Groups([
'read:user:collection',
'read:message:collection',
'read:relation:collection',
'read:notification:collection',
'read:conversation:collection',
])]
#[ORM\Column(type: 'boolean', options: ['default' => false])]
private bool $isPremium = false;
#[ORM\Column(nullable: true)]
private ?array $position = null;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
private bool $locationOptIn = false;
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $locationUpdatedAt = null;
#[ORM\OneToMany(mappedBy: 'user', targetEntity: Reservation::class, orphanRemoval: true)]
private Collection $reservations;
#[ORM\OneToOne(mappedBy: 'owner', cascade: ['persist', 'remove'])]
private ?Account $account = null;
#[ORM\OneToMany(mappedBy: 'organizer', targetEntity: GroupMatchSchedule::class)]
private Collection $groupMatchSchedules;
#[ORM\Column(type: 'float', nullable: true)]
private ?float $reliabilityScore = null;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
private bool $ageVerified = false;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
private bool $identityVerified = false;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
private bool $incomeVerified = false;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
private bool $mfaEnabled = false;
#[ORM\Column(type: 'string', length: 30, nullable: true)]
private ?string $mfaMethod = null;
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $mfaVerifiedAt = null;
public function __construct()
{
$this->roles = ['ROLE_USER'];
$this->sourceMessages = new ArrayCollection();
$this->groupUsers = new ArrayCollection();
$this->sourceAtivities = new ArrayCollection();
$this->targetActivities = new ArrayCollection();
$this->sourceNotifications = new ArrayCollection();
$this->targetNotifications = new ArrayCollection();
$this->authToken = hash_hmac('sha512', random_bytes(150), random_bytes(150), false);
$this->payments = new ArrayCollection();
$this->reservations = new ArrayCollection();
$this->createdAt = new \DateTimeImmutable();
$this->requirements = [];
$this->isPremium = false;
$this->groupMatchSchedules = new ArrayCollection();
$this->locationOptIn = false;
$this->mfaEnabled = false;
}
public function getReliabilityScore(): ?float
{
return $this->reliabilityScore;
}
public function setReliabilityScore(?float $reliabilityScore): self
{
$this->reliabilityScore = $reliabilityScore !== null ? max(0, min(100, $reliabilityScore)) : null;
return $this;
}
public function isAgeVerified(): bool
{
return $this->ageVerified;
}
public function setAgeVerified(bool $ageVerified): self
{
$this->ageVerified = $ageVerified;
return $this;
}
public function isIdentityVerified(): bool
{
return $this->identityVerified;
}
public function setIdentityVerified(bool $identityVerified): self
{
$this->identityVerified = $identityVerified;
return $this;
}
public function isIncomeVerified(): bool
{
return $this->incomeVerified;
}
public function setIncomeVerified(bool $incomeVerified): self
{
$this->incomeVerified = $incomeVerified;
return $this;
}
public function isMfaEnabled(): bool
{
return $this->mfaEnabled;
}
public function setMfaEnabled(bool $mfaEnabled): self
{
$this->mfaEnabled = $mfaEnabled;
return $this;
}
public function getMfaMethod(): ?string
{
return $this->mfaMethod;
}
public function setMfaMethod(?string $mfaMethod): self
{
$this->mfaMethod = $mfaMethod;
return $this;
}
public function getMfaVerifiedAt(): ?\DateTimeImmutable
{
return $this->mfaVerifiedAt;
}
public function setMfaVerifiedAt(?\DateTimeImmutable $mfaVerifiedAt): self
{
$this->mfaVerifiedAt = $mfaVerifiedAt;
return $this;
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* @return Collection<int, GroupMatchSchedule>
*/
public function getGroupMatchSchedules(): Collection
{
return $this->groupMatchSchedules;
}
public function addGroupMatchSchedule(GroupMatchSchedule $schedule): static
{
if (!$this->groupMatchSchedules->contains($schedule)) {
$this->groupMatchSchedules->add($schedule);
$schedule->setOrganizer($this);
}
return $this;
}
public function removeGroupMatchSchedule(GroupMatchSchedule $schedule): static
{
if ($this->groupMatchSchedules->removeElement($schedule)) {
if ($schedule->getOrganizer() === $this) {
$schedule->setOrganizer(null);
}
}
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->username;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->createdAt;
}
public function getBioShort(): ?string
{
return $this->bioShort;
}
public function setBioShort(?string $bioShort): self
{
$this->bioShort = $bioShort;
return $this;
}
public function setCreatedAt(?\DateTimeImmutable $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Returning a salt is only needed, if you are not using a modern
* hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
*
* @see UserInterface
*/
public function getSalt(): ?string
{
return null;
}
/**
* @see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* @return Collection<int, Message>
*/
public function getSourceMessages(): Collection
{
return $this->sourceMessages;
}
public function addSourceMessage(Message $sourceMessage): self
{
if (!$this->sourceMessages->contains($sourceMessage)) {
$this->sourceMessages[] = $sourceMessage;
$sourceMessage->setSourceUser($this);
}
return $this;
}
public function removeSourceMessage(Message $sourceMessage): self
{
if ($this->sourceMessages->removeElement($sourceMessage)) {
// set the owning side to null (unless already changed)
if ($sourceMessage->getSourceUser() === $this) {
$sourceMessage->setSourceUser(null);
}
}
return $this;
}
/**
* @return Collection<int, GroupUser>
*/
public function getGroupUsers(): Collection
{
return $this->groupUsers;
}
public function addGroupUser(GroupUser $groupUser): self
{
if (!$this->groupUsers->contains($groupUser)) {
$this->groupUsers[] = $groupUser;
$groupUser->addConcernedUser($this);
}
return $this;
}
public function removeGroupUser(GroupUser $groupUser): self
{
if ($this->groupUsers->removeElement($groupUser)) {
$groupUser->removeConcernedUser($this);
}
return $this;
}
public function getFlux(): ?Flux
{
return $this->flux;
}
public function setFlux(?Flux $flux): self
{
// unset the owning side of the relation if necessary
if ($flux === null && $this->flux !== null) {
$this->flux->setUser(null);
}
// set the owning side of the relation if necessary
if ($flux !== null && $flux->getUser() !== $this) {
$flux->setUser($this);
}
$this->flux = $flux;
return $this;
}
/**
* @return Collection<int, Activity>
*/
public function getSourceAtivities(): Collection
{
return $this->sourceAtivities;
}
public function addSourceAtivity(Activity $sourceAtivity): self
{
if (!$this->sourceAtivities->contains($sourceAtivity)) {
$this->sourceAtivities[] = $sourceAtivity;
$sourceAtivity->setInitialUser($this);
}
return $this;
}
public function removeSourceAtivity(Activity $sourceAtivity): self
{
if ($this->sourceAtivities->removeElement($sourceAtivity)) {
// set the owning side to null (unless already changed)
if ($sourceAtivity->getInitialUser() === $this) {
$sourceAtivity->setInitialUser(null);
}
}
return $this;
}
/**
* @return Collection<int, Activity>
*/
public function getTargetActivities(): Collection
{
return $this->targetActivities;
}
public function addTargetActivity(Activity $targetActivity): self
{
if (!$this->targetActivities->contains($targetActivity)) {
$this->targetActivities[] = $targetActivity;
// $targetActivity->addTargetUser($this);
}
return $this;
}
/** TODO : Retiser du groupeUser de l'activité (avec une requete SQL dans le Repository) */
// TODO : Retiser du groupeUser de l'activité (avec une requete SQL dans le Repository)
// public function removeTargetActivity(Activity $targetActivity): self
// {
// if ($this->targetActivities->removeElement($targetActivity)) {
// $targetActivity->removeTargetUser($this);
// }
// return $this;
// }
/**
* @return Collection<int, Notification>
*/
public function getSourceNotifications(): Collection
{
return $this->sourceNotifications;
}
public function addSourceNotification(Notification $sourceNotification): self
{
if (!$this->sourceNotifications->contains($sourceNotification)) {
$this->sourceNotifications[] = $sourceNotification;
$sourceNotification->setSourceUser($this);
}
return $this;
}
public function removeSourceNotification(Notification $sourceNotification): self
{
if ($this->sourceNotifications->removeElement($sourceNotification)) {
// set the owning side to null (unless already changed)
if ($sourceNotification->getSourceUser() === $this) {
$sourceNotification->setSourceUser(null);
}
}
return $this;
}
/**
* @return Collection<int, Notification>
*/
public function getTargetNotifications(): Collection
{
return $this->targetNotifications;
}
public function addTargetNotification(Notification $targetNotification): self
{
if (!$this->targetNotifications->contains($targetNotification)) {
$this->targetNotifications[] = $targetNotification;
$targetNotification->setTargetUser($this);
}
return $this;
}
public function removeTargetNotification(Notification $targetNotification): self
{
if ($this->targetNotifications->removeElement($targetNotification)) {
// set the owning side to null (unless already changed)
if ($targetNotification->getTargetUser() === $this) {
$targetNotification->setTargetUser(null);
}
}
return $this;
}
public function getAuthToken(): ?string
{
return $this->authToken;
}
public function setAuthToken(?string $authToken): self
{
$this->authToken = $authToken;
return $this;
}
public function getImage(): ?string
{
return $this->image;
}
public function setImage(?string $image): self
{
$this->image = $image;
return $this;
}
public function getRelationGroupUser(): ?GroupUser
{
return $this->relationGroupUser;
}
public function setRelationGroupUser(?GroupUser $relationGroupUser): self
{
$this->relationGroupUser = $relationGroupUser;
return $this;
}
public function getUsername(): string
{
return $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getPayData(): ?string
{
return $this->payData;
}
public function setPayData(?string $payData): self
{
$this->payData = $payData;
return $this;
}
public function isIsActif(): ?bool
{
return $this->isActif;
}
public function setIsActif(?bool $isActif): static
{
$this->isActif = $isActif;
return $this;
}
/**
* @return array<int, string>
*/
public function getAvailabilitySlots(): array
{
return $this->normalizeAvailabilitySlots($this->availabilitySlots ?? []);
}
/**
* @param array<int, string> $availabilitySlots
*/
public function setAvailabilitySlots(array $availabilitySlots): self
{
$this->availabilitySlots = $this->normalizeAvailabilitySlots($availabilitySlots);
$this->availabilityUpdatedAt = new \DateTimeImmutable();
return $this;
}
public function getAvailabilityUpdatedAt(): ?\DateTimeImmutable
{
return $this->availabilityUpdatedAt;
}
/**
* @param array<int, string> $slots
* @return array<int, string>
*/
private function normalizeAvailabilitySlots(array $slots): array
{
$normalized = [];
foreach ($slots as $slot) {
$value = mb_strtolower(trim((string) $slot));
if ($value !== '' && in_array($value, self::AVAILABILITY_SLOTS, true)) {
$normalized[] = $value;
}
}
$normalized = array_values(array_unique($normalized));
if (empty($normalized)) {
return [];
}
$order = array_flip(self::AVAILABILITY_SLOTS);
usort($normalized, static fn (string $left, string $right): int => $order[$left] <=> $order[$right]);
return $normalized;
}
/**
* @return Collection<int, Payment>
*/
public function getPayments(): Collection
{
return $this->payments;
}
public function addPayment(Payment $payment): static
{
if (!$this->payments->contains($payment)) {
$this->payments->add($payment);
$payment->setSourceUser($this);
}
return $this;
}
public function removePayment(Payment $payment): static
{
if ($this->payments->removeElement($payment)) {
// set the owning side to null (unless already changed)
if ($payment->getSourceUser() === $this) {
$payment->setSourceUser(null);
}
}
return $this;
}
public function isIsConfirmed(): ?bool
{
return $this->isConfirmed;
}
public function setIsConfirmed(?bool $isConfirmed): static
{
$this->isConfirmed = $isConfirmed;
return $this;
}
public function getPosition(): ?array
{
return $this->position;
}
public function setPosition(?array $position): static
{
$this->position = $position;
return $this;
}
public function isLocationOptIn(): bool
{
return $this->locationOptIn;
}
public function setLocationOptIn(bool $locationOptIn): self
{
$this->locationOptIn = $locationOptIn;
return $this;
}
public function getLocationUpdatedAt(): ?\DateTimeImmutable
{
return $this->locationUpdatedAt;
}
public function setLocationUpdatedAt(?\DateTimeImmutable $locationUpdatedAt): self
{
$this->locationUpdatedAt = $locationUpdatedAt;
return $this;
}
public function getRequirements(): array
{
return $this->requirements ?? [];
}
public function setRequirements(?array $requirements): static
{
$this->requirements = $requirements ?? [];
return $this;
}
public function isPremium(): bool
{
return (bool) $this->isPremium;
}
public function setIsPremium(bool $isPremium): static
{
$this->isPremium = $isPremium;
return $this;
}
/**
* @return Collection<int, Reservation>
*/
public function getReservations(): Collection
{
return $this->reservations;
}
public function addReservation(Reservation $reservation): static
{
if (!$this->reservations->contains($reservation)) {
$this->reservations->add($reservation);
$reservation->setUser($this);
}
return $this;
}
public function removeReservation(Reservation $reservation): static
{
if ($this->reservations->removeElement($reservation)) {
// set the owning side to null (unless already changed)
if ($reservation->getUser() === $this) {
$reservation->setUser(null);
}
}
return $this;
}
public function getBackgroundImage(): ?string
{
return $this->backgroundImage;
}
public function setBackgroundImage(?string $backgroundImage): static
{
$this->backgroundImage = $backgroundImage;
return $this;
}
public function getAccount(): ?Account
{
return $this->account;
}
public function setAccount(?Account $account): static
{
// unset the owning side of the relation if necessary
if ($account === null && $this->account !== null) {
$this->account->setOwner(null);
}
// set the owning side of the relation if necessary
if ($account !== null && $account->getOwner() !== $this) {
$account->setOwner($this);
}
$this->account = $account;
return $this;
}
}