src/Entity/Admin.php line 98
<?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\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\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;
#[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 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'])]
#[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', '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 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::NATIVE_TITLE,
JournalArticleAdminStatsColumn::STATS_AMOUNT_OF_DOWNLOADS,
JournalArticleAdminStatsColumn::JOURNAL,
JournalArticleAdminStatsColumn::TYPE,
JournalArticleAdminStatsColumn::AUTHORS,
JournalArticleAdminStatsColumn::AUTHORS_AFFILIATIONS
];
#[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;
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 getVisibleJournalArticleAdminStatsColumns(): array
{
return $this->visibleJournalArticleAdminStatsColumns;
}
public function setVisibleJournalArticleAdminStatsColumns(array $visibleJournalArticleAdminStatsColumns): self
{
$this->visibleJournalArticleAdminStatsColumns = $visibleJournalArticleAdminStatsColumns;
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;
}
}