<?php
namespace App\Security\Voter;
use App\Entity\BlogPost;
use App\Entity\User;
use App\Service\BlogPostPreviewService;
use Psr\Log\LoggerInterface;
use Ramsey\Collection\Collection;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class ContentCriteriaVoter extends Voter
{
public function __construct(
private LoggerInterface $logger,
private BlogPostPreviewService $blogPostPreviewService,
) {
}
protected function supports(string $attribute, $subject): bool
{
return $attribute === 'VIEW_CONTENT' && is_object($subject);
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
if ($subject instanceof BlogPost && $this->blogPostPreviewService->isPreviewRequested()) {
if ($this->blogPostPreviewService->isValidPreviewRequestFor($subject)) {
$this->logger->debug('BO preview bypass granted for content.', [
'blog_post_id' => $subject->getId(),
'user_id' => $user->getId(),
]);
return true;
}
return false;
}
if (method_exists($subject, 'getRoles')) {
$contentRoles = $subject->getRoles();
$contentRoleCodes = [];
foreach ($contentRoles as $role) {
if (method_exists($role, 'getCode')) {
$contentRoleCodes[] = $role->getCode();
}
}
if (!empty($contentRoleCodes) && empty(array_intersect($contentRoleCodes, $user->getRoles()))) {
$this->logger->info('Access denied on roles', [
'required' => $contentRoleCodes,
'user' => $user->getRoles(),
]);
return false;
}
}
$userCriteria = $this->getUserCriteria($user);
$contentCriteria = $this->getContentCriteria($subject);
$hasAllCriteria = [
'criteria1Items' => method_exists($subject, 'isHasAllCriteria1') ? $subject->isHasAllCriteria1() : false,
'criteria2Items' => method_exists($subject, 'isHasAllCriteria2') ? $subject->isHasAllCriteria2() : false,
'criteria3Items' => method_exists($subject, 'isHasAllCriteria3') ? $subject->isHasAllCriteria3() : false,
'criteria4Items' => method_exists($subject, 'isHasAllCriteria4') ? $subject->isHasAllCriteria4() : false,
'criteria5Items' => method_exists($subject, 'isHasAllCriteria5') ? $subject->isHasAllCriteria5() : false,
];
foreach ($contentCriteria as $key => $required) {
if (isset($hasAllCriteria[$key]) && $hasAllCriteria[$key]) {
continue;
}
if (empty($required)) {
continue;
}
$userValue = $userCriteria[$key] ?? [];
if (is_iterable($required)) {
$requiredIds = [];
foreach ($required as $item) {
if (method_exists($item, 'getId')) {
$requiredIds[] = $item->getId();
} else {
$requiredIds[] = (string) $item;
}
}
if (empty($requiredIds)) {
continue;
}
$userIds = [];
foreach ($userValue as $item) {
if (method_exists($item, 'getId')) {
$userIds[] = $item->getId();
} else {
$userIds[] = (string) $item;
}
}
if (empty(array_intersect($requiredIds, $userIds))) {
$this->logger->info("Access denied on criterion '$key'", [
'required' => $requiredIds,
'user' => $userIds,
]);
return false;
}
} elseif ($userValue != $required) {
$this->logger->info("Access denied on simple criterion '$key'", [
'required' => $required,
'user' => $userValue,
]);
return false;
}
}
return true;
}
private function getUserCriteria(User $user): array
{
return [
'instances' => method_exists($user, 'getInstances') ? $user->getInstances() : [],
'criteria1Items' => method_exists($user, 'getCriteria1Items') ? $user->getCriteria1Items() : [],
'criteria2Items' => method_exists($user, 'getCriteria2Items') ? $user->getCriteria2Items() : [],
'criteria3Items' => method_exists($user, 'getCriteria3Items') ? $user->getCriteria3Items() : [],
'criteria4Items' => method_exists($user, 'getCriteria4Items') ? $user->getCriteria4Items() : [],
'criteria5Items' => method_exists($user, 'getCriteria5Items') ? $user->getCriteria5Items() : [],
];
}
private function getContentCriteria($content): array
{
return [
'instances' => method_exists($content, 'getInstances') ? $content->getInstances() : [],
'criteria1Items' => method_exists($content, 'getCriteria1Items') ? $content->getCriteria1Items() : [],
'criteria2Items' => method_exists($content, 'getCriteria2Items') ? $content->getCriteria2Items() : [],
'criteria3Items' => method_exists($content, 'getCriteria3Items') ? $content->getCriteria3Items() : [],
'criteria4Items' => method_exists($content, 'getCriteria4Items') ? $content->getCriteria4Items() : [],
'criteria5Items' => method_exists($content, 'getCriteria5Items') ? $content->getCriteria5Items() : [],
];
}
private function extractValues($collection): array
{
$values = [];
if ($collection instanceof Collection || is_iterable($collection)) {
foreach ($collection as $item) {
if (method_exists($item, 'getId')) {
$values[] = $item->getId();
} else {
$values[] = (string) $item;
}
}
}
return $values;
}
}