src/Entity/Admin.php line 98

  1. <?php
  2. namespace App\Entity;
  3. use Doctrine\ORM\Mapping as ORM,
  4.     Doctrine\DBAL\Types\Types,
  5.     Doctrine\Common\Collections\Collection,
  6.     Doctrine\Common\Collections\ArrayCollection;
  7. use Symfony\Component\Serializer\Annotation\Groups,
  8.     Symfony\Component\Validator\Constraints as Assert,
  9.     Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface,
  10.     Symfony\Component\Validator\Constraint;
  11. use ApiPlatform\Metadata\ApiResource,
  12.     ApiPlatform\Metadata\ApiProperty,
  13.     ApiPlatform\Metadata\Get,
  14.     ApiPlatform\Metadata\GetCollection,
  15.     ApiPlatform\Metadata\Post,
  16.     ApiPlatform\Metadata\Put,
  17.     ApiPlatform\Metadata\Delete,
  18.     ApiPlatform\Metadata\ApiFilter,
  19.     ApiPlatform\Doctrine\Orm\Filter\SearchFilter,
  20.     ApiPlatform\Doctrine\Orm\Filter\BooleanFilter,
  21.     ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
  22. use App\Entity\Trait\IdTrait,
  23.     App\Entity\Trait\UuidTrait,
  24.     App\Entity\Trait\TimestampableTrait,
  25.     App\Entity\Trait\StatTrait,
  26.     App\Entity\Interface\AdminInterface,
  27.     App\Entity\Interface\PasswordResetableInterface,
  28.     App\Repository\AdminRepository;
  29. use App\Lib\Roles,
  30.     App\Lib\ModuleRoles,
  31.     App\Lib\Actions,
  32.     App\StateProvider\AdminCollectionProvider,
  33.     App\StateProvider\ProfileProvider,
  34.     App\Security\Voter\Resource\ProfileVoter,
  35.     App\Validator\Constraints as CustomAssert,
  36.     App\Enum\JournalAdminColumn,
  37.     App\Enum\JournalArticleAdminColumn,
  38.     App\Enum\JournalArticleAdminStatsColumn,
  39.     App\Doctrine\DBAL\Types\JournalAdminColumnsType,
  40.     App\Doctrine\DBAL\Types\JournalArticleAdminColumnsType,
  41.     App\Doctrine\DBAL\Types\JournalArticleAdminStatsColumnsType;
  42. #[ApiResource(
  43.     description'Administrators',
  44.     normalizationContext: ['groups' => ['read''read:' self::class]],
  45.     denormalizationContext: ['groups' => ['write']],
  46.     order: ['createdAt' => 'desc'],
  47.     operations: [
  48.         new GetCollection(
  49.             providerAdminCollectionProvider::class,
  50.             security'is_granted("' self::class . '")'
  51.         ),
  52.         new Post(
  53.             security'is_granted("' self::class . '")',
  54.             validationContext: ['groups' => [Constraint::DEFAULT_GROUP'post']],
  55.             securityPostDenormalize'is_granted("' Actions::CREATE '", object)'
  56.         ),
  57.         new Get(
  58.             security'is_granted("' self::class . '")',
  59.             securityPostDenormalize'is_granted("' Actions::VIEW '", object)'
  60.         ),
  61.         new Put(
  62.             security'is_granted("' self::class . '")',
  63.             securityPostDenormalize'is_granted("' Actions::EDIT '", object)'
  64.         ),
  65.         new Delete(
  66.             security'is_granted("' self::class . '")',
  67.             securityPostDenormalize'is_granted("' Actions::DELETE '", object)'
  68.         ),
  69.         new Get(
  70.             uriTemplate'/profile',
  71.             providerProfileProvider::class,
  72.             securityPostDenormalize'is_granted("' ProfileVoter::ATTRIBUTE '", object)'
  73.         ),
  74.         new Put(
  75.             uriTemplate'/profile',
  76.             providerProfileProvider::class,
  77.             denormalizationContext: ['groups' => ['profile']],
  78.             securityPostDenormalize'is_granted("' ProfileVoter::ATTRIBUTE '", object)',
  79.             validationContext: ['groups' => [Constraint::DEFAULT_GROUP'profile']]
  80.         ),
  81.     ],
  82.     extraProperties: ['standard_put' => false],
  83. )]
  84. #[ApiFilter(SearchFilter::class, properties: [
  85.     'email' => 'partial',
  86.     'fullName' => 'partial',
  87.     'group.title' => 'partial'
  88. ])]
  89. #[ApiFilter(BooleanFilter::class, properties: ['stat'])]
  90. #[ApiFilter(OrderFilter::class, properties: ['email''fullName''stat''createdAt''updatedAt'])]
  91. #[ORM\Entity(repositoryClassAdminRepository::class)]
  92. class Admin implements AdminInterfacePasswordResetableInterface
  93. {
  94.     use IdTrait,
  95.         UuidTrait,
  96.         TimestampableTrait,
  97.         StatTrait;
  98.     const MAX_PREVIOUS_PASSWORDS 5;
  99.     #[ApiProperty(description'Email')]
  100.     #[Groups(['read''write'])]
  101.     #[Assert\NotBlank]
  102.     #[Assert\Email]
  103.     #[ORM\Column(typeTypes::STRINGlength191uniquetrue)]
  104.     private string $email;
  105.     #[ApiProperty(description'Persisted password')]
  106.     #[ORM\Column(typeTypes::STRINGlength255)]
  107.     private ?string $password null;
  108.     #[ApiProperty(description'New password')]
  109.     #[Groups(['write''profile'])]
  110.     #[Assert\NotBlank(groups: ['post'])]
  111.     #[Assert\Length(max50)]
  112.     #[Assert\Regex(
  113.         pattern'/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-\.]).{8,}$/',
  114.         match: true,
  115.         message'Password must consist of at least 8 characters (upper case, lower case, number, special character)'
  116.     )]
  117.     #[Assert\Expression(
  118.         expression'!value || this.getCurrentPassword() !== null',
  119.         message'Current password is required.'
  120.     )]
  121.     #[Assert\NotCompromisedPassword]
  122.     #[CustomAssert\User\PreviousPasswords]
  123.     private ?string $rawPassword null;
  124.     #[ApiProperty(description'Current password')]
  125.     #[Groups(['profile'])]
  126.     #[CustomAssert\User\CurrentPassword(groups: ['profile'])]
  127.     private ?string $currentPassword null;
  128.     #[ApiProperty(description'Previous passwords')]
  129.     #[ORM\Column(typeTypes::JSON)]
  130.     private array $previousPasswords = [];
  131.     #[ApiProperty(description'Name and surname')]
  132.     #[Groups(['read''write''profile'])]
  133.     #[Assert\NotBlank]
  134.     #[Assert\Length(min3)]
  135.     #[ORM\Column(typeTypes::STRINGlength255)]
  136.     private string $fullName;
  137.     #[ApiProperty(description'Last activity')]
  138.     #[Groups(['read:' self::class])]
  139.     #[ORM\Column(typeTypes::DATETIME_IMMUTABLE)]
  140.     private \DateTimeImmutable $lastActivityAt;
  141.     #[ApiProperty(description'Reset password hash')]
  142.     #[ORM\Column(typeTypes::STRINGlength255nullabletrue)]
  143.     private ?string $resetPasswordHash null;
  144.     #[ApiProperty(description'Reset password hash generated at')]
  145.     #[ORM\Column(typeTypes::DATETIME_IMMUTABLEnullabletrue)]
  146.     private ?\DateTimeImmutable $resetPasswordHashGeneratedAt null;
  147.     #[ApiProperty(description'Is password reset required (account is inactive till reset)')]
  148.     #[Groups(['read:' self::class])]
  149.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => false])]
  150.     private bool $isPasswordResetRequired false;
  151.     #[ApiProperty(description'Is contextual hints visible')]
  152.     #[Groups(['read:' self::class, 'profile'])]
  153.     #[ORM\Column(typeTypes::BOOLEANoptions: ['default' => true])]
  154.     private bool $isContextualHintsVisible true;
  155.     #[ApiProperty(description'Visible journal admin columns'writableLinkfalse)]
  156.     #[Groups(['read:' self::class, 'write''profile'])]
  157.     #[Assert\Choice(callback: [JournalAdminColumn::class, 'cases'], multipletrue)]
  158.     #[Assert\Unique]
  159.     #[Assert\Count(min1)]
  160.     #[ORM\Column(typeJournalAdminColumnsType::NAME)]
  161.     private array $visibleJournalAdminColumns = [
  162.         JournalAdminColumn::NO,
  163.         JournalAdminColumn::WORKINGTITLE,
  164.         JournalAdminColumn::AFFILIATION,
  165.         JournalAdminColumn::PERIODICITY,
  166.         JournalAdminColumn::STAT
  167.     ];
  168.     #[ApiProperty(description'Visible journal article admin columns'writableLinkfalse)]
  169.     #[Groups(['read:' self::class, 'write''profile'])]
  170.     #[Assert\Choice(callback: [JournalArticleAdminColumn::class, 'cases'], multipletrue)]
  171.     #[Assert\Unique]
  172.     #[Assert\Count(min1)]
  173.     #[ORM\Column(typeJournalArticleAdminColumnsType::NAME)]
  174.     private array $visibleJournalArticleAdminColumns = [
  175.         JournalArticleAdminColumn::NO,
  176.         JournalArticleAdminColumn::TITLE,
  177.         JournalArticleAdminColumn::ISSUE,
  178.         JournalArticleAdminColumn::TYPE,
  179.         JournalArticleAdminColumn::SECTION,
  180.         JournalArticleAdminColumn::UPDATED_AT,
  181.         JournalArticleAdminColumn::DOI,
  182.         JournalArticleAdminColumn::FILES,
  183.         JournalArticleAdminColumn::STAT
  184.     ];
  185.     #[ApiProperty(description'Visible journal article admin stats columns'writableLinkfalse)]
  186.     #[Groups(['read:' self::class, 'write''profile'])]
  187.     #[Assert\Choice(callback: [JournalArticleAdminStatsColumn::class, 'cases'], multipletrue)]
  188.     #[Assert\Unique]
  189.     // #[Assert\Count(min: 1)]
  190.     #[ORM\Column(typeJournalArticleAdminStatsColumnsType::NAME)]
  191.     private array $visibleJournalArticleAdminStatsColumns = [
  192.         JournalArticleAdminStatsColumn::NATIVE_TITLE,
  193.         JournalArticleAdminStatsColumn::STATS_AMOUNT_OF_DOWNLOADS,
  194.         JournalArticleAdminStatsColumn::JOURNAL,
  195.         JournalArticleAdminStatsColumn::TYPE,
  196.         JournalArticleAdminStatsColumn::AUTHORS,
  197.         JournalArticleAdminStatsColumn::AUTHORS_AFFILIATIONS
  198.     ];
  199.     #[ApiProperty(description'Administration group'writableLinkfalse)]
  200.     #[Groups(['read:' self::class, 'write'])]
  201.     #[ORM\ManyToOne(targetEntityAdminGroup::class, inversedBy'members')]
  202.     #[ORM\JoinColumn(onDelete'set null')]
  203.     private ?AdminGroup $group null;
  204.     #[ApiProperty(description'Registry entries')]
  205.     #[ORM\OneToMany(targetEntityRegistryEntry::class, mappedBy'admin')]
  206.     private Collection $registryEntries;
  207.     public function __construct(string $emailstring $fullName)
  208.     {
  209.         $this->setUuid();
  210.         $this->email $email;
  211.         $this->fullName $fullName;
  212.         $this->lastActivityAt = new \DateTimeImmutable();
  213.         $this->registryEntries = new ArrayCollection();
  214.         $this->createdAt = new \DateTimeImmutable();
  215.         $this->updatedAt = new \DateTimeImmutable();
  216.     }
  217.     public function getEmail(): string
  218.     {
  219.         return $this->email;
  220.     }
  221.     public function setEmail(string $email): self
  222.     {
  223.         $this->email $email;
  224.         return $this;
  225.     }
  226.     public function getPassword(): string
  227.     {
  228.         return $this->password;
  229.     }
  230.     public function getRawPassword(): ?string
  231.     {
  232.         return $this->rawPassword;
  233.     }
  234.     public function setRawPassword(
  235.         ?string $rawPassword,
  236.         UserPasswordHasherInterface $encoder,
  237.         Admin $setBy
  238.     ): self {
  239.         if (! $rawPassword) {
  240.             return $this;
  241.         }
  242.         $this->rawPassword $rawPassword;
  243.         $this->password $encoder->hashPassword($this$rawPassword);
  244.         if (count($this->previousPasswords) >= self::MAX_PREVIOUS_PASSWORDS) {
  245.             $this->previousPasswords array_slice($this->previousPasswords1);
  246.         }
  247.         $this->previousPasswords[] = password_hash($rawPasswordPASSWORD_DEFAULT);
  248.         $this->isPasswordResetRequired $setBy->getUuid() !== $this->getUuid();
  249.         $this->resetPasswordHash null;
  250.         $this->resetPasswordHashGeneratedAt null;
  251.         return $this;
  252.     }
  253.     public function setHashedPassword(string $hashedPassword): self
  254.     {
  255.         $this->password $hashedPassword;
  256.         return $this;
  257.     }
  258.     public function getCurrentPassword(): ?string
  259.     {
  260.         return $this->currentPassword;
  261.     }
  262.     public function setCurrentPassword(?string $currentPassword): self
  263.     {
  264.         $this->currentPassword $currentPassword;
  265.         return $this;
  266.     }
  267.     public function getPreviousPasswords(): array
  268.     {
  269.         return $this->previousPasswords;
  270.     }
  271.     public function getFullName(): string
  272.     {
  273.         return $this->fullName;
  274.     }
  275.     public function setFullName(string $fullName): self
  276.     {
  277.         $this->fullName $fullName;
  278.         return $this;
  279.     }
  280.     public function getLastActivityAt(): \DateTimeImmutable
  281.     {
  282.         return $this->lastActivityAt;
  283.     }
  284.     public function setLastActivityAt(\DateTimeImmutable $lastActivityAt): self
  285.     {
  286.         $this->lastActivityAt $lastActivityAt;
  287.         return $this;
  288.     }
  289.     public function getUsername(): string
  290.     {
  291.         return $this->email;
  292.     }
  293.     public function getUserIdentifier(): string
  294.     {
  295.         return $this->email;
  296.     }
  297.     public function getRoles(): array
  298.     {
  299.         $roles = [Roles::ROLE_ADMIN];
  300.         if (! $this->group?->getStat()) {
  301.             return $roles;
  302.         }
  303.         if ($this->group->getIsOpenform()) {
  304.             $roles[] = Roles::ROLE_OPENFORM;
  305.             return $roles;
  306.         }
  307.         if ($this->group->getIsSuperAdmin()) {
  308.             $roles[] = Roles::ROLE_SUPER_ADMIN;
  309.             return $roles;
  310.         }
  311.         foreach ($this->group->getFullyQualifiedResourceAccess() as $resourceAccess) {
  312.             switch(true) {
  313.                 case defined(ModuleRoles::class . '::ROLE_MODULE_' strtoupper($resourceAccess)):
  314.                     return constant(ModuleRoles::class . '::ROLE_MODULE_' strtoupper($resourceAccess));
  315.                 case class_exists($resourceAccess):
  316.                     $roles[] = str_replace('\\'''$resourceAccess);
  317.                 default:
  318.                     throw new \Exception('Resource access is invalid.');
  319.             }
  320.             $roles[] = str_replace('\\'''$resourceAccess);
  321.         }
  322.         return $roles;
  323.     }
  324.     public function getSalt(): ?string
  325.     {
  326.         return null;
  327.     }
  328.     public function eraseCredentials(): void
  329.     {
  330.         return;
  331.     }
  332.     public function setResetPasswordHash(string $hash): self
  333.     {
  334.         $this->resetPasswordHash $hash;
  335.         $this->resetPasswordHashGeneratedAt = new \DateTimeImmutable();
  336.         return $this;
  337.     }
  338.     public function getResetPasswordHash(): ?string
  339.     {
  340.         return $this->resetPasswordHash;
  341.     }
  342.     public function getResetPasswordHashGeneratedAt(): ?\DateTimeImmutable
  343.     {
  344.         return $this->resetPasswordHashGeneratedAt;
  345.     }
  346.     public function getIsPasswordResetRequired(): bool
  347.     {
  348.         return $this->isPasswordResetRequired;
  349.     }
  350.     public function setIsPasswordResetRequired(bool $isPasswordResetRequired): self
  351.     {
  352.         $this->isPasswordResetRequired $isPasswordResetRequired;
  353.         return $this;
  354.     }
  355.     public function getIsContextualHintsVisible(): bool
  356.     {
  357.         return $this->isContextualHintsVisible;
  358.     }
  359.     public function setIsContextualHintsVisible(bool $isContextualHintsVisible): self
  360.     {
  361.         $this->isContextualHintsVisible $isContextualHintsVisible;
  362.         return $this;
  363.     }
  364.     public function getVisibleJournalAdminColumns(): array
  365.     {
  366.         return $this->visibleJournalAdminColumns;
  367.     }
  368.     public function setVisibleJournalAdminColumns(array $visibleJournalAdminColumns): self
  369.     {
  370.         $this->visibleJournalAdminColumns $visibleJournalAdminColumns;
  371.         return $this;
  372.     }
  373.     public function getVisibleJournalArticleAdminColumns(): array
  374.     {
  375.         return $this->visibleJournalArticleAdminColumns;
  376.     }
  377.     public function setVisibleJournalArticleAdminColumns(array $visibleJournalArticleAdminColumns): self
  378.     {
  379.         $this->visibleJournalArticleAdminColumns $visibleJournalArticleAdminColumns;
  380.         return $this;
  381.     }
  382.     public function getVisibleJournalArticleAdminStatsColumns(): array
  383.     {
  384.         return $this->visibleJournalArticleAdminStatsColumns;
  385.     }
  386.     public function setVisibleJournalArticleAdminStatsColumns(array $visibleJournalArticleAdminStatsColumns): self
  387.     {
  388.         $this->visibleJournalArticleAdminStatsColumns $visibleJournalArticleAdminStatsColumns;
  389.         return $this;
  390.     }
  391.     public function getGroup(): ?AdminGroup
  392.     {
  393.         return $this->group;
  394.     }
  395.     public function setGroup(?AdminGroup $group): self
  396.     {
  397.         $this->group $group;
  398.         return $this;
  399.     }
  400.     public function getRegistryEntries(): Collection
  401.     {
  402.         return $this->registryEntries;
  403.     }
  404.     public function addRegistryEntry(RegistryEntry $registryEntry): self
  405.     {
  406.         if ($this->registryEntries->contains($registryEntry)) {
  407.             return $this;
  408.         }
  409.         $this->registryEntries[] = $registryEntry;
  410.         return $this;
  411.     }
  412.     public function removeRegistryEntry(RegistryEntry $registryEntry): self
  413.     {
  414.         $this->registryEntries->removeElement($registryEntry);
  415.         return $this;
  416.     }
  417. }