gestion des roles et applications

This commit is contained in:
Charles 2025-11-19 16:58:49 +01:00
parent 8c9a5da604
commit 7e08998005
4 changed files with 356 additions and 121 deletions

View File

@ -8,7 +8,9 @@ use App\Entity\User;
use App\Entity\UserOrganizatonApp; use App\Entity\UserOrganizatonApp;
use App\Entity\UsersOrganizations; use App\Entity\UsersOrganizations;
use App\Form\UserForm; use App\Form\UserForm;
use App\Repository\AppsRepository;
use App\Repository\OrganizationsRepository; use App\Repository\OrganizationsRepository;
use App\Repository\RolesRepository;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use App\Repository\UsersOrganizationsRepository; use App\Repository\UsersOrganizationsRepository;
use App\Service\ActionService; use App\Service\ActionService;
@ -44,7 +46,13 @@ class UserController extends AbstractController
private readonly UserOrganizationService $userOrganizationService, private readonly UserOrganizationService $userOrganizationService,
private readonly UserRepository $userRepository, private readonly UserRepository $userRepository,
private readonly UsersOrganizationsRepository $uoRepository, private readonly UsersOrganizationsRepository $uoRepository,
private readonly OrganizationsRepository $organizationRepository, private readonly LoggerInterface $logger, private readonly EmailService $emailService, private readonly AwsService $awsService, private readonly OrganizationsService $organizationsService, private readonly OrganizationsRepository $organizationRepository,
private readonly LoggerInterface $logger,
private readonly EmailService $emailService,
private readonly AwsService $awsService,
private readonly OrganizationsService $organizationsService,
private readonly AppsRepository $appsRepository,
private readonly RolesRepository $rolesRepository,
) )
{ {
} }
@ -54,41 +62,148 @@ class UserController extends AbstractController
public function view(int $id, Request $request): Response public function view(int $id, Request $request): Response
{ {
$this->denyAccessUnlessGranted('ROLE_USER'); $this->denyAccessUnlessGranted('ROLE_USER');
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
if ($this->userService->hasAccessTo($actingUser)) {
$user = $this->userRepository->find($id); if (!$this->userService->hasAccessTo($actingUser)) {
try {
$orgId = $request->query->get('organizationId');
if ($orgId) {
$orgs = $this->organizationRepository->findBy(['id' => $orgId]);
$uo = $this->uoRepository->findBy(['users' => $user, 'organization' => $orgs]);
if (!$uo) {
throw $this->createNotFoundException(self::NOT_FOUND);
}
$uoActive = $uo[0]->isActive();
} else {
$uo = $this->uoRepository->findBy(['users' => $user, 'isActive' => true]);
foreach ($uo as $u) {
$orgs[] = $u->getOrganization();
}
}
$uoa = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['userOrganization' => $uo, 'isActive' => true]);
$uoas = $this->userOrganizationAppService->groupUserOrganizationAppsByApplication($uoa);
$this->actionService->createAction("View user information", $actingUser, null, $user->getUserIdentifier());
} catch (\Exception $e) {
//ignore
}
} else {
throw $this->createAccessDeniedException(self::ACCESS_DENIED); throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} }
$user = $this->userRepository->find($id);
try {
$orgId = $request->query->get('organizationId');
$apps = $this->appsRepository->findAll();
$roles = $this->rolesRepository->findAll();
$data = [
'roles' => $roles,
];
$uoList = [];
$singleUo = null;
$uoActive = null;
$orgs = [];
if ($orgId) {
// Specific organization context
$orgs = $this->organizationRepository->findBy(['id' => $orgId]);
$uoList = $this->uoRepository->findBy([
'users' => $user,
'organization' => $orgs,
]);
if (!$uoList) {
throw $this->createNotFoundException(self::NOT_FOUND);
}
$singleUo = $uoList[0];
$uoActive = $singleUo->isActive();
} else {
// All active organizations
$uoList = $this->uoRepository->findBy([
'users' => $user,
'isActive'=> true,
]);
foreach ($uoList as $u) {
$orgs[] = $u->getOrganization();
}
if (count($uoList) === 1) {
$singleUo = $uoList[0];
$uoActive = $singleUo->isActive();
}
}
$data['uoList'] = $uoList;
$data['singleUo'] = $singleUo;
// Load user-organization-app roles (can be empty)
$uoa = $this->entityManager
->getRepository(UserOrganizatonApp::class)
->findBy([
'userOrganization' => $uoList,
'isActive' => true,
]);
// Group existing UOA per app
$uoas = $this->userOrganizationAppService
->groupUserOrganizationAppsByApplication($uoa);
// ---------- HERE: create empty groups for apps with no UOA ----------
// Index existing groups by app id
$indexedUoas = [];
foreach ($uoas as $group) {
$indexedUoas[$group['application']->getId()] = $group;
}
// Load all possible roles once
$allRoles = $this->entityManager->getRepository(Roles::class)->findAll();
foreach ($apps as $app) {
$appId = $app->getId();
if (!isset($indexedUoas[$appId])) {
// No UOA for this app yet: create an empty group
$indexedUoas[$appId] = [
'uoId' => $singleUo ? $singleUo->getId() : null,
'application' => $app,
'roles' => [],
'rolesArray' => [],
'selectedRoleIds' => [],
];
foreach ($allRoles as $role) {
// Same security logic: ADMIN cannot assign SUPER ADMIN
if ($this->isGranted('ROLE_ADMIN')
&& !$this->isGranted('ROLE_SUPER_ADMIN')
&& $role->getName() === 'SUPER ADMIN') {
continue;
}
$indexedUoas[$appId]['rolesArray'][] = [
'id' => $role->getId(),
'name' => $role->getName(),
];
}
}
}
if(!$orgId){
$data['singleUo'] = null;
}
// Overwrite $uoas to include groups for *all* apps
$uoas = array_values($indexedUoas);
$data['uoas'] = $uoas;
// -------------------------------------------------------------------
// Compute "can edit" flag: admin AND exactly one UO
$canEditRoles = $this->isGranted('ROLE_ADMIN') && count($uoList) === 1;
$this->actionService->createAction(
"View user information",
$actingUser,
null,
$user->getUserIdentifier()
);
} catch (\Exception $e) {
$canEditRoles = false;
$this->logger->error($e->getMessage());
}
return $this->render('user/show.html.twig', [ return $this->render('user/show.html.twig', [
'user' => $user, 'user' => $user,
'uoas' => $uoas ?? null, 'uoas' => $uoas ?? null,
'orgs' => $orgs ?? null, 'orgs' => $orgs ?? null,
'organizationId' => $orgId ?? null, 'organizationId' => $orgId ?? null,
'uoActive' => $uoActive ?? null// specific for single organization context and deactivate user from said org 'uoActive' => $uoActive ?? null,
'apps' => $apps ?? [],
'data' => $data ?? [],
'canEditRoles' => $canEditRoles ?? false,
]); ]);
} }
//TODO : MONOLOG //TODO : MONOLOG
#[Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])] #[Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])]
public function edit(int $id, Request $request): Response public function edit(int $id, Request $request): Response
@ -162,8 +277,8 @@ class UserController extends AbstractController
$this->logger->notice("User added to organization " . $org->getName()); $this->logger->notice("User added to organization " . $org->getName());
$this->emailService->sendExistingUserNotificationEmail($existingUser, $org); $this->emailService->sendExistingUserNotificationEmail($existingUser, $org);
$this->logger->notice("Existing user notification email sent to " . $existingUser->getUserIdentifier()); $this->logger->notice("Existing user notification email sent to " . $existingUser->getUserIdentifier());
$data = ['user'=>$uo->getUsers(), 'organization'=>$uo->getOrganization()]; $data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
$this->organizationsService->notifyOrganizationAdmins($data,'USER_INVITED'); $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
return $this->redirectToRoute('organization_show', ['id' => $orgId]); return $this->redirectToRoute('organization_show', ['id' => $orgId]);
} }
@ -195,8 +310,8 @@ class UserController extends AbstractController
$this->logger->notice("User added to organization " . $org->getName()); $this->logger->notice("User added to organization " . $org->getName());
$this->emailService->sendPasswordSetupEmail($user, $orgId); $this->emailService->sendPasswordSetupEmail($user, $orgId);
$this->logger->notice("Password setup email sent to " . $user->getUserIdentifier()); $this->logger->notice("Password setup email sent to " . $user->getUserIdentifier());
$data = ['user'=>$uo->getUsers(), 'organization'=>$uo->getOrganization()]; $data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
$this->organizationsService->notifyOrganizationAdmins($data,'USER_INVITED'); $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
} }
} }
$this->actionService->createAction("Create new user", $actingUser, null, $user->getUserIdentifier()); $this->actionService->createAction("Create new user", $actingUser, null, $user->getUserIdentifier());
@ -204,7 +319,7 @@ class UserController extends AbstractController
$this->entityManager->persist($user); $this->entityManager->persist($user);
$this->entityManager->flush(); $this->entityManager->flush();
if( $orgId) { if ($orgId) {
return $this->redirectToRoute('organization_show', ['organizationId' => $orgId]); return $this->redirectToRoute('organization_show', ['organizationId' => $orgId]);
} }
return $this->redirectToRoute('user_index'); return $this->redirectToRoute('user_index');
@ -217,7 +332,7 @@ class UserController extends AbstractController
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error($e->getMessage()); $this->logger->error($e->getMessage());
if( $orgId) { if ($orgId) {
return $this->redirectToRoute('organization_show', ['id' => $orgId]); return $this->redirectToRoute('organization_show', ['id' => $orgId]);
} }
return $this->redirectToRoute('user_index'); return $this->redirectToRoute('user_index');
@ -300,7 +415,7 @@ class UserController extends AbstractController
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo); $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo);
$data = ['user' => $user, $data = ['user' => $user,
'organization' => $org]; 'organization' => $org];
$this->organizationsService->notifyOrganizationAdmins($data,"USER_DEACTIVATED"); $this->organizationsService->notifyOrganizationAdmins($data, "USER_DEACTIVATED");
$this->entityManager->persist($uo); $this->entityManager->persist($uo);
$this->entityManager->flush(); $this->entityManager->flush();
$this->actionService->createAction("Deactivate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier()); $this->actionService->createAction("Deactivate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier());
@ -339,13 +454,14 @@ class UserController extends AbstractController
$this->actionService->createAction("Activate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier()); $this->actionService->createAction("Activate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier());
$data = ['user' => $user, $data = ['user' => $user,
'organization' => $org]; 'organization' => $org];
$this->organizationsService->notifyOrganizationAdmins($data,"USER_ACTIVATED"); $this->organizationsService->notifyOrganizationAdmins($data, "USER_ACTIVATED");
return $this->redirectToRoute('user_index'); return $this->redirectToRoute('user_index');
} }
throw $this->createAccessDeniedException(self::ACCESS_DENIED); throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} }
//TODO : MONOLOG + remove picture from bucket //TODO : MONOLOG + remove picture from bucket
#[Route('/delete/{id}', name: 'delete', methods: ['GET', 'POST'])] #[Route('/delete/{id}', name: 'delete', methods: ['GET', 'POST'])]
public function delete(int $id, Request $request): Response public function delete(int $id, Request $request): Response
@ -368,7 +484,7 @@ class UserController extends AbstractController
$this->actionService->createAction("Delete user", $actingUser, null, $user->getUserIdentifier()); $this->actionService->createAction("Delete user", $actingUser, null, $user->getUserIdentifier());
$data = ['user' => $user, $data = ['user' => $user,
'organization' => null]; 'organization' => null];
$this->organizationsService->notifyOrganizationAdmins($data,"USER_DELETED"); $this->organizationsService->notifyOrganizationAdmins($data, "USER_DELETED");
return new Response('', Response::HTTP_NO_CONTENT); //204 return new Response('', Response::HTTP_NO_CONTENT); //204
} }
@ -382,8 +498,7 @@ class UserController extends AbstractController
if ($this->userService->hasAccessTo($actingUser, true)) { if ($this->userService->hasAccessTo($actingUser, true)) {
$uo = $this->userOrganizationService->getByIdOrFail($id); $uo = $this->userOrganizationService->getByIdOrFail($id);
$application = $this->entityManager->getRepository(Apps::class)->find($request->get('appId'));
$application = $this->entityManager->getRepository(Apps::class)->find($request->get('applicationId'));
if (!$application) { if (!$application) {
throw $this->createNotFoundException(self::NOT_FOUND); throw $this->createNotFoundException(self::NOT_FOUND);
} }
@ -395,12 +510,17 @@ class UserController extends AbstractController
} }
if (!empty($selectedRolesIds)) { if (!empty($selectedRolesIds)) {
$this->userOrganizationAppService->syncRolesForUserOrganizationApp( if (!in_array((string)$roleUser->getId(), $selectedRolesIds, true)){
$uo, $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application);
$application, }else{
$selectedRolesIds, $this->userOrganizationAppService->syncRolesForUserOrganizationApp(
$actingUser $uo,
); $application,
$selectedRolesIds,
$actingUser
);
}
} else { } else {
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application); $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application);
} }
@ -558,7 +678,7 @@ class UserController extends AbstractController
$picture = $this->awsService->getPublicUrl($_ENV['S3_PORTAL_BUCKET']) . $user->getPictureUrl(); $picture = $this->awsService->getPublicUrl($_ENV['S3_PORTAL_BUCKET']) . $user->getPictureUrl();
$initials = $user->getName()[0] . $user->getSurname()[0]; $initials = $user->getName()[0] . $user->getSurname()[0];
return [ return [
'pictureUrl' =>$picture, 'pictureUrl' => $picture,
'email' => $user->getEmail(), 'email' => $user->getEmail(),
'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()), 'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()),
'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]), 'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]),
@ -645,10 +765,10 @@ class UserController extends AbstractController
} }
$uo->setModifiedAt(new \DateTimeImmutable()); $uo->setModifiedAt(new \DateTimeImmutable());
try { try {
$data = ['user'=>$uo->getUsers(), 'organization'=>$uo->getOrganization()]; $data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
$this->emailService->sendPasswordSetupEmail($user, $orgId); $this->emailService->sendPasswordSetupEmail($user, $orgId);
$this->logger->info("Invitation email resent to user " . $user->getUserIdentifier() . " for organization " . $org->getName()); $this->logger->info("Invitation email resent to user " . $user->getUserIdentifier() . " for organization " . $org->getName());
$this->organizationsService->notifyOrganizationAdmins($data,'USER_INVITED'); $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK); return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error("Error resending invitation email to user " . $user->getUserIdentifier() . " for organization " . $org->getName() . ": " . $e->getMessage()); $this->logger->error("Error resending invitation email to user " . $user->getUserIdentifier() . " for organization " . $org->getName() . ": " . $e->getMessage());

View File

@ -30,20 +30,18 @@ class UserOrganizationAppService
$grouped = []; $grouped = [];
foreach ($userOrgApps as $uoa) { foreach ($userOrgApps as $uoa) {
if(!$uoa->getRole()->getName() === 'USER') {
continue; // Skip USER role
}
$app = $uoa->getApplication(); $app = $uoa->getApplication();
$appId = $app->getId(); $appId = $app->getId();
$roleEntity = $uoa->getRole(); $roleEntity = $uoa->getRole();
if (!isset($grouped[$appId])) { if (!isset($grouped[$appId])) {
$grouped[$appId] = [ $grouped[$appId] = [
'uoId' => $uoa->getUserOrganization()->getId(), 'uoId' => $uoa->getUserOrganization()->getId(),
'application' => $app, 'application' => $app,
'roles' => [], 'roles' => [],
'rolesArray' => [], 'rolesArray' => [],
'selectedRoleIds' => [], 'selectedRoleIds' => [],
]; ];
} }
@ -60,14 +58,11 @@ class UserOrganizationAppService
foreach ($grouped as &$appGroup) { foreach ($grouped as &$appGroup) {
foreach ($allRoles as $role) { foreach ($allRoles as $role) {
// exclude SUPER ADMIN from assignable roles if current user is just ADMIN // exclude SUPER ADMIN from assignable roles if current user is just ADMIN
if ($this->security->isGranted('ROLE_ADMIN') && !$this->security->isGranted('ROLE_SUPER_ADMIN') if ($this->security->isGranted('ROLE_ADMIN')
&& !$this->security->isGranted('ROLE_SUPER_ADMIN')
&& $role->getName() === 'SUPER ADMIN') { && $role->getName() === 'SUPER ADMIN') {
continue; continue;
} }
// exclude USER role from assignable roles
if ($role->getName() === 'USER') {
continue;
}
$appGroup['rolesArray'][] = [ $appGroup['rolesArray'][] = [
'id' => $role->getId(), 'id' => $role->getId(),

View File

@ -1,57 +1,75 @@
{% block body %} {#{% block body %}#}
{% set roles = uoa.roles %} {# #}{# TODO: compare style with/without border #}
{# TODO: compare style with/without border#} {# <div class="card col-6">#}
<div class="card col-6"> {# <div class="card-header d-flex gap-2">#}
<div class="card-header d-flex gap-2"> {# {% if app.logoMiniUrl %}#}
{% if uoa.application.logoUrl %} {# <img src="{{ aws_url ~ app.logoMiniUrl }}" alt="Logo {{ app.name }}"#}
<img src="{{ asset(uoa.application.logoUrl) }}" alt="Logo {{ uoa.application.name }}" {# class="rounded-circle " style="width:50px; height:50px;">#}
class="rounded-circle " style="width:50px; height:50px;"> {# {% endif %}#}
{% endif %} {# <div class="card-title">#}
<div class="card-title"> {# <h1>{{ app.name|title }}</h1>#}
<h1>{{ uoa.application.name|title }}</h1> {# </div>#}
</div>
</div> {# </div>#}
<div class="card-body"> {# <div class="card-body">#}
<div class="row"> {# <div class="row">#}
{# TODO: pb avec le |raw retour à la ligne#} {# <p><b> Description : </b>{{ app.descriptionSmall|default('Aucune description disponible.')|raw }}</p>#}
{# maybe remove Description label #} {# </div>#}
<p><b> Description : </b>{{ uoa.application.descriptionSmall|default('Aucune description disponible.')|raw }}</p> {# {% if is_granted("ROLE_ADMIN") %}#}
</div> {# <form method="POST"#}
{% if is_granted('ROLE_ADMIN') %} {# action="{{ path('user_application_role', { id : data.test1 }) }}">#}
{# TODO: Can be turn into an ajax function#} {# <div class="form-group mb-3">#}
<form method="POST" {# <label for="roles-{{ app.id }}"><b>Rôles :</b></label>#}
action="{{ path('user_application_role', { id : uoa.uoId }) }}" {# <div class="form-check">#}
data-controller="user" {# {% for role in roles %}#}
data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}" {# <input class="form-check"> #}
data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}"> {# </div>#}
<div class="form-group mb-3"> {# </div>#}
<label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label> {# </form>#}
<select data-user-target="select" {# {% endif %}#}
class="choices" {# </div>#}
id="roles-{{ uoa.application.id }}" {# </div>#}
name="roles[]" {# #}{# <div class="card-body"> #}
multiple> {# #}{# <div class="row"> #}
</select> {# #}{# TODO: pb avec le |raw retour à la ligne #}
</div> {# #}{# maybe remove Description label #}
<input hidden type="text" value="{{ uoa.application.id }}" name="applicationId"> {# #}{# <p><b> Description : </b>{{ uoa.application.descriptionSmall|default('Aucune description disponible.')|raw }}</p> #}
<button type="submit" class="btn btn-primary">Sauvegarder</button> {# #}{# </div> #}
</form> {# #}{# {% if is_granted('ROLE_ADMIN') %} #}
{% else %}
<label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label> {# #}{# TODO: Can be turn into an ajax function #}
<select data-user-target="select" {# #}{# <form method="POST" #}
class="choices" {# #}{# action="{{ path('user_application_role', { id : uoa.uoId }) }}" #}
id="roles-{{ uoa.application.id }}" {# #}{# data-controller="user" #}
name="roles[]" {# #}{# data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}" #}
data-controller="user" {# #}{# data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}"> #}
data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}" {# #}{# <div class="form-group mb-3"> #}
data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}" {# #}{# <label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label> #}
multiple {# #}{# <select data-user-target="select" #}
disabled> {# #}{# class="choices" #}
</select> {# #}{# id="roles-{{ uoa.application.id }}" #}
{% endif %} {# #}{# name="roles[]" #}
</div> {# #}{# multiple> #}
</div> {# #}{# </select> #}
{# #}{# </div> #}
{# #}{# <input hidden type="text" value="{{ uoa.application.id }}" name="applicationId"> #}
{# #}{# <button type="submit" class="btn btn-primary">Sauvegarder</button> #}
{# #}{# </form> #}
{# #}{# {% else %} #}
{# #}{# <label for="roles-{{ uoa.application.id }}"><b>Rôles :</b></label> #}
{# #}{# <select data-user-target="select" #}
{# #}{# class="choices" #}
{# #}{# id="roles-{{ uoa.application.id }}" #}
{# #}{# name="roles[]" #}
{# #}{# data-controller="user" #}
{# #}{# data-user-roles-array-value="{{ uoa.rolesArray|json_encode }}" #}
{# #}{# data-user-selected-role-ids-value="{{ uoa.selectedRoleIds|json_encode }}" #}
{# #}{# multiple #}
{# #}{# disabled> #}
{# #}{# </select> #}
{# #}{# {% endif %} #}
{# #}{# </div> #}
{# #}{# </div> #}
{% endblock %} {#{% endblock %}#}

View File

@ -45,10 +45,112 @@
</div> </div>
<div class="d-flex gap-2 card-body"> <div class="d-flex gap-2 card-body">
{% for uoa in uoas %} {% for app in apps %}
{% include 'user/application/information.html.twig' %} <div class="card col-6">
<div class="card-header d-flex gap-2">
{% if app.logoMiniUrl %}
<img src="{{ aws_url ~ app.logoMiniUrl }}" alt="Logo {{ app.name }}"
class="rounded-circle " style="width:50px; height:50px;">
{% endif %}
<div class="card-title">
<h1>{{ app.name|title }}</h1>
</div>
</div>
<div class="card-body">
<div class="row">
<p><b>Description
:</b> {{ app.descriptionSmall|default('Aucune description disponible.')|raw }}
</p>
</div>
{# EDITABLE if admin and exactly one UO #}
{% if canEditRoles and data.singleUo is not null %}
<form method="POST"
action="{{ path('user_application_role', { id: data.singleUo.id }) }}">
{# for this app, find its grouped info #}
{# Find the group for this specific app #}
{% set appGroup = null %}
{% for group in data.uoas|default([]) %}
{% if group.application.id == app.id %}
{% set appGroup = group %}
{% endif %}
{% endfor %}
<div class="form-group mb-3">
<label for="roles-{{ app.id }}"><b>Rôles :</b></label>
<div class="form-check">
{% if appGroup %}
{# Use rolesArray: filtered by current user's level (no SUPER ADMIN for plain ADMIN, etc.) #}
{% for role in appGroup.rolesArray %}
<input class="form-check-input" type="checkbox"
name="roles[]"
value="{{ role.id }}"
id="role-{{ role.id }}-app-{{ app.id }}"
{% if role.id in appGroup.selectedRoleIds %}checked{% endif %}>
<label class="form-check"
for="role-{{ role.id }}-app-{{ app.id }}">
{% if role.name == 'USER' %}
Accès
{% else %}
{{ role.name|capitalize }}
{% endif %}
</label>
{% endfor %}
{% else %}
<p class="text-muted">Aucun rôle défini pour cette
application.</p>
{% endif %}
</div>
<button type="submit" name="appId" value="{{ app.id }}"
class="btn btn-primary mt-2">
Sauvegarder
</button>
</div>
</form>
{# READ ONLY otherwise #}
{% else %}
{% set appGroup = null %}
{% for group in data.uoas|default([]) %}
{% if group.application.id == app.id %}
{% set appGroup = group %}
{% endif %}
{% endfor %}
<div class="form-group mb-3">
<label for="roles-{{ app.id }}"><b>Rôles :</b></label>
<div class="form-check">
{% if appGroup %}
{# Use rolesArray: filtered by current user's level (no SUPER ADMIN for plain ADMIN, etc.) #}
{% for role in appGroup.rolesArray %}
<input class="form-check-input" type="checkbox"
disabled
name="roles[]"
value="{{ role.id }}"
id="role-{{ role.id }}-app-{{ app.id }}"
{% if appGroup and role.id in appGroup.selectedRoleIds %}checked{% endif %}>
<label class="form-check"
for="role-{{ role.id }}-app-{{ app.id }}">
{{ role.name }}
</label>
{% endfor %}
{% else %}
<p class="text-muted">Aucun rôle défini pour cette
application.</p>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -59,7 +161,7 @@
{% endblock %} {% endblock %}
{% block title %} {% block title %}
{% endblock %} {% endblock %}