<?php
namespace App\Services\Common;
use App\Constants\ACL;
use App\Constants\UserExtension;
use App\Entity\AclSetting;
use App\Entity\Parameter;
use App\Entity\Univers;
use App\Entity\User;
use App\Model\AclSettingConfig;
use App\Model\Product;
use App\Services\Back\ParameterService;
use App\Services\Front\Catalogue\JsonCatalogueService;
use Doctrine\ORM\EntityManagerInterface;
use JsonException;
use League\Csv\Exception;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Service pour la gestion des ACL V2 coté Plateforme
*/
class AclServiceV2
{
private RouterInterface $router;
private TokenStorageInterface $tokenStorage;
private ParameterService $parameterService;
private EntityManagerInterface $em;
private ModuleSettingService $moduleSettingService;
private JsonCatalogueService $jsonCatalogueService;
private CommunityService $communityService;
private LoggerInterface $logger;
public function __construct(
RouterInterface $router,
ParameterService $parameterService,
TokenStorageInterface $tokenStorage,
EntityManagerInterface $em,
ModuleSettingService $moduleSettingService,
JsonCatalogueService $jsonCatalogueService,
CommunityService $communityService,
LoggerInterface $logger
) {
$this->router = $router;
$this->parameterService = $parameterService;
$this->tokenStorage = $tokenStorage;
$this->em = $em;
$this->moduleSettingService = $moduleSettingService;
$this->jsonCatalogueService = $jsonCatalogueService;
$this->communityService = $communityService;
$this->logger = $logger;
}
/**
* @param $config
*
* @return false|mixed|string
*
* @throws JsonException
*/
public function getNormalizedAclSettingConfigParams($config)
{
$config = $config instanceof AclSettingConfig ? $config->toArray() : $config;
$params = $config[ 'params' ] ?? ACL::ACL_NO_PARAMS;
if (is_array($params)) {
if ($config[ 'route' ] !== NULL) {
$params = $this->getRouteParamsForAcl($config[ 'route' ], $params);
} else {
$params = $this->formatArrayParamsToString($params);
}
} elseif (in_array($params, [NULL, ''], TRUE)) {
$params = ACL::ACL_NO_PARAMS;
}
return $params;
}
/**
* Retourne les params sous forme de string pour les ACL
*
* Supprime les params inutiles comme "_env"
* Supprime les params qui ne sont pas configurés pour être pris en compte dans la route
*
* @param string|null $routeName
* @param array|string|null $params
*
* @return string
*
*/
public function getRouteParamsForAcl(?string $routeName, $params): string
{
// cette fonction doit être capable de normalizer les params qui sont en string ou en array
if (is_string($params)) {
try {
$params = json_decode($params, TRUE, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
$this->logger->error($e->getMessage());
$params = [];
}
}
//unset de la clef _env présente en back_office
if (isset($params[ '_env' ])) {
unset($params[ '_env' ]);
}
$paramsOptions = $this->getAclRouteOptions($routeName);
foreach ($paramsOptions as $key => $value) {
if (isset($params[ $key ]) && !$value) {
unset($params[ $key ]);
}
}
return $this->formatArrayParamsToString($params);
}
/**
* Retourne les informations de la clef acl dans les options de la route
*
* Certains params ne doivent pas être pris en compte pour la création des ACL (ex id d'une commande)
* Il faut ajouter dans les options de la route le tableau suivant
* acl :
* id : false <=== le params ID ne doit pas peser dans la règle des ACL
*
* @param string|null $routeName
*
* @return array|mixed|null
*/
public function getAclRouteOptions(?string $routeName)
{
if ($routeName === NULL) {
return [];
}
// Récupère les informations complètes de la route courante
$route = $this->router->getRouteCollection()->get($routeName);
if ($route === NULL) {
return [];
}
return $route->getOption('acl') ?? [];
}
/**
* Normalise le json_encode des params pour la transformation array to string
*
* @param array|null $params
*
* @return string
*/
public function formatArrayParamsToString(?array $params): string
{
if ($params === NULL) {
return ACL::ACL_NO_PARAMS;
}
try {
return json_encode($params, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
} catch (JsonException $e) {
$this->logger->error($e->getMessage());
return ACL::ACL_NO_PARAMS;
}
}
/**
* Récupère l'environnement d'une route (front ou back) par le début du routename
*
* @param string $routeName
*
* @return string
*/
public function getEnvByRoute(string $routeName): string
{
if (str_contains($routeName, ACL::START_ROUTE_BACK)) {
return ACL::BACK_ENV;
}
return ACL::FRONT_ENV;
}
/**
* Remplit le fichier d'ACL avec les données lors du POST de la modale
*
* @param AclSettingConfig $config
* @param array $acl
* @param bool $debug
*
* @return array
*/
public function setAllData(AclSettingConfig $config, array $acl = [], bool $debug = FALSE): array
{
$acls = [];
foreach ($acl as $role => $jobs) {
$config->setRole($role);
foreach ($jobs as $job => $value) {
$config->setJob($job);
$acls[] = $this->setData($config, $value, FALSE);
}
}
// reset de la config si on en a besoin ultérieurement
$config
->setRole(NULL)
->setJob(NULL)
;
$this->em->flush();
return $acls;
}
/**
* Set 1 ligne d'acl uniquement
*
* @param AclSettingConfig $config
* @param bool $value
* @param bool $withFlush
* @param bool $debug
*
* @return AclSetting|float|int|mixed|string|null
*/
public function setData(AclSettingConfig $config, bool $value, bool $withFlush = TRUE, bool $debug = FALSE)
{
// SI pas de role set, on met par défaut le ROLE_USER
// TODO voir si on retourne une erreur à la place
if ($config->getRole() === NULL) {
$config->setRole('ROLE_USER');
}
// Si pas de job set, on met la constant ACL_NO_JOB
if ($config->getJob() === NULL) {
$config->setRole(ACL::ACL_NO_JOB);
}
// vérifications des params en fonction de la configuration des routes
$params = $this->getRouteParamsForAcl($config->getRoute(), $config->getParams());
$config->setParams($params);
$registeredAcl = $this->em->getRepository(AclSetting::class)->getAclSettingsFor($config, TRUE);
if ($registeredAcl === NULL) {
$aclSetting = (new AclSetting())
->setFromAclSettingConfig($config)
->setConcatKey($config->getConcatKey())
;
$this->em->persist($aclSetting);
} else {
$aclSetting = $registeredAcl;
}
$aclSetting
->setValue($value)
;
if ($withFlush) {
$this->em->flush();
}
return $aclSetting;
}
/**
* @param $config
*
* @return string
* @deprecated
*/
private function getConcatKey($config): string
{
$default = [
'env' => ACL::FRONT_ENV,
'route' => NULL,
'params' => ACL::ACL_NO_PARAMS,
'component' => ACL::ACL_NO_COMPONENT,
'slug' => ACL::ACL_NO_SLUG,
'action' => ACL::READ,
];
$config = array_merge($default, $config);
return $config[ 'env' ] . ACL::ACL_KEY_SEPARATOR . ($config[ 'route' ] ?? '') . ACL::ACL_KEY_SEPARATOR . $config[ 'params' ] . ACL::ACL_KEY_SEPARATOR . $config[ 'component' ] . ACL::ACL_KEY_SEPARATOR . $config[ 'slug' ] . ACL::ACL_KEY_SEPARATOR . $config[ 'action' ];
}
/**
* Retourne l'objet utilisé pour construire le tableau d'ACL dans la modale
*
* @param AclSettingConfig $config
*
* @return array
*/
public function getAclConfig(AclSettingConfig $config): array
{
// on garde la route d'origine en mémoire
$routeName = $config->getRoute();
if (NULL === $routeName) {
return [];
}
// on transforme les valeurs pour correspondre aux différents cas
$config = $this->transformAclVariables($config);
$aclItems = $this->getAclItems($config);
$tables = [];
foreach ($aclItems as $role => $jobs) {
$rows = [];
$rows[] = array_merge([$role], array_values($jobs));
$tables[ $role ] = [
'header' => [array_keys($jobs)],
'row' => $rows,
];
// Ajoute au début du tableau $jobs le $role
$rows = [array_merge([$role], array_values($jobs))];
$tables[ $role ] = [
'rows' => $rows,
'header' => array_keys($jobs),
];
}
$data = [
'method' => 'NaN',
'env' => $config->getEnv(),
'route' => $config->getRoute(),
'params' => $config->getParams(),
'component' => $config->getComponent(),
'slug' => $config->getSlug(),
'action' => $config->getAction(),
'tables' => $tables,
];
$route = $this->router->getRouteCollection()->get($routeName);
if ($route !== NULL) {
$defaults = $route->getDefaults();
// On remplace ':' par '::' pour pouvoir utiliser la reflection
$re = '/(.*\w):(\w.*)/m';
$subst = "$1::$2";
$defaults[ '_controller' ] = preg_replace($re, $subst, $defaults[ '_controller' ]);
$method = explode('::', $defaults[ '_controller' ]);
if (method_exists($method[ 0 ], $method[ 1 ])) {
$data[ 'method' ] = $defaults[ '_controller' ];
}
}
$data[ 'routeName' ] = $routeName;
return $data;
}
/**
* Prépare les variables d'ACL en fonction de cas particulier
*
* - slug pas toujours obligatoire
* - les composants "commun" (header, footer) ne dépendent pas de la route
* - les catalogues ne dépendent pas des routes
*
* @param AclSettingConfig $config
*
* @return AclSettingConfig
*/
private function transformAclVariables(AclSettingConfig $config): AclSettingConfig
{
// le slug n'est pas toujours obligatoire
if (in_array($config->getSlug(), ['', NULL], TRUE)) {
$config->setSlug(ACL::ACL_NO_SLUG);
}
// les components dans la partie common ne sont pas dépendant de la page
if (strpos($config->getComponent(), 'common.') === 0) {
$config->setRoute(ACL::ACL_ROUTE_FRONT_ALL);
$config->setParams(ACL::ACL_NO_PARAMS);
}
// même principe pour le header dans le back office
if ($config->getEnv() === ACL::BACK_ENV && strpos($config->getComponent(), 'header.') === 0) {
$config->setRoute(ACL::ACL_ROUTE_BACK_ALL);
$config->setParams(ACL::ACL_NO_PARAMS);
}
// on s'occupe d'un acl global de catalogue
if (strpos($config->getSlug(), ACL::ACL_KEY_SLUG_SHOP_CATALOG) === 0) {
$config->setRoute(ACL::ACL_ROUTE_SHOP_CONFIG);
$config->setParams(ACL::ACL_NO_PARAMS);
$config->setEnv(ACL::FRONT_ENV);
}
return $config;
}
/**
* Donne l'ACL correspondant à l'environnement, la route, au composant et à l'action demandés et retourne un
* tableau formaté pour l'affichage de la modale d'édition
*
* @param AclSettingConfig $config
*
* @return array
*/
private function getAclItems(AclSettingConfig $config): array
{
$currentUser = $this->tokenStorage->getToken() !== NULL ? $this->tokenStorage->getToken()->getUser() : NULL;
// on est obligé de transformer les clefs en premier en fonction des conditions
$config = $this->transformAclVariables($config);
$rolesAndJobs = $this->getDefaultRoleAndJob();
$acls = [];
foreach ($rolesAndJobs as $role => $jobs) {
$config->setRole($role);
foreach ($jobs as $job => $value) {
$config->setJob($job);
$acls[] = $this->getAclItemForRoleAndJob($config, TRUE, FALSE);
}
}
$formattedResult = [];
// Pour chaque acl, injecte dans $formattedResult les roles et jobs voulus
/** @var AclSetting $acl */
foreach ($acls as $acl) {
$formattedResult[ $acl->getRole() ][ $acl->getJob() ] = $acl->getValue();
}
// Si on est ROLE_ADMIN, on a accès au tableau pour les ROLE_USER
if ($currentUser instanceof User && $currentUser->isAdmin()) {
unset($formattedResult[ 'ROLE_SUPER_ADMIN' ], $formattedResult[ 'ROLE_DEVELOPER' ]);
}
// Si on est ROLE_SUPER_ADMIN, on a accès au tableau pour les ROLE_USER, et ROLE_ADMIN
if ($currentUser instanceof User && $currentUser->isSuperAdmin()) {
unset($formattedResult[ 'ROLE_DEVELOPER' ]);
}
return $formattedResult;
}
/**
* Retourne le tableau des roles et job en fonction de la configuration YAML
*
* Si un role n'a pas de job, le système des acls le considère avec le job ACL::ACL_NO_JOB
*
* @return array[]
*/
public function getDefaultRoleAndJob(bool $debug = FALSE): array
{
$tree = $this->communityService->getTreeJobs();
$roles = [
'ROLE_USER' => array_merge(array_keys(array_filter($tree, static function ($item) {
return !isset($item[ 'role' ]) || $item[ 'role' ] === 'ROLE_USER';
})), [ACL::ACL_NO_JOB]),
'ROLE_ADMIN' => array_merge(array_keys(array_filter($tree, static function ($item) {
return isset($item[ 'role' ]) && $item[ 'role' ] === 'ROLE_ADMIN';
})), [ACL::ACL_NO_JOB]),
'ROLE_SUPER_ADMIN' => array_merge(array_keys(array_filter($tree, static function ($item) {
return isset($item[ 'role' ]) && $item[ 'role' ] === 'ROLE_SUPER_ADMIN';
})), [ACL::ACL_NO_JOB]),
];
if (isset($roles[ 'ROLE_DEVELOPER' ])) {
unset($roles[ 'ROLE_DEVELOPER' ]);
}
return array_map(static function ($role) {
// Si $role est vide, ajoute une clé 'ACL::ACL_NO_JOB'
if (count($role) === 0) {
return [ACL::ACL_NO_JOB => TRUE];
}
return array_map(static function () {
return TRUE;
}, array_flip($role));
}, $roles);
}
/**
* Retourne une règle d'ACL complète
*
* Retourne une règle existante ou en créé une nouvelle par rapport au contexte
* Force la valeur à FALSE pour la création d'une règle qui concerne le back + un ROLE_USER
*
* @param AclSettingConfig $config config complète contexte + ROLE + JOB
* @param bool $withFlush ajoute un flush dans la fonction pour enregistrer AclSetting
* @param bool $debug
*
* @return AclSetting|float|int|mixed|string|null
*/
public function getAclItemForRoleAndJob(AclSettingConfig $config, bool $withFlush = TRUE, bool $debug = FALSE)
{
$acl = $this->em->getRepository(AclSetting::class)->getAclSettingsFor($config, TRUE, TRUE);
// si les clefs n'existent pas, on les set pour chaque role/job
if ($acl === NULL)
{
// Si la règle concerne le back pour un ROLE_USER, on set à FALSE par défaut
if ($config->getEnv() === ACL::BACK_ENV && $config->getRole() === "ROLE_USER") {
$defaultValue = FALSE;
}
else
{
$defaultValues = $this->getDefaultRoleAndJob();
try {
$defaultValue = (bool)$defaultValues[ $config->getRole() ][ $config->getJob() ];
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
$defaultValue = FALSE;
}
}
$acl = $this->setData(
$config,
$defaultValue,
$withFlush,
$debug,
);
}
return $acl;
}
/**
* Vérifie si le user à le droit de voir le produit
*
* Vérifie les catalogues où est présent le produit et recherche les droits d'accès du user sur ces catalogues
* Retourne TRUE au premier qui match
*
* @param User|null $user
* @param Product $product
*
* @return bool
*
* @throws JsonException
*/
public function userIsGrantedProduct(?User $user, Product $product): bool
{
foreach ($product->getCatalogues() as $catalogue) {
//if ($this->userIsGrantedCatalogue($user, $catalogue) && $this->jsonCatalogueService->isProductInCatalogue($product->getSku(), $catalogue)) {
//TODO @Manu la vérification pour savoir si le produit est dans le catalogue est lourde, sans doute redondante si on a déjà récupéré le produit via le JSON pour obtenir la variable $product
if ($this->userIsGrantedCatalogue($user, $catalogue)) {
return TRUE;
}
}
return FALSE;
}
/**
* Retourne si l'utilisateur peut voir ou non le catalogue via son slug, prend en compte les Univers si
* l'option est active
*
* @param User|null $user
* @param string $catalogueSlug
* @param bool $debug
*
* @return bool
*
* @throws JsonException
*/
public function userIsGrantedCatalogue(?User $user, string $catalogueSlug, bool $debug = FALSE): bool
{
$isGranted = $this->userIsGranted(
$user,
[
'route' => ACL::ACL_ROUTE_SHOP_CONFIG,
'params' => ACL::ACL_NO_PARAMS,
'component' => ACL::ACL_NO_COMPONENT,
'slug' => ACL::ACL_KEY_SLUG_SHOP_CATALOG . '.' . $catalogueSlug,
'env' => ACL::FRONT_ENV,
],
);
// en cas d'univers il faut vérifier si on a le droit de voir quand on est ni dev, ni super admin
$universActive = $this->moduleSettingService->isModuleActive('univers');
if ($universActive && $user !== NULL && !$user->isSuperAdmin() && !$user->isDeveloper()) {
$catalogueHasUnivers = $this->em->getRepository(Univers::class)->findUniversForUserAndCatalogSlug($user, $catalogueSlug);
$isGranted = $catalogueHasUnivers !== [];
}
return $isGranted;
}
/**
* Indique si un utilisateur a les droits d'accès de la page ou du composant avec son action
*
* Retourne TRUE si on est sur un component qui s'affiche coté security (non logué)
* Retourne TRUE si on est ROLE_DEVELOPER
* Retourne FALSE si la règle n'est pas trouvée ou qu'aucune règle n'est TRUE
* Retourne TRUE à la première règle dont la valeur est TRUE (si le user à plusieurs roles par exemple)
*
* @param User|null $user
* @param array $config
* @param bool $debug
*
* @return bool
* @throws JsonException
*/
public function userIsGranted(
?User $user,
array $config,
bool $debug = FALSE
): bool
{
$default = [
'route' => NULL,
'params' => ACL::ACL_NO_PARAMS,
'component' => ACL::ACL_NO_COMPONENT,
'slug' => ACL::ACL_NO_SLUG,
'action' => ACL::READ,
'env' => ACL::FRONT_ENV,
];
$config = array_merge($default, $config);
// cette étape normalise la variable params que ça soit une string ou un array.
$config[ 'params' ] = $this->getRouteParamsForAcl($config[ 'route' ], $config[ 'params' ]);
//Si c'est un component qui vient de la clef security alors on autorise
// @todo passer par l'array des routes concernée par la security ?
// security_path dans twig.yaml
if (strpos($config[ 'component' ], 'security.', 0) !== FALSE || in_array($config[ 'route' ], ACL::ACL_SECURITY_ROUTES, TRUE)) {
return TRUE;
}
// Utilisateur non connecté
if (!$user instanceof User) return FALSE;
// Aucune restriction pour le ROLE_DEVELOPER
if ($user->isDeveloper()) return TRUE;
$isGranted = FALSE;
foreach ($user->getRoles() as $role)
{
$aclConfig =
(new AclSettingConfig())
->setFromArray($config)
->setRole($role)
->setJob($user->getJob() ?? ACL::ACL_NO_JOB)
;
$aclConfig = $this->transformAclVariables($aclConfig);
$aclItem = $this->getAclItemForRoleAndJob($aclConfig, TRUE, $debug);
$isGranted = $aclItem->getValue();
if ($isGranted) break;
}
return $isGranted;
}
/**
* Retourne le slug du premier catalogue où le user a les accès ACL
*
* @param User|null $user
* @param Product $product
*
* @return mixed|null
* @throws JsonException
*/
public function getUserFirstGrantedCatalogSlugForProduct(?User $user, Product $product)
{
foreach ($product->getCatalogues() as $catalogue) {
//TODO @manu la vérification pour savoir si le produit est dans le catalogue est lourde, sans doute redondante si on a déjà récupéré le produit via le JSON pour obtenir la variable $product
if ($this->userIsGrantedCatalogue($user, $catalogue) /*&& $this->jsonCatalogueService->isProductInCatalogue($product->getSku(), $catalogue)*/) {
return $catalogue;
}
}
return NULL;
}
/**
* Indique si un user a les droits d'accès au document
*
* Les ACL du document se font lors de la création de l'entité
* Choix des roles => si pas de choix tout le monde voit
* Choix des jobs => si pas de choix tout le monde voit
* Choix des univers => si pas de choix tout le monde voit
*
* @param User|null $user
* @param Parameter $document
*
* @return bool
*
* @throws JsonException
* @throws Exception
*/
public function userIsGrantedOnDocument(?User $user, Parameter $document): bool
{
if ($user === NULL) {
return TRUE;
}
$isGranted = TRUE;
//Check sur job
if ($document->getDisplayJob() !== NULL
&& !in_array($user->getJob(), $document->getDisplayJob(), TRUE)
) {
return FALSE;
}
//Check sur le role
if ($document->getDisplayRole() !== NULL && array_diff(
$user->getRoles(),
$document->getDisplayRole(),
) !== []) {
return FALSE;
}
$universes = $document->getDisplayUniverses();
// Si config par univers on regarde si ça match
if ($universes !== NULL) {
$isGranted = FALSE;
foreach ($user->getUniverses() as $userUnivers) {
if (in_array($userUnivers->getSlug(), $universes, TRUE)) {
$isGranted = TRUE;
break;
}
}
if (!$isGranted) {
return FALSE;
}
}
// Check si la selection des documents est activé sur des parents dont il dépend et qui ont une extension
if ($user->getParents()->getValues() !== []) {
$isGranted = $this->parameterService->isDocumentSelectedByUserParents($user, $document->getId());
}
// sur le user lui-même si une extension existe
$currentUserDocumentSelected = $user->getExtensionBySlug(UserExtension::DOCUMENT_SELECTION);
if ($currentUserDocumentSelected !== NULL) {
$isGranted = $this->parameterService->isDocumentSelectedForUser($user, $document->getId());
}
return $isGranted;
}
/**
* Définit si un element d'un formType est visible en fonction de ses droits configurés dans le yaml
*
* @param AclSettingConfig|null $aclConfig
* @param array $config
* @param bool $debug
*
* @return bool
* @throws JsonException
*/
public function currentUserIsGrantedByConfigFormType(?AclSettingConfig $aclConfig, array $config = [], bool $debug = FALSE): bool
{
// Pas de config, tout le monde voit
if ($aclConfig === NULL &&
!array_key_exists('jobs', $config) &&
!array_key_exists('roles', $config) &&
!array_key_exists('univers', $config)) {
return TRUE;
}
$tokenStorage = $this->tokenStorage->getToken();
$currentUser = $tokenStorage ? $tokenStorage->getUser() : NULL;
if (!$currentUser instanceof User) {
return FALSE;
}
// Le super_admin et dev doivent pouvoir tout voir
if ($currentUser->isDeveloper() || $currentUser->isSuperAdmin()) {
return TRUE;
}
if (array_key_exists('jobs', $config) && $config[ 'jobs' ] !== null) {
return $this->canDisplayByJobs($currentUser, $config[ 'jobs' ]);
}
if (array_key_exists('roles', $config) && $config[ 'roles' ] !== null) {
return $this->canDisplayByRoles($currentUser, $config[ 'roles' ]);
}
$universesConfig = isset($config[ 'univers' ]) && count($config[ 'univers' ]) > 0;
// cas 2 $aclConfig + config => on ne garde que les univers pour le moment, $aclConfig prend le dessus.
$isGranted = !$aclConfig || $this->userIsGranted($currentUser, $aclConfig->toArray(), $debug);
if ($isGranted && $universesConfig) {
return $this->canDisplayByUniverses($currentUser, $config[ 'univers' ]);
}
return $isGranted;
}
/**
* Vérifie si on peut afficher un élément en fonction du roles
*
* @param User $user
* @param array $roles
*
* @return bool
*/
public function canDisplayByRoles(User $user, array $roles): bool
{
$userRole = $user->getRoles();
if(array_diff($userRole, $roles) === []) {
return TRUE;
}
return FALSE;
}
/**
* Vérifie si on peut afficher un élément en fonction du jobs
*
* @param User $user
* @param array $jobs
*
* @return bool
*/
public function canDisplayByJobs(User $user, array $jobs): bool
{
$userJob = $user->getJob();
if(in_array($userJob, $jobs)) {
return TRUE;
}
return FALSE;
}
/**
* Vérifie si on peut afficher un élément en fonction des univers de l'utilisateur
*
* @param User $user
* @param array $universes
*
* @return bool
*/
public function canDisplayByUniverses(User $user, array $universes): bool
{
$userUniverses = $user->getUniverses();
foreach ($userUniverses as $univers) {
if (in_array($univers->getSlug(), $universes, TRUE)) {
return TRUE;
}
}
return FALSE;
}
}