<?php
/** @noinspection ALL */
namespace App\Twig\Runtime;
use App\Constants\ACL;
use App\Entity\Parameter;
use App\Entity\SliderItem;
use App\Entity\User;
use App\Model\Product;
use App\Services\Back\ParameterService;
use App\Services\Common\AclServiceV2;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Twig\Extension\RuntimeExtensionInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
/**
* Rassemble toute les fonction disponible dans twig qui touchent aux ACL et à l'affichage des données via les règles acl ou les systèmes paralèlles (roles, jobs sur les entités comme pour les slider ou les documents)
*/
class AclRuntime implements RuntimeExtensionInterface
{
private AclServiceV2 $aclService;
private RequestStack $requestStack;
private ParameterService $parameterService;
private Security $security;
private RoleHierarchyInterface $roleHierarchy;
private EntityManagerInterface $em;
public function __construct(
AclServiceV2 $aclService,
RequestStack $requestStack,
ParameterService $parameterService,
Security $security,
RoleHierarchyInterface $roleHierarchy,
EntityManagerInterface $em
)
{
$this->aclService = $aclService;
$this->requestStack = $requestStack;
$this->parameterService = $parameterService;
$this->security = $security;
$this->roleHierarchy = $roleHierarchy;
$this->em = $em;
}
/**
* Permet de savoir si le user a le droit d'accéder à la route
* @param User|null $user
* @param string $route
* @param array $params
* @param string $env
* @return bool
*/
public function userIsGrantedRoute(
?User $user,
string $route,
array $params = [],
string $env = ACL::FRONT_ENV,
bool $debug = FALSE
)
{
$config = [
'route' => $route,
'params' => $params,
'component' => ACL::ACL_NO_COMPONENT,
'slug' => ACL::ACL_NO_SLUG,
'action' => ACL::READ,
'env' => $env,
];
return $this->aclService->userIsGranted( $user, $config, $debug );
}
/**
* Permet de savoir si un user peut faire une action en fonction de son rôle ou de son job
*
* @param User|null $user
* @param string $slug
* @param string $action
* @param string $env
*
* @return bool
*
* @throws InvalidArgumentException
*/
public function userIsGranted(
?User $user,
string $component,
string $slug = ACL::ACL_NO_SLUG,
string $action = ACL::READ,
string $env = ACL::FRONT_ENV,
string $route = NULL,
string $params = NULL,
bool $debug = FALSE
): bool
{
$currentRoute = $route ?? $this->requestStack->getCurrentRequest()->get( '_route' );
$currentParams = $params ?? $this->requestStack->getCurrentRequest()->get( '_route_params' );
if ($currentRoute === NULL){
return $this->checkWhenRouteIsNull();
}
$config = [
'route' => $currentRoute,
'params' => $this->aclService->getRouteParamsForAcl($currentRoute, $currentParams),
'component' => $component,
'slug' => $slug,
'action' => $action,
'env' => $env,
];
return $this->aclService->userIsGranted( $user, $config, $debug );
}
/**
* Logique pour éviter des erreurs 500 si jamais une route est évaluée à NULL
* Si on est dans le cas d'une exception on autorise la visualition
* @return true
* @throws \Exception
*/
private function checkWhenRouteIsNull()
{
$request = $this->requestStack->getCurrentRequest();
$exeption = $request->attributes->get('exception');
if ($exeption !== null) {
return TRUE;
}
throw new \Exception('Une erreur est survenue, la route ne peut pas être null et ne pas être une exception');
}
/**
* Permet de savoir si le current user à le droit de voir le catalogue par son slug
* @param $catalogue
* @return bool
* @throws InvalidArgumentException
*/
public function userIsGrantedCatalogue($catalogue)
{
$currentUser = $this->security->getUser();
return $this->aclService->userIsGrantedCatalogue($currentUser, $catalogue);
}
/**
* Permet de savoir si le current user à le droit de voir le produit
* @param Product $product
*
* @return bool
* @throws \JsonException
*/
public function userIsGrantedProduct(Product $product)
{
$currentUser = $this->security->getUser();
return $this->aclService->userIsGrantedProduct($currentUser, $product);
}
/**
* Retourne le slug du premier catalogue qui contient le produit et qui est accèssible au user courant
* @param Product $product
*
* @return mixed|null
* @throws \JsonException
*/
public function getUserFirstGrantedCatalogSlugForProduct(Product $product)
{
$currentUser = $this->security->getUser();
return $this->aclService->getUserFirstGrantedCatalogSlugForProduct($currentUser, $product);
}
/**
* Défini si l'utilisateur à le droit de voir le document
*
* @param User|null $user
* @param Parameter $document
*
* @return bool
*
* @throws \JsonException
*/
public function canDisplayDocument( ?User $user, Parameter $document ): bool
{
return $this->aclService->userIsGrantedOnDocument( $user, $document );
}
/**
* Lors des documents personnalisé, permet de savoit si le user peut voir le document
*
* @param User|null $user
* @param $id
*
* @return bool
*
* @throws \JsonException
*/
public function isDocumentSelectedForUser( ?User $user, $id ): bool
{
return $this->parameterService->isDocumentSelectedForUser( $user, $id );
}
/**
* Permet de savoir si on component est visible par le user courant
*
* Ne doit être utilisé que pour l'affichage twig des components en front
*
* @param array|null $componentOptions
* @param bool $debug
* @return bool
* @throws InvalidArgumentException
*/
public function canDisplayComponentByAcl(?array $componentOptions, bool $debug = FALSE)
{
if ($componentOptions === NULL || $componentOptions === []){
return TRUE;
}
$item = $componentOptions['item'] ?? $componentOptions;
// Le component est actif sur la page ?
$canDisplay = (bool)$item[ 'enabled' ] ?? TRUE;
if (!$canDisplay) {
return FALSE;
}
// on regarde s'il peut s'afficher sur la page via la clef display
$canDisplay = $this->canDisplayOnPageByConfig($item['display'], $debug);
if (!$canDisplay) {
return FALSE;
}
/** @var User $currentUser */
$currentUser = $this->security->getUser();
// on recherche l'acl si on peut récupérer son slug data-component-acl
if ( isset($item['data']['data-component-acl'])){
$canDisplay = $this->userIsGranted($currentUser, $item['data']['data-component-acl'] ?? ACL::ACL_NO_COMPONENT);
}
if (!$canDisplay) {
return FALSE;
}
// on regarde s'il y a des univers... on verifie le currentUser car le component peut être utilisé en partie security
if ($currentUser !== NULL && isset($item['univers'])){
if ( $canDisplay && !$currentUser->isDeveloper() && !$currentUser->isSuperAdmin() && isset($item['univers'])){
$canDisplay = $this->aclService->canDisplayByUniverses($currentUser, $item['univers']);
}
}
return $canDisplay;
}
/**
* Défini si un component s'affiche via la clef display
*
* Elle contient 2 enfants:
* enabled_on : array (tableau de route)|null
* disabled_on : array (tableau de route)|null
* Si disabled_on est a autre chose que null, alors c'est lui qui prend l'ascendant
* Afficher partout => enabled_on: null + disabled_on: [] ou null
*
* @param array $display
*
* @return bool
*/
private function canDisplayOnPageByConfig(array $display, bool $debug = FALSE)
{
$currentRoute = $this->requestStack->getCurrentRequest()->get( '_route' );
if ($currentRoute === NULL){
return $this->checkWhenRouteIsNull();
}
$canDisplay = TRUE; // par défaut on affiche
// On prend la config du disabled_on si elle n'est pas nulle
$displayByDisabled = FALSE;
if (isset($display['disabled_on']) && $display['disabled_on'] !== NULL){
$displayByDisabled = TRUE;
}
// On prend la config enbaled_on
if (isset($display['enabled_on']) && !$displayByDisabled){
$arrayRoute = $display['enabled_on'];
if(gettype($display['enabled_on']) === "string") {
$arrayRoute = json_decode($display['enabled_on'], true);
}
switch (TRUE){
// tableau vide, ce n'est pas visible
case $arrayRoute === []:
$canDisplay = FALSE;
break;
// NULL ou valeur qui n'est pas un tableau, on considère que c'est visible
// ainsi si enabled_on et disabled_on sont NULL, on affiche
case $arrayRoute === NULL:
case !is_array($arrayRoute):
$canDisplay = TRUE;
break;
// on regarde si la route actuelle est dans le tableau pour l'afficher
default:
$canDisplay = in_array( $currentRoute, $arrayRoute, TRUE );
break;
}
}
// on prend la config disabled_on
if (isset($display['disabled_on']) && $displayByDisabled){
$arrayRoute = $display['disabled_on'];
if(gettype($display['disabled_on']) === "string") {
$arrayRoute = json_decode($display['disabled_on'], true);
}
switch (TRUE){
// tableau vide, c'est visible partout
case $arrayRoute === []:
$canDisplay = TRUE;
break;
// NULL ou valeur qui n'est pas un tableau, on refuse l'affichage
// Normalement on ne tombe pas dans cette configuration puisque $displayByDisabled est FALSE
case $arrayRoute === NULL:
case !is_array($arrayRoute):
$canDisplay = FALSE;
break;
// on regarde si la route actuelle est dans le tableau pour refuser l'affichage
default:
$canDisplay = !in_array( $currentRoute, $arrayRoute, TRUE );
break;
}
}
return $canDisplay;
}
/**
* Permet de savoir si le user peut voir le slider
*
* Les acl du slider sont intégré à l'édition de l'entité
*
* @param User $user
* @param SliderItem $item
*
* @return bool
*/
public function canDisplaySliderItem(?User $user, SliderItem $item): bool
{
if(!$user) return FALSE;
$jobs = $item->getDisplayJob();
$universes = $item->getDisplayUniverses();
// Le superadmin et dev doivent pouvoir tout voir...
if ( $user->isDeveloper() || $user->isSuperAdmin() ) {
return TRUE;
}
// Par défaut, tout s'affiche
$canDisplay = TRUE;
// SI config par job on regarde si ça match
if ( $jobs !== NULL && !in_array( $user->getJob(), $jobs, TRUE ) ) {
$canDisplay = FALSE;
}
// Si config par univers on regarde si ça match
if ( $universes !== NULL ) {
foreach ( $user->getUniverses() as $userUnivers ) {
if ( in_array( $userUnivers->getSlug(), $universes, TRUE ) ) {
$canDisplay = TRUE;
break;
}
$canDisplay = FALSE;
}
}
return $canDisplay;
}
/**
* Normalise la transformation de l'array qui contient les params d'une route pour la transformer en string
* @param array $params
*
* @return false|string
* @throws \JsonException
*/
public function formatParamsToString(array $params)
{
return $this->aclService->formatArrayParamsToString($params);
}
/**
* Retourne un tableau avec la liste de roles et les jobs relatif à ces roles
*
* @return array[]
*/
public function getDefaultRolesAndJobs()
{
return $this->aclService->getDefaultRoleAndJob();
}
/**
* @return UserInterface|null
*/
public function getOriginalUser(): ?UserInterface
{
$token = $this->security->getToken();
if ($token instanceof SwitchUserToken)
{
$user = $token->getOriginalToken()->getUser();
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $user->getEmail()]);
return $user;
}
return $this->security->getUser();
}
/**
* @param User $user
* @param string $role
*
* @return bool
*/
public function hasRole(User $user, string $role): bool
{
$reachableRoles = $this->roleHierarchy->getReachableRoleNames($user->getRoles());
return in_array($role, $reachableRoles);
}
}