<?php
namespace App\Service\Module;
use App\Entity\Agence;
use App\Entity\AgenceModule;
use App\Service\Module\ModuleMenuRegistry;
use App\Service\Module\SourceModuleService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Service de vérification d'accès aux modules et fonctionnalités
*
* Permet de vérifier si une agence a accès à un module ou une fonctionnalité spécifique
* en fonction de la configuration des modules et menus actifs
*/
class AccesModuleService
{
private ?Agence $agenceCourante = null;
public function __construct(
private EntityManagerInterface $entityManager,
private RequestStack $requestStack,
private ModuleMenuRegistry $menuRegistry,
private SourceModuleService $sourceModuleService
) {
// Récupérer l'agence depuis la session
$session = $this->requestStack->getSession();
if ($session && $session->has('user')) {
$user = $session->get('user');
if (isset($user['agence'])) {
$this->agenceCourante = $this->entityManager
->getRepository(Agence::class)
->find($user['agence']);
}
}
}
/**
* Vérifie si l'agence courante peut accéder à une route donnée
*
* @param string $route Nom de la route Symfony
* @return bool
*/
public function agencePeutAccederRoute(string $route): bool
{
if (!$this->agenceCourante) {
return false;
}
return $this->agencePeutAccederRouteAvecAgence($this->agenceCourante, $route);
}
/**
* Vérifie si une agence spécifique peut accéder à une route donnée
*
* @param Agence $agence
* @param string $route Nom de la route Symfony
* @return bool
*/
public function agencePeutAccederRouteAvecAgence(Agence $agence, string $route): bool
{
$codesActifs = $this->codesModulesActifs($agence);
$agenceModulesIndex = $this->indexerAgenceModules($agence);
foreach ($codesActifs as $codeModule) {
$am = $agenceModulesIndex[$codeModule] ?? null;
$menusActifs = $am ? ($am->getMenusActifs() ?? []) : [];
if (in_array($route, $menusActifs, true)) {
return true;
}
}
return false;
}
/**
* Vérifie si l'agence courante a un module actif
*
* @param string $codeModule Code du module (ex: 'boulangerie')
* @return bool
*/
public function agencePeutAccederModule(string $codeModule): bool
{
if (!$this->agenceCourante) {
return false;
}
return $this->agencePeutAccederModuleAvecAgence($this->agenceCourante, $codeModule);
}
/**
* Vérifie si une agence spécifique a un module actif
*
* @param Agence $agence
* @param string $codeModule Code du module (ex: 'boulangerie')
* @return bool
*/
public function agencePeutAccederModuleAvecAgence(Agence $agence, string $codeModule): bool
{
return in_array($codeModule, $this->codesModulesActifs($agence), true);
}
/**
* Retourne la liste des fonctionnalités accessibles pour l'agence courante
*
* @param string|null $codeModule Filtrer par module spécifique (optionnel)
* @return array Liste des routes accessibles
*/
public function getFonctionnalitesAccessibles(?string $codeModule = null): array
{
if (!$this->agenceCourante) {
return [];
}
return $this->getFonctionnalitesAccessiblesAvecAgence($this->agenceCourante, $codeModule);
}
/**
* Retourne la liste des fonctionnalités accessibles pour une agence spécifique
*
* @param Agence $agence
* @param string|null $codeModule Filtrer par module spécifique (optionnel)
* @return array Liste des routes accessibles
*/
public function getFonctionnalitesAccessiblesAvecAgence(Agence $agence, ?string $codeModule = null): array
{
$codesActifs = $this->codesModulesActifs($agence);
if ($codeModule !== null) {
$codesActifs = in_array($codeModule, $codesActifs, true) ? [$codeModule] : [];
}
$agenceModulesIndex = $this->indexerAgenceModules($agence);
$fonctionnalites = [];
foreach ($codesActifs as $code) {
$am = $agenceModulesIndex[$code] ?? null;
$menusActifs = $am ? ($am->getMenusActifs() ?? []) : [];
foreach ($menusActifs as $route) {
$fonctionnalites[] = ['module' => $code, 'route' => $route];
}
}
return $fonctionnalites;
}
/**
* Définit l'agence courante manuellement (utile pour les tests ou cas spéciaux)
*
* @param Agence|null $agence
* @return self
*/
public function setAgenceCourante(?Agence $agence): self
{
$this->agenceCourante = $agence;
return $this;
}
/**
* Retourne l'agence courante
*
* @return Agence|null
*/
public function getAgenceCourante(): ?Agence
{
return $this->agenceCourante;
}
/**
* Retourne tous les modules actifs de l'agence courante
*
* @return array Liste des codes de modules actifs
*/
public function getModulesActifs(): array
{
if (!$this->agenceCourante) {
return [];
}
return $this->getModulesActifsAvecAgence($this->agenceCourante);
}
/**
* Retourne tous les modules actifs d'une agence spécifique
*
* @param Agence $agence
* @return array Liste des codes de modules actifs
*/
public function getModulesActifsAvecAgence(Agence $agence): array
{
return $this->codesModulesActifs($agence);
}
/**
* Retourne les menus de navigation récursifs pour l'agence courante.
* Niveau 1 : module (parent accordéon)
* Niveau 2+ : arbre récursif complet (sections > nœuds > feuilles)
* Seuls les nœuds ayant au moins une route active dans leur sous-arbre sont inclus.
*
* @return array
*/
public function getMenusNavigation(): array
{
if (!$this->agenceCourante) {
return [];
}
$codesActifs = $this->codesModulesActifs($this->agenceCourante);
$agenceModulesIndex = $this->indexerAgenceModules($this->agenceCourante);
$navigation = [];
foreach ($codesActifs as $codeModule) {
if (!$this->menuRegistry->moduleHasMenuStructure($codeModule)) {
continue;
}
$agenceModule = $agenceModulesIndex[$codeModule] ?? null;
$menusActifs = $agenceModule ? ($agenceModule->getMenusActifs() ?? []) : [];
$menus = $this->menuRegistry->getMenusForModule($codeModule);
$enfants = $this->filtrerArbreRecursif($menus, $menusActifs);
if (empty($enfants)) {
continue;
}
$navigation[] = [
'code' => $codeModule,
'nom' => \App\Service\Module::MODULES[$codeModule] ?? $codeModule,
'icone' => 'fas ' . (\App\Service\Module::MODULES_ICONS[$codeModule] ?? 'fa-cube'),
'route' => null,
'enfants' => $enfants,
];
}
return $navigation;
}
private function codesModulesActifs(Agence $agence): array
{
$agenceModules = $this->entityManager
->getRepository(AgenceModule::class)
->findBy(['agence' => $agence]);
$codesManuel = array_map(
fn(AgenceModule $am) => $am->getCodeModule(),
array_filter($agenceModules, fn(AgenceModule $am) => $am->isActif())
);
$codesAbonnement = $this->sourceModuleService->codesModulesAbonnement($agence);
return array_values(array_unique(array_merge($codesManuel, $codesAbonnement)));
}
private function indexerAgenceModules(Agence $agence): array
{
$agenceModules = $this->entityManager
->getRepository(AgenceModule::class)
->findBy(['agence' => $agence]);
$index = [];
foreach ($agenceModules as $am) {
$index[$am->getCodeModule()] = $am;
}
return $index;
}
/**
* Filtre récursif : ne garde que les nœuds ayant au moins une route active dans leur sous-arbre.
* Les feuilles (route non null) ne sont incluses que si leur route est dans $menusActifs.
*/
private function filtrerArbreRecursif(array $menus, array $menusActifs): array
{
$resultat = [];
foreach ($menus as $menu) {
if (!empty($menu['route'])) {
// Feuille : incluse seulement si route active
if (in_array($menu['route'], $menusActifs, true)) {
$resultat[] = [
'code' => $menu['code'],
'nom' => $menu['nom'],
'icone' => $menu['icone'],
'route' => $menu['route'],
'enfants' => [],
];
}
} else {
// Nœud intermédiaire : filtrer ses enfants récursivement
$enfantsFiltres = $this->filtrerArbreRecursif($menu['enfants'] ?? [], $menusActifs);
if (!empty($enfantsFiltres)) {
$resultat[] = [
'code' => $menu['code'],
'nom' => $menu['nom'],
'icone' => $menu['icone'],
'route' => null,
'enfants' => $enfantsFiltres,
];
}
}
}
return $resultat;
}
}