src/Entity/Admin.php line 126
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM,
Doctrine\DBAL\Types\Types,
Doctrine\Common\Collections\Collection,
Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Serializer\Annotation\Groups,
Symfony\Component\Validator\Constraints as Assert,
Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface,
Symfony\Component\Validator\Constraint;
use ApiPlatform\Metadata\ApiResource,
ApiPlatform\Metadata\ApiProperty,
ApiPlatform\Metadata\Get,
ApiPlatform\Metadata\GetCollection,
ApiPlatform\Metadata\Post,
ApiPlatform\Metadata\Put,
ApiPlatform\Metadata\Delete,
ApiPlatform\Metadata\ApiFilter,
ApiPlatform\Doctrine\Orm\Filter\SearchFilter,
ApiPlatform\Doctrine\Orm\Filter\BooleanFilter,
ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use App\Doctrine\DBAL\Types\JournalAdminStatsColumnsType;
use App\Doctrine\DBAL\Types\AuthorAdminStatsColumnsType;
use App\Doctrine\DBAL\Types\AdminStatsColumnsType;
use App\Entity\Trait\IdTrait,
App\Entity\Trait\UuidTrait,
App\Entity\Trait\TimestampableTrait,
App\Entity\Trait\StatTrait,
App\Entity\Interface\AdminInterface,
App\Entity\Interface\PasswordResetableInterface,
App\Repository\AdminRepository;
use App\Enum\JournalAdminStatsColumn;
use App\Enum\AuthorAdminStatsColumn;
use App\Enum\AdminStatsColumn;
use App\Lib\Roles,
App\Lib\ModuleRoles,
App\Lib\Actions,
App\StateProvider\AdminCollectionProvider,
App\StateProvider\ProfileProvider,
App\Security\Voter\Resource\ProfileVoter,
App\Validator\Constraints as CustomAssert,
App\Enum\JournalAdminColumn,
App\Enum\JournalArticleAdminColumn,
App\Enum\JournalArticleAdminStatsColumn,
App\Doctrine\DBAL\Types\JournalAdminColumnsType,
App\Doctrine\DBAL\Types\JournalArticleAdminColumnsType,
App\Doctrine\DBAL\Types\JournalArticleAdminStatsColumnsType;
use App\StateProvider\StatsAdminProvider;
use App\Controller\Stats\StatsAdminXlsxGenerator;
use App\Filter\OrderByUnmapped;
#[ApiResource(
description: 'Administrators',
normalizationContext: ['groups' => ['read', 'read:' . self::class]],
denormalizationContext: ['groups' => ['write']],
order: ['createdAt' => 'desc'],
operations: [
new GetCollection(
provider: AdminCollectionProvider::class,
security: 'is_granted("' . self::class . '")'
),
new GetCollection(
name: 'get_admin_stats',
uriTemplate: '/admins/stats',
provider: StatsAdminProvider::class,
security: 'is_granted("ROLE_MODULE_STATS")',
normalizationContext: ['groups' => ['read:stats']],
paginationMaximumItemsPerPage: 500
),
new GetCollection(
name: 'get_admin_stats_xlsx',
uriTemplate: '/admins/stats/xlsx',
provider: StatsAdminProvider::class,
controller: StatsAdminXlsxGenerator::class,
security: 'is_granted("ROLE_MODULE_STATS")',
),
new Post(
security: 'is_granted("' . self::class . '")',
validationContext: ['groups' => [Constraint::DEFAULT_GROUP, 'post']],
securityPostDenormalize: 'is_granted("' . Actions::CREATE . '", object)'
),
new Get(
security: 'is_granted("' . self::class . '")',
securityPostDenormalize: 'is_granted("' . Actions::VIEW . '", object)'
),
new Put(
security: 'is_granted("' . self::class . '")',
securityPostDenormalize: 'is_granted("' . Actions::EDIT . '", object)'
),
new Delete(
security: 'is_granted("' . self::class . '")',
securityPostDenormalize: 'is_granted("' . Actions::DELETE . '", object)'
),
new Get(
uriTemplate: '/profile',
provider: ProfileProvider::class,
securityPostDenormalize: 'is_granted("' . ProfileVoter::ATTRIBUTE . '", object)'
),
new Put(
uriTemplate: '/profile',
provider: ProfileProvider::class,
denormalizationContext: ['groups' => ['profile']],
securityPostDenormalize: 'is_granted("' . ProfileVoter::ATTRIBUTE . '", object)',
validationContext: ['groups' => [Constraint::DEFAULT_GROUP, 'profile']]
),
],
extraProperties: ['standard_put' => false],
)]
#[ApiFilter(SearchFilter::class, properties: [
'email' => 'partial',
'fullName' => 'partial',
'group.title' => 'partial'
])]
#[ApiFilter(BooleanFilter::class, properties: ['stat'])]
#[ApiFilter(OrderFilter::class, properties: ['email', 'fullName', 'stat', 'createdAt', 'updatedAt'])]
#[ApiFilter(OrderByUnmapped::class, properties: ['statsAmountOfJournals'])]
#[ORM\Entity(repositoryClass: AdminRepository::class)]
class Admin implements AdminInterface, PasswordResetableInterface
{
use IdTrait,
UuidTrait,
TimestampableTrait,
StatTrait;
const MAX_PREVIOUS_PASSWORDS = 5;
#[ApiProperty(description: 'Email')]
#[Groups(['read', 'write'])]
#[Assert\NotBlank]
#[Assert\Email]
#[ORM\Column(type: Types::STRING, length: 191, unique: true)]
private string $email;
#[ApiProperty(description: 'Persisted password')]
#[ORM\Column(type: Types::STRING, length: 255)]
private ?string $password = null;
#[ApiProperty(description: 'New password')]
#[Groups(['write', 'profile'])]
#[Assert\NotBlank(groups: ['post'])]
#[Assert\Length(max: 50)]
#[Assert\Regex(
pattern: '/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-\.]).{8,}$/',
match: true,
message: 'Password must consist of at least 8 characters (upper case, lower case, number, special character)'
)]
#[Assert\Expression(
expression: '!value || this.getCurrentPassword() !== null',
message: 'Current password is required.'
)]
#[Assert\NotCompromisedPassword]
#[CustomAssert\User\PreviousPasswords]
private ?string $rawPassword = null;
#[ApiProperty(description: 'Current password')]
#[Groups(['profile'])]
#[CustomAssert\User\CurrentPassword(groups: ['profile'])]
private ?string $currentPassword = null;
#[ApiProperty(description: 'Previous passwords')]
#[ORM\Column(type: Types::JSON)]
private array $previousPasswords = [];
#[ApiProperty(description: 'Name and surname')]
#[Groups(['read', 'read:stats', 'write', 'profile'])]
#[Assert\NotBlank]
#[Assert\Length(min: 3)]
#[ORM\Column(type: Types::STRING, length: 255)]
private string $fullName;
#[ApiProperty(description: 'Last activity')]
#[Groups(['read:' . self::class])]
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
private \DateTimeImmutable $lastActivityAt;
#[ApiProperty(description: 'Reset password hash')]
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
private ?string $resetPasswordHash = null;
#[ApiProperty(description: 'Reset password hash generated at')]
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
private ?\DateTimeImmutable $resetPasswordHashGeneratedAt = null;
#[ApiProperty(description: 'Is password reset required (account is inactive till reset)')]
#[Groups(['read:' . self::class])]
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
private bool $isPasswordResetRequired = false;
#[ApiProperty(description: 'Is contextual hints visible')]
#[Groups(['read:' . self::class, 'profile'])]
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => true])]
private bool $isContextualHintsVisible = true;
#[ApiProperty(description: 'Visible journal admin columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [JournalAdminColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
#[Assert\Count(min: 1)]
#[ORM\Column(type: JournalAdminColumnsType::NAME)]
private array $visibleJournalAdminColumns = [
JournalAdminColumn::NO,
JournalAdminColumn::WORKINGTITLE,
JournalAdminColumn::AFFILIATION,
JournalAdminColumn::PERIODICITY,
JournalAdminColumn::STAT
];
#[ApiProperty(description: 'Visible journal article admin columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [JournalArticleAdminColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
#[Assert\Count(min: 1)]
#[ORM\Column(type: JournalArticleAdminColumnsType::NAME)]
private array $visibleJournalArticleAdminColumns = [
JournalArticleAdminColumn::NO,
JournalArticleAdminColumn::TITLE,
JournalArticleAdminColumn::ISSUE,
JournalArticleAdminColumn::TYPE,
JournalArticleAdminColumn::SECTION,
JournalArticleAdminColumn::UPDATED_AT,
JournalArticleAdminColumn::DOI,
JournalArticleAdminColumn::FILES,
JournalArticleAdminColumn::STAT
];
#[ApiProperty(description: 'Visible journal admin stats columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [JournalAdminStatsColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
// #[Assert\Count(min: 1)]
#[ORM\Column(type: JournalAdminStatsColumnsType::NAME)]
private array $visibleJournalAdminStatsColumns = JournalAdminStatsColumn::ORDERABLE;
#[ApiProperty(description: 'Order of journal admin stats columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [JournalAdminStatsColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
// #[Assert\Count(min: 13, max: 13)]
#[ORM\Column(type: JournalAdminStatsColumnsType::NAME)]
private array $orderJournalAdminStatsColumns = JournalAdminStatsColumn::ORDERABLE;
#[ApiProperty(description: 'Visible journal article admin stats columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [JournalArticleAdminStatsColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
// #[Assert\Count(min: 1)]
#[ORM\Column(type: JournalArticleAdminStatsColumnsType::NAME)]
private array $visibleJournalArticleAdminStatsColumns = JournalArticleAdminStatsColumn::ORDERABLE;
#[ApiProperty(description: 'Order of journal article admin stats columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [JournalArticleAdminStatsColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
// #[Assert\Count(min: 26, max: 26)]
#[ORM\Column(type: JournalArticleAdminStatsColumnsType::NAME)]
private array $orderJournalArticleAdminStatsColumns = JournalArticleAdminStatsColumn::ORDERABLE;
#[ApiProperty(description: 'Visible author admin stats columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [AuthorAdminStatsColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
// #[Assert\Count(min: 1)]
#[ORM\Column(type: AuthorAdminStatsColumnsType::NAME)]
private array $visibleAuthorAdminStatsColumns = AuthorAdminStatsColumn::ORDERABLE;
#[ApiProperty(description: 'Order of author admin stats columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [AuthorAdminStatsColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
// #[Assert\Count(min: 9, max: 9)]
#[ORM\Column(type: AuthorAdminStatsColumnsType::NAME)]
private array $orderAuthorAdminStatsColumns = AuthorAdminStatsColumn::ORDERABLE;
#[ApiProperty(description: 'Visible admin stats columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [AdminStatsColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
// #[Assert\Count(min: 1)]
#[ORM\Column(type: AdminStatsColumnsType::NAME)]
private array $visibleAdminStatsColumns = AdminStatsColumn::ORDERABLE;
#[ApiProperty(description: 'Order of admin stats columns', writableLink: false)]
#[Groups(['read:' . self::class, 'write', 'profile'])]
#[Assert\Choice(callback: [AdminStatsColumn::class, 'cases'], multiple: true)]
#[Assert\Unique]
// #[Assert\Count(min: 2, max: 2)]
#[ORM\Column(type: AdminStatsColumnsType::NAME)]
private array $orderAdminStatsColumns = AdminStatsColumn::ORDERABLE;
#[ApiProperty(description: 'Administration group', writableLink: false)]
#[Groups(['read:' . self::class, 'write'])]
#[ORM\ManyToOne(targetEntity: AdminGroup::class, inversedBy: 'members')]
#[ORM\JoinColumn(onDelete: 'set null')]
private ?AdminGroup $group = null;
#[ApiProperty(description: 'Registry entries')]
#[ORM\OneToMany(targetEntity: RegistryEntry::class, mappedBy: 'admin')]
private Collection $registryEntries;
#[ApiProperty(description: 'Journals')]
#[ORM\OneToMany(targetEntity: Journal::class, mappedBy: 'createdBy')]
private Collection $journals;
#[ApiProperty(description: 'Statistics: amount of journals')]
#[Groups(['read:stats'])]
private int $statsAmountOfJournals = 0;
public function __construct(string $email, string $fullName)
{
$this->setUuid();
$this->email = $email;
$this->fullName = $fullName;
$this->lastActivityAt = new \DateTimeImmutable();
$this->registryEntries = new ArrayCollection();
$this->createdAt = new \DateTimeImmutable();
$this->updatedAt = new \DateTimeImmutable();
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getPassword(): string
{
return $this->password;
}
public function getRawPassword(): ?string
{
return $this->rawPassword;
}
public function setRawPassword(
?string $rawPassword,
UserPasswordHasherInterface $encoder,
Admin $setBy
): self {
if (! $rawPassword) {
return $this;
}
$this->rawPassword = $rawPassword;
$this->password = $encoder->hashPassword($this, $rawPassword);
if (count($this->previousPasswords) >= self::MAX_PREVIOUS_PASSWORDS) {
$this->previousPasswords = array_slice($this->previousPasswords, 1);
}
$this->previousPasswords[] = password_hash($rawPassword, PASSWORD_DEFAULT);
$this->isPasswordResetRequired = $setBy->getUuid() !== $this->getUuid();
$this->resetPasswordHash = null;
$this->resetPasswordHashGeneratedAt = null;
return $this;
}
public function setHashedPassword(string $hashedPassword): self
{
$this->password = $hashedPassword;
return $this;
}
public function getCurrentPassword(): ?string
{
return $this->currentPassword;
}
public function setCurrentPassword(?string $currentPassword): self
{
$this->currentPassword = $currentPassword;
return $this;
}
public function getPreviousPasswords(): array
{
return $this->previousPasswords;
}
public function getFullName(): string
{
return $this->fullName;
}
public function setFullName(string $fullName): self
{
$this->fullName = $fullName;
return $this;
}
public function getLastActivityAt(): \DateTimeImmutable
{
return $this->lastActivityAt;
}
public function setLastActivityAt(\DateTimeImmutable $lastActivityAt): self
{
$this->lastActivityAt = $lastActivityAt;
return $this;
}
public function getUsername(): string
{
return $this->email;
}
public function getUserIdentifier(): string
{
return $this->email;
}
public function getRoles(): array
{
$roles = [Roles::ROLE_ADMIN];
if (! $this->group?->getStat()) {
return $roles;
}
if ($this->group->getIsOpenform()) {
$roles[] = Roles::ROLE_OPENFORM;
return $roles;
}
if ($this->group->getIsSuperAdmin()) {
$roles[] = Roles::ROLE_SUPER_ADMIN;
return $roles;
}
foreach ($this->group->getFullyQualifiedResourceAccess() as $resourceAccess) {
switch(true) {
case defined(ModuleRoles::class . '::ROLE_MODULE_' . strtoupper($resourceAccess)):
return constant(ModuleRoles::class . '::ROLE_MODULE_' . strtoupper($resourceAccess));
case class_exists($resourceAccess):
$roles[] = str_replace('\\', '', $resourceAccess);
default:
throw new \Exception('Resource access is invalid.');
}
$roles[] = str_replace('\\', '', $resourceAccess);
}
return $roles;
}
public function getSalt(): ?string
{
return null;
}
public function eraseCredentials(): void
{
return;
}
public function setResetPasswordHash(string $hash): self
{
$this->resetPasswordHash = $hash;
$this->resetPasswordHashGeneratedAt = new \DateTimeImmutable();
return $this;
}
public function getResetPasswordHash(): ?string
{
return $this->resetPasswordHash;
}
public function getResetPasswordHashGeneratedAt(): ?\DateTimeImmutable
{
return $this->resetPasswordHashGeneratedAt;
}
public function getIsPasswordResetRequired(): bool
{
return $this->isPasswordResetRequired;
}
public function setIsPasswordResetRequired(bool $isPasswordResetRequired): self
{
$this->isPasswordResetRequired = $isPasswordResetRequired;
return $this;
}
public function getIsContextualHintsVisible(): bool
{
return $this->isContextualHintsVisible;
}
public function setIsContextualHintsVisible(bool $isContextualHintsVisible): self
{
$this->isContextualHintsVisible = $isContextualHintsVisible;
return $this;
}
public function getVisibleJournalAdminColumns(): array
{
return $this->visibleJournalAdminColumns;
}
public function setVisibleJournalAdminColumns(array $visibleJournalAdminColumns): self
{
$this->visibleJournalAdminColumns = $visibleJournalAdminColumns;
return $this;
}
public function getVisibleJournalArticleAdminColumns(): array
{
return $this->visibleJournalArticleAdminColumns;
}
public function setVisibleJournalArticleAdminColumns(array $visibleJournalArticleAdminColumns): self
{
$this->visibleJournalArticleAdminColumns = $visibleJournalArticleAdminColumns;
return $this;
}
public function getVisibleJournalAdminStatsColumns(): array
{
return $this->visibleJournalAdminStatsColumns;
}
public function setVisibleJournalAdminStatsColumns(array $visibleJournalAdminStatsColumns): self
{
$this->visibleJournalAdminStatsColumns = $visibleJournalAdminStatsColumns;
return $this;
}
public function getOrderJournalAdminStatsColumns(): array
{
return $this->orderJournalAdminStatsColumns;
}
public function setOrderJournalAdminStatsColumns(array $orderJournalAdminStatsColumns): self
{
$this->orderJournalAdminStatsColumns = $orderJournalAdminStatsColumns;
return $this;
}
public function getVisibleJournalArticleAdminStatsColumns(): array
{
return $this->visibleJournalArticleAdminStatsColumns;
}
public function setVisibleJournalArticleAdminStatsColumns(array $visibleJournalArticleAdminStatsColumns): self
{
$this->visibleJournalArticleAdminStatsColumns = $visibleJournalArticleAdminStatsColumns;
return $this;
}
public function getOrderJournalArticleAdminStatsColumns(): array
{
return $this->orderJournalArticleAdminStatsColumns;
}
public function setOrderJournalArticleAdminStatsColumns(array $orderJournalArticleAdminStatsColumns): self
{
$this->orderJournalArticleAdminStatsColumns = $orderJournalArticleAdminStatsColumns;
return $this;
}
public function getVisibleAuthorAdminStatsColumns(): array
{
return $this->visibleAuthorAdminStatsColumns;
}
public function setVisibleAuthorAdminStatsColumns(array $visibleAuthorAdminStatsColumns): self
{
$this->visibleAuthorAdminStatsColumns = $visibleAuthorAdminStatsColumns;
return $this;
}
public function getOrderAuthorAdminStatsColumns(): array
{
return $this->orderAuthorAdminStatsColumns;
}
public function setOrderAuthorAdminStatsColumns(array $orderAuthorAdminStatsColumns): self
{
$this->orderAuthorAdminStatsColumns = $orderAuthorAdminStatsColumns;
return $this;
}
public function getVisibleAdminStatsColumns(): array
{
return $this->visibleAdminStatsColumns;
}
public function setVisibleAdminStatsColumns(array $visibleAdminStatsColumns): self
{
$this->visibleAdminStatsColumns = $visibleAdminStatsColumns;
return $this;
}
public function getOrderAdminStatsColumns(): array
{
return $this->orderAdminStatsColumns;
}
public function setOrderAdminStatsColumns(array $orderAdminStatsColumns): self
{
$this->orderAdminStatsColumns = $orderAdminStatsColumns;
return $this;
}
public function getGroup(): ?AdminGroup
{
return $this->group;
}
public function setGroup(?AdminGroup $group): self
{
$this->group = $group;
return $this;
}
public function getRegistryEntries(): Collection
{
return $this->registryEntries;
}
public function addRegistryEntry(RegistryEntry $registryEntry): self
{
if ($this->registryEntries->contains($registryEntry)) {
return $this;
}
$this->registryEntries[] = $registryEntry;
return $this;
}
public function removeRegistryEntry(RegistryEntry $registryEntry): self
{
$this->registryEntries->removeElement($registryEntry);
return $this;
}
public function getStatsAmountOfJournals(): int
{
return $this->statsAmountOfJournals;
}
public function setStatsAmountOfJournals(int $statsAmountOfJournals): self
{
$this->statsAmountOfJournals = $statsAmountOfJournals;
return $this;
}
}