<?php declare(strict_types=1);
namespace App\Security\Voter;
use App\Entity\{Project\Manager, TimeControl\TimeEntry, TimeControl\TimeEntryValidation, User};
use App\Service\TimeEntryManagement\IsTimeBlocked;
use Symfony\Component\Security\Core\{Authentication\Token\TokenInterface, Authorization\Voter\Voter, Security};
class TimeEntryVoter extends Voter
{
public const ENTRY_CREATE = 'ENTRY_CREATE';
public const ENTRY_OPEN = 'ENTRY_OPEN';
public const ENTRY_DELETE = 'ENTRY_DELETE';
public const ENTRY_MODIFY = 'ENTRY_MODIFY';
public const ENTRY_APPROVED = 'ENTRY_APPROVED';
public function __construct(private Security $security, private IsTimeBlocked $isTimeBlocked)
{
}
protected function supports($attribute, $subject): bool
{
$operations = [
self::ENTRY_CREATE,
self::ENTRY_DELETE,
self::ENTRY_OPEN,
self::ENTRY_MODIFY,
self::ENTRY_APPROVED,
];
return \in_array($attribute, $operations, true)
&& $subject instanceof TimeEntry;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
try {
if (!$this->checkFixPermission($user, $subject, $attribute)) {
return false;
}
return match ($attribute) {
self::ENTRY_CREATE => $this->canUserCreateTimeEntry($user, $subject),
self::ENTRY_MODIFY => $this->canUserModifyTimeEntry($user, $subject),
self::ENTRY_DELETE => $this->canUserDeleteTimeEntry($user, $subject),
self::ENTRY_OPEN => $this->canUserOpenTimeEntry($user, $subject),
self::ENTRY_APPROVED => $this->canUserApproveTimeEntry($user, $subject),
default => false,
};
} catch (\UnhandledMatchError) {
return false;
}
}
protected function canUserCreateTimeEntry(User $currentUser, TimeEntry $subj): bool
{
return $currentUser === $subj->getTask()?->getUser();
}
protected function canUserModifyTimeEntry(User $currentUser, TimeEntry $subj): bool
{
if ($this->canUserCreateTimeEntry($currentUser, $subj)) {
return true;
}
return $this->isResponsibleManagerOrHighPrivileges($currentUser, $subj);
}
protected function isResponsibleManagerOrHighPrivileges(User $currentUser, TimeEntry $subj): bool
{
if ($this->security->isGranted('ROLE_MASTER_MANAGER')) {
return true;
}
if (!$this->security->isGranted('ROLE_MANAGER')) {
return false;
}
return $this->isUserIsManagerOfProjectForTimeEntry($currentUser, $subj);
}
protected function canUserDeleteTimeEntry(User $currentUser, TimeEntry $subj): bool
{
$project = $subj->getProject();
$start = $subj->getStart();
if (!$project) {
$project = $subj->getTask()?->getProject();
}
if ((!$project) || ($this->isTimeBlocked)($project, $start)) {
return false;
}
if ($currentUser === $subj->getTask()?->getUser()) {
return true;
}
if ($this->security->isGranted('ROLE_MASTER_MANAGER')) {
return true;
}
return false;
}
protected function canUserOpenTimeEntry(User $currentUser, TimeEntry $subj): bool
{
return $this->canUserCreateTimeEntry($currentUser, $subj);
}
protected function canUserApproveTimeEntry(User $currentUser, TimeEntry $subj): bool
{
return $this->hasFixedPermission($currentUser, $subj);
}
protected function isUserIsManagerOfProjectForTimeEntry(User $currentUser, TimeEntry $subj): bool
{
$manager = $currentUser->getManager();
if (!$manager instanceof Manager) {
return false;
}
$projects = $manager->getProjects();
return $projects->contains($subj->getTask()?->getProject());
}
protected function checkFixPermission(User $currentUser, TimeEntry $subj, string $attribute): bool
{
$fixed = $subj->getIsFixed();
$fixedPermission = $this->hasFixedPermission($currentUser, $subj);
$isDeleteOperation = ($attribute === self::ENTRY_DELETE);
// ТЕ не зафиксирована или Есть доступ на изменение фиксированной ТЕ и это не операция удаления.
return !($fixed && (!$fixedPermission || $isDeleteOperation));
}
protected function hasFixedPermission(User $currentUser, TimeEntry $subj): bool
{
if ($this->security->isGranted('ROLE_PROJECT_FIXER')) {
return true;
}
if (!$this->security->isGranted('ROLE_USER_FIXER')) {
return false;
}
return $this->isUserIsManagerOfProjectForTimeEntry($currentUser, $subj);
}
}