783 lines
35 KiB
PHP
783 lines
35 KiB
PHP
<?php
|
||
|
||
namespace App\Controller;
|
||
|
||
use App\Entity\Apps;
|
||
use App\Entity\Roles;
|
||
use App\Entity\User;
|
||
use App\Entity\UserOrganizatonApp;
|
||
use App\Entity\UsersOrganizations;
|
||
use App\Form\UserForm;
|
||
use App\Repository\AppsRepository;
|
||
use App\Repository\OrganizationsRepository;
|
||
use App\Repository\RolesRepository;
|
||
use App\Repository\UserRepository;
|
||
use App\Repository\UsersOrganizationsRepository;
|
||
use App\Service\ActionService;
|
||
use App\Service\AwsService;
|
||
use App\Service\EmailService;
|
||
use App\Service\OrganizationsService;
|
||
use App\Service\UserOrganizationAppService;
|
||
use App\Service\UserOrganizationService;
|
||
use App\Service\UserService;
|
||
use Doctrine\ORM\EntityManagerInterface;
|
||
use Psr\Log\LoggerInterface;
|
||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||
use Symfony\Component\HttpFoundation\Request;
|
||
use Symfony\Component\HttpFoundation\Response;
|
||
use Symfony\Component\Mailer\Mailer;
|
||
use Symfony\Component\Mailer\MailerInterface;
|
||
use Symfony\Component\Mime\Email;
|
||
use Symfony\Component\Routing\Attribute\Route;
|
||
|
||
#[Route(path: '/user', name: 'user_')]
|
||
class UserController extends AbstractController
|
||
{
|
||
private const NOT_FOUND = 'Entity not found';
|
||
private const ACCESS_DENIED = 'Access denied';
|
||
|
||
public function __construct(
|
||
private readonly EntityManagerInterface $entityManager,
|
||
private readonly UserService $userService,
|
||
private readonly ActionService $actionService,
|
||
private readonly UserOrganizationAppService $userOrganizationAppService,
|
||
private readonly UserOrganizationService $userOrganizationService,
|
||
private readonly UserRepository $userRepository,
|
||
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 AppsRepository $appsRepository,
|
||
private readonly RolesRepository $rolesRepository,
|
||
)
|
||
{
|
||
}
|
||
|
||
|
||
#[Route('/view/{id}', name: 'show', methods: ['GET'])]
|
||
public function view(int $id, Request $request): Response
|
||
{
|
||
// Accès : uniquement utilisateur authentifié
|
||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||
|
||
// Utilisateur courant (acting user) via UserService
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
|
||
// Vérification des droits d'accès supplémentaires
|
||
if (!$this->userService->hasAccessTo($actingUser)) {
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
}
|
||
|
||
// Chargement de l'utilisateur cible à afficher
|
||
$user = $this->userRepository->find($id);
|
||
|
||
try {
|
||
// Paramètre optionnel de contexte organisationnel
|
||
$orgId = $request->query->get('organizationId');
|
||
|
||
// Liste de toutes les applications (pour créer des groupes même si vides)
|
||
$apps = $this->appsRepository->findAll();
|
||
|
||
// Initialisations pour la résolution des UsersOrganizations (UO)
|
||
$singleUo = null;
|
||
$uoActive = null;
|
||
|
||
// get uo or uoS based on orgId
|
||
if ($orgId) {
|
||
// Contexte organisation précis : récupérer l'organisation et les liens UO
|
||
$organization = $this->organizationRepository->findBy(['id' => $orgId]);
|
||
$uoList = $this->uoRepository->findBy([
|
||
'users' => $user,
|
||
'organization' => $organization,
|
||
'isActive' => true,
|
||
]);
|
||
|
||
if (!$uoList) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
|
||
// Si contexte org donné, on retient la première UO (singleUo)
|
||
$singleUo = $uoList[0];
|
||
$data["singleUo"] = $singleUo;
|
||
$uoActive = $singleUo->isActive();
|
||
} else {
|
||
// Pas de contexte org : récupérer toutes les UO actives de l'utilisateur
|
||
$uoList = $this->uoRepository->findBy([
|
||
'users' => $user,
|
||
'isActive' => true,
|
||
]);
|
||
}
|
||
|
||
// Charger les liens UserOrganizationApp (UOA) actifs pour les UO trouvées
|
||
// Load user-organization-app roles (can be empty)
|
||
$uoa = $this->entityManager
|
||
->getRepository(UserOrganizatonApp::class)
|
||
->findBy([
|
||
'userOrganization' => $uoList,
|
||
'isActive' => true,
|
||
]);
|
||
|
||
// Group UOA by app and ensure every app has a group
|
||
$data['uoas'] = $this->userOrganizationAppService
|
||
->groupUserOrganizationAppsByApplication(
|
||
$uoa,
|
||
$apps,
|
||
$singleUo ? $singleUo->getId() : null
|
||
);
|
||
|
||
//Build roles based on user permissions.
|
||
//Admin can't see or edit a super admin user
|
||
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
|
||
$data['rolesArray'] = $this->rolesRepository->findAll();
|
||
} elseif (!$orgId) {
|
||
$data['rolesArray'] = $this->userService->getRolesArrayForUser($actingUser, true);
|
||
} else {
|
||
$data['rolesArray'] = $this->userService->getRolesArrayForUser($actingUser);
|
||
}
|
||
|
||
// -------------------------------------------------------------------
|
||
|
||
// Calcul du flag de modification : utilisateur admin ET exactement 1 UO
|
||
$canEdit = $this->userService->canEditRolesCheck($actingUser, $user, $organization, $this->isGranted('ROLE_ADMIN'));
|
||
|
||
} catch (\Exception $e) {
|
||
// En cas d'erreur, désactiver l'édition et logger l'exception
|
||
$canEdit = false;
|
||
$this->logger->error($e->getMessage());
|
||
}
|
||
return $this->render('user/show.html.twig', [
|
||
'user' => $user,
|
||
'organizationId' => $orgId ?? null,
|
||
'uoActive' => $uoActive ?? null,
|
||
'apps' => $apps ?? [],
|
||
'data' => $data ?? [],
|
||
'canEdit' => $canEdit ?? false,
|
||
]);
|
||
}
|
||
|
||
//TODO : MONOLOG
|
||
#[Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])]
|
||
public function edit(int $id, Request $request): Response
|
||
{
|
||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser)) {
|
||
$user = $this->userRepository->find($id);
|
||
if (!$user) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$form = $this->createForm(UserForm::class, $user);
|
||
$form->handleRequest($request);
|
||
|
||
if ($form->isSubmitted() && $form->isValid()) {
|
||
// Handle file upload
|
||
$picture = $form->get('pictureUrl')->getData();
|
||
|
||
if ($picture) {
|
||
$this->userService->handleProfilePicture($user, $picture);
|
||
}
|
||
$user->setModifiedAt(new \DateTimeImmutable('now'));
|
||
$this->entityManager->persist($user);
|
||
$this->entityManager->flush();
|
||
if ($request->get('organizationId')) {
|
||
$org = $this->organizationRepository->find($request->get('organizationId'));
|
||
if ($org) {
|
||
$this->actionService->createAction("Edit user information", $actingUser, $org, $user->getUserIdentifier());
|
||
}
|
||
} else {
|
||
$this->actionService->createAction("Edit user information", $actingUser, null, $user->getUserIdentifier());
|
||
}
|
||
|
||
return $this->redirectToRoute('user_show', ['id' => $user->getId(), 'organizationId' => $request->get('organizationId')]);
|
||
}
|
||
|
||
return $this->render('user/edit.html.twig', [
|
||
'user' => $user,
|
||
'form' => $form->createView(),
|
||
'organizationId' => $request->get('organizationId')
|
||
]);
|
||
}
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
}
|
||
|
||
#[Route('/new', name: 'new', methods: ['GET', 'POST'])]
|
||
public function new(Request $request): Response
|
||
{
|
||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||
try {
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser)) {
|
||
$user = new User();
|
||
$form = $this->createForm(UserForm::class, $user);
|
||
$form->handleRequest($request);
|
||
$orgId = $request->get('organizationId');
|
||
|
||
if ($form->isSubmitted() && $form->isValid()) {
|
||
$existingUser = $this->userRepository->findOneBy(['email' => $user->getEmail()]);
|
||
if ($existingUser && $orgId) {
|
||
$org = $this->organizationRepository->find($orgId);
|
||
$uo = new UsersOrganizations();
|
||
$uo->setUsers($existingUser);
|
||
$uo->setOrganization($org);
|
||
$uo->setStatut("INVITED");
|
||
$uo->setIsActive(false);
|
||
$uo->setModifiedAt(new \DateTimeImmutable('now'));
|
||
$this->entityManager->persist($uo);
|
||
$this->entityManager->flush();
|
||
$this->actionService->createAction("Create new user", $existingUser, $org, "Added user to organization" . $existingUser->getUserIdentifier() . " for organization " . $org->getName());
|
||
$this->logger->notice("User added to organization " . $org->getName());
|
||
$this->emailService->sendExistingUserNotificationEmail($existingUser, $org);
|
||
$this->logger->notice("Existing user notification email sent to " . $existingUser->getUserIdentifier());
|
||
$data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
|
||
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
|
||
|
||
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
|
||
}
|
||
|
||
// capitalize name and surname
|
||
$user->setName(ucfirst(strtolower($user->getName())));
|
||
$user->setSurname(ucfirst(strtolower($user->getSurname())));
|
||
|
||
// Handle file upload
|
||
$picture = $form->get('pictureUrl')->getData();
|
||
|
||
if ($picture) {
|
||
$this->userService->handleProfilePicture($user, $picture);
|
||
}
|
||
|
||
//FOR TEST PURPOSES, SETTING A DEFAULT RANDOM PASSWORD
|
||
$user->setPassword($this->userService->generateRandomPassword());
|
||
if ($orgId) {
|
||
$org = $this->organizationRepository->find($orgId);
|
||
if ($org) {
|
||
$uo = new UsersOrganizations();
|
||
$uo->setUsers($user);
|
||
$uo->setOrganization($org);
|
||
$uo->setStatut("INVITED");
|
||
$uo->setIsActive(false);
|
||
$uo->setModifiedAt(new \DateTimeImmutable('now'));
|
||
$this->entityManager->persist($uo);
|
||
$this->actionService->createAction("Create new user", $user, $org, "Added user to organization" . $user->getUserIdentifier() . " for organization " . $org->getName());
|
||
$this->logger->notice("User added to organization " . $org->getName());
|
||
$this->emailService->sendPasswordSetupEmail($user, $orgId);
|
||
$this->logger->notice("Password setup email sent to " . $user->getUserIdentifier());
|
||
$data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
|
||
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
|
||
}
|
||
}
|
||
$this->actionService->createAction("Create new user", $actingUser, null, $user->getUserIdentifier());
|
||
$this->logger->notice("User created " . $user->getUserIdentifier());
|
||
$this->entityManager->persist($user);
|
||
$this->entityManager->flush();
|
||
|
||
if ($orgId) {
|
||
return $this->redirectToRoute('organization_show', ['organizationId' => $orgId]);
|
||
}
|
||
return $this->redirectToRoute('user_index');
|
||
}
|
||
}
|
||
return $this->render('user/new.html.twig', [
|
||
'user' => $user,
|
||
'form' => $form->createView(),
|
||
'organizationId' => $orgId
|
||
]);
|
||
} catch (\Exception $e) {
|
||
$this->logger->error($e->getMessage());
|
||
if ($orgId) {
|
||
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
|
||
}
|
||
return $this->redirectToRoute('user_index');
|
||
}
|
||
}
|
||
|
||
//TODO : MONOLOG
|
||
#[Route('/deactivate/{id}', name: 'deactivate', methods: ['GET', 'POST'])]
|
||
public function deactivate(int $id): Response
|
||
{
|
||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser, true)) {
|
||
$user = $this->userRepository->find($id);
|
||
if (!$user) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$user->setIsActive(false);
|
||
$user->setModifiedAt(new \DateTimeImmutable('now'));
|
||
$this->userOrganizationService->deactivateAllUserOrganizationLinks($user, $actingUser);
|
||
if ($this->userService->isUserConnected($user->getUserIdentifier())) {
|
||
$this->userService->revokeUserTokens($user->getUserIdentifier());
|
||
}
|
||
$this->entityManager->persist($user);
|
||
$this->entityManager->flush();
|
||
$this->actionService->createAction("Deactivate user", $actingUser, null, $user->getUserIdentifier());
|
||
|
||
return $this->redirectToRoute('user_index');
|
||
}
|
||
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
}
|
||
|
||
//TODO : MONOLOG
|
||
#[Route('/activate/{id}', name: 'activate', methods: ['GET', 'POST'])]
|
||
public function activate(int $id): Response
|
||
{
|
||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser, true)) {
|
||
$user = $this->userRepository->find($id);
|
||
if (!$user) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$user->setIsActive(true);
|
||
$user->setModifiedAt(new \DateTimeImmutable('now'));
|
||
$this->entityManager->persist($user);
|
||
$this->entityManager->flush();
|
||
$this->actionService->createAction("Activate user", $actingUser, null, $user->getUserIdentifier());
|
||
|
||
return $this->redirectToRoute('user_index');
|
||
}
|
||
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
}
|
||
|
||
//TODO : MONOLOG
|
||
#[Route('/organization/deactivate/{id}', name: 'deactivate_organization', methods: ['GET', 'POST'])]
|
||
public function deactivateUserInOrganization(int $id, Request $request): Response
|
||
{
|
||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser, true)) {
|
||
$orgId = $request->get('organizationId');
|
||
$org = $this->organizationRepository->find($orgId);
|
||
if (!$org) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$user = $this->userRepository->find($id);
|
||
if (!$user) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$uo = $this->uoRepository->findOneBy(['users' => $user,
|
||
'organization' => $org,
|
||
'isActive' => true]);
|
||
if (!$uo) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$uo->setIsActive(false);
|
||
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo);
|
||
$data = ['user' => $user,
|
||
'organization' => $org];
|
||
$this->organizationsService->notifyOrganizationAdmins($data, "USER_DEACTIVATED");
|
||
$this->entityManager->persist($uo);
|
||
$this->entityManager->flush();
|
||
$this->actionService->createAction("Deactivate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier());
|
||
|
||
return new Response('', Response::HTTP_NO_CONTENT); //204
|
||
}
|
||
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
}
|
||
|
||
//TODO : MONOLOG
|
||
#[Route('/organization/activate/{id}', name: 'activate_organization', methods: ['GET', 'POST'])]
|
||
public function activateUserInOrganization(int $id, Request $request): Response
|
||
{
|
||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser, true)) {
|
||
$orgId = $request->get('organizationId');
|
||
$org = $this->organizationRepository->find($orgId);
|
||
if (!$org) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$user = $this->userRepository->find($id);
|
||
if (!$user) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$uo = $this->uoRepository->findOneBy(['users' => $user,
|
||
'organization' => $org,
|
||
'isActive' => false]);
|
||
if (!$uo) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$uo->setIsActive(true);
|
||
$this->entityManager->persist($uo);
|
||
$this->entityManager->flush();
|
||
$this->actionService->createAction("Activate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier());
|
||
$data = ['user' => $user,
|
||
'organization' => $org];
|
||
$this->organizationsService->notifyOrganizationAdmins($data, "USER_ACTIVATED");
|
||
|
||
return $this->redirectToRoute('user_index');
|
||
}
|
||
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
}
|
||
|
||
//TODO : MONOLOG + remove picture from bucket
|
||
#[Route('/delete/{id}', name: 'delete', methods: ['GET', 'POST'])]
|
||
public function delete(int $id, Request $request): Response
|
||
{
|
||
$this->denyAccessUnlessGranted("ROLE_SUPER_ADMIN");
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
$user = $this->userRepository->find($id);
|
||
if (!$user) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$user->setIsActive(false);
|
||
$user->setModifiedAt(new \DateTimeImmutable('now'));
|
||
$this->userOrganizationService->deactivateAllUserOrganizationLinks($user, $actingUser);
|
||
$user->setIsDeleted(true);
|
||
if ($this->userService->isUserConnected($user)) {
|
||
$this->userService->revokeUserTokens($user->getUserIdentifier());
|
||
}
|
||
$this->entityManager->persist($user);
|
||
$this->entityManager->flush();
|
||
$this->actionService->createAction("Delete user", $actingUser, null, $user->getUserIdentifier());
|
||
$data = ['user' => $user,
|
||
'organization' => null];
|
||
$this->organizationsService->notifyOrganizationAdmins($data, "USER_DELETED");
|
||
|
||
return new Response('', Response::HTTP_NO_CONTENT); //204
|
||
}
|
||
|
||
//TODO : MONOLOG
|
||
#[Route(path: '/application/roles/{id}', name: 'application_role', methods: ['GET', 'POST'])]
|
||
public function applicationRole(int $id, Request $request): Response
|
||
{
|
||
$this->denyAccessUnlessGranted("ROLE_ADMIN");
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
|
||
if ($this->userService->hasAccessTo($actingUser, true)) {
|
||
$uo = $this->userOrganizationService->getByIdOrFail($id);
|
||
$application = $this->entityManager->getRepository(Apps::class)->find($request->get('appId'));
|
||
if (!$application) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
|
||
$selectedRolesIds = $request->get('roles', []);
|
||
$roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']);
|
||
if (!$roleUser) {
|
||
throw $this->createNotFoundException('Default role not found');
|
||
}
|
||
|
||
if (!empty($selectedRolesIds)) {
|
||
if (!in_array((string)$roleUser->getId(), $selectedRolesIds, true)) {
|
||
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application);
|
||
} else {
|
||
$this->userOrganizationAppService->syncRolesForUserOrganizationApp(
|
||
$uo,
|
||
$application,
|
||
$selectedRolesIds,
|
||
$actingUser
|
||
);
|
||
}
|
||
|
||
} else {
|
||
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo, $application);
|
||
}
|
||
|
||
$user = $uo->getUsers();
|
||
return $this->redirectToRoute('user_show', [
|
||
'user' => $user,
|
||
'id' => $user->getId(),
|
||
'organizationId' => $uo->getOrganization()->getId()
|
||
]);
|
||
}
|
||
|
||
throw $this->createAccessDeniedException();
|
||
}
|
||
|
||
/*
|
||
* AJAX endpoint for user listing with pagination
|
||
* Get all the users that aren´t deleted and are active
|
||
*/
|
||
#[Route(path: '/data', name: 'data', methods: ['GET'])]
|
||
public function data(Request $request): JsonResponse
|
||
{
|
||
$this->denyAccessUnlessGranted("ROLE_ADMIN");
|
||
|
||
$page = max(1, (int)$request->query->get('page', 1));
|
||
$size = max(1, (int)$request->query->get('size', 10));
|
||
|
||
// Get filter parameters
|
||
$filters = $request->query->all('filter', []);
|
||
|
||
$repo = $this->userRepository;
|
||
|
||
// Base query
|
||
$qb = $repo->createQueryBuilder('u')
|
||
->where('u.isDeleted = :del')->setParameter('del', false);
|
||
|
||
// Apply filters
|
||
if (!empty($filters['name'])) {
|
||
$qb->andWhere('u.surname LIKE :name')
|
||
->setParameter('name', '%' . $filters['name'] . '%');
|
||
}
|
||
if (!empty($filters['prenom'])) {
|
||
$qb->andWhere('u.name LIKE :prenom')
|
||
->setParameter('prenom', '%' . $filters['prenom'] . '%');
|
||
}
|
||
if (!empty($filters['email'])) {
|
||
$qb->andWhere('u.email LIKE :email')
|
||
->setParameter('email', '%' . $filters['email'] . '%');
|
||
}
|
||
|
||
$countQb = clone $qb;
|
||
$total = (int)$countQb->select('COUNT(u.id)')->getQuery()->getSingleScalarResult();
|
||
|
||
// Pagination
|
||
$offset = ($page - 1) * $size;
|
||
$rows = $qb->setFirstResult($offset)->setMaxResults($size)->getQuery()->getResult();
|
||
|
||
// Map to array
|
||
$data = array_map(function (User $user) {
|
||
$picture = $this->awsService->getPublicUrl($_ENV['S3_PORTAL_BUCKET']) . $user->getPictureUrl();
|
||
return [
|
||
'id' => $user->getId(),
|
||
'pictureUrl' => $picture,
|
||
'name' => $user->getSurname(),
|
||
'prenom' => $user->getName(),
|
||
'email' => $user->getEmail(),
|
||
'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()),
|
||
'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]),
|
||
'statut' => $user->isActive(),
|
||
];
|
||
}, $rows);
|
||
|
||
$lastPage = (int)ceil($total / $size);
|
||
|
||
return $this->json([
|
||
'data' => $data,
|
||
'last_page' => $lastPage,
|
||
'total' => $total,
|
||
]);
|
||
}
|
||
|
||
#[Route(path: '/', name: 'index', methods: ['GET'])]
|
||
public function index(): Response
|
||
{
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser, true) && $this->isGranted("ROLE_ADMIN")) {
|
||
$totalUsers = $this->userRepository->count(['isDeleted' => false, 'isActive' => true]);
|
||
return $this->render('user/index.html.twig', [
|
||
'users' => $totalUsers
|
||
]);
|
||
}
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
}
|
||
|
||
/*
|
||
* AJAX endpoint for new users listing
|
||
* Get the 5 most recently created users for an organization
|
||
*/
|
||
#[Route(path: '/data/new', name: 'dataNew', methods: ['GET'])]
|
||
public function dataNew(Request $request): JsonResponse
|
||
{
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser, true) && $this->isGranted("ROLE_ADMIN")) {
|
||
$orgId = $request->query->get('orgId');
|
||
$uos = $this->uoRepository->findBy(['organization' => $orgId, 'statut' => ["ACCEPTED", "INVITED"]],
|
||
orderBy: ['createdAt' => 'DESC'], limit: 5);
|
||
|
||
|
||
// Map to array (keep isConnected)
|
||
$data = array_map(function (UsersOrganizations $uo) {
|
||
$user = $uo->getUsers();
|
||
$picture = $this->awsService->getPublicUrl($_ENV['S3_PORTAL_BUCKET']) . $user->getPictureUrl();
|
||
$initials = $user->getName()[0] . $user->getSurname()[0];
|
||
return [
|
||
'pictureUrl' => $picture,
|
||
'email' => $user->getEmail(),
|
||
'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()),
|
||
'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]),
|
||
'initials' => strtoupper($initials),
|
||
];
|
||
}, $uos);
|
||
return $this->json([
|
||
'data' => $data,
|
||
]);
|
||
}
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
|
||
|
||
}
|
||
|
||
/*
|
||
* AJAX endpoint for admin users listing
|
||
* Get all admin users for an organization
|
||
*/
|
||
|
||
#[Route(path: '/data/admin', name: 'dataAdmin', methods: ['GET'])]
|
||
public function dataAdmin(Request $request): JsonResponse
|
||
{
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser, true) && $this->isGranted("ROLE_ADMIN")) {
|
||
$orgId = $request->query->get('orgId');
|
||
$uos = $this->uoRepository->findBy(['organization' => $orgId]);
|
||
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']);
|
||
$users = [];
|
||
foreach ($uos as $uo) {
|
||
if ($this->entityManager->getRepository(UserOrganizatonApp::class)->findOneBy(['userOrganization' => $uo, 'role' => $roleAdmin])) {
|
||
$users[] = $uo;
|
||
}
|
||
}
|
||
|
||
|
||
// Map to array (keep isConnected)
|
||
$data = array_map(function (UsersOrganizations $uo) {
|
||
$user = $uo->getUsers();
|
||
$picture = $this->awsService->getPublicUrl($_ENV['S3_PORTAL_BUCKET']) . $user->getPictureUrl();
|
||
$initials = $user->getName()[0] . $user->getSurname()[0];
|
||
return [
|
||
'pictureUrl' => $picture,
|
||
'email' => $user->getEmail(),
|
||
'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()),
|
||
'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]),
|
||
'initials' => strtoupper($initials),
|
||
];
|
||
}, $users);
|
||
|
||
return $this->json([
|
||
'data' => $data,
|
||
]);
|
||
}
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
|
||
|
||
}
|
||
|
||
/*
|
||
* AJAX endpoint for All users in an organization
|
||
*/
|
||
#[Route(path: '/data/organization', name: 'dataUserOrganization', methods: ['GET'])]
|
||
public function dataUserOrganization(Request $request): JsonResponse
|
||
{
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
|
||
if ($this->userService->hasAccessTo($actingUser, true) && $this->isGranted("ROLE_ADMIN")) {
|
||
$orgId = $request->query->get('orgId');
|
||
$page = max(1, (int)$request->query->get('page', 1));
|
||
$size = max(1, (int)$request->query->get('size', 10));
|
||
|
||
$filters = $request->query->all('filter') ?? [];
|
||
|
||
$repo = $this->uoRepository;
|
||
|
||
// Base query
|
||
$qb = $repo->createQueryBuilder('uo')
|
||
->join('uo.users', 'u')
|
||
->where('uo.organization = :orgId')
|
||
->setParameter('orgId', $orgId);
|
||
|
||
// Apply filters
|
||
if (!empty($filters['name'])) {
|
||
$qb->andWhere('u.surname LIKE :name')
|
||
->setParameter('name', '%' . $filters['name'] . '%');
|
||
}
|
||
if (!empty($filters['prenom'])) {
|
||
$qb->andWhere('u.name LIKE :prenom')
|
||
->setParameter('prenom', '%' . $filters['prenom'] . '%');
|
||
}
|
||
if (!empty($filters['email'])) {
|
||
$qb->andWhere('u.email LIKE :email')
|
||
->setParameter('email', '%' . $filters['email'] . '%');
|
||
}
|
||
|
||
$countQb = clone $qb;
|
||
$total = (int)$countQb->select('COUNT(uo.id)')->getQuery()->getSingleScalarResult();
|
||
|
||
$qb->orderBy('uo.isActive', 'DESC')
|
||
->addOrderBy('CASE WHEN uo.statut = :invited THEN 0 ELSE 1 END', 'ASC')
|
||
->setParameter('invited', 'INVITED');
|
||
|
||
$offset = ($page - 1) * $size;
|
||
$rows = $qb->setFirstResult($offset)->setMaxResults($size)->getQuery()->getResult();
|
||
$data = $this->userService->formatStatutForOrganizations($rows);
|
||
|
||
$lastPage = (int)ceil($total / $size);
|
||
|
||
return $this->json([
|
||
'data' => $data,
|
||
'last_page' => $lastPage,
|
||
'total' => $total,
|
||
]);
|
||
}
|
||
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
|
||
}
|
||
|
||
#[Route(path: '/organization/resend-invitation/{userId}', name: 'resend_invitation', methods: ['POST'])]
|
||
public function resendInvitation(int $userId, Request $request): JsonResponse
|
||
{
|
||
$this->denyAccessUnlessGranted("ROLE_ADMIN");
|
||
$actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
|
||
if ($this->userService->hasAccessTo($actingUser, true)) {
|
||
$orgId = $request->get('organizationId');
|
||
$org = $this->organizationRepository->find($orgId);
|
||
if (!$org) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$user = $this->userRepository->find($userId);
|
||
if (!$user) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$uo = $this->uoRepository->findOneBy(['users' => $user,
|
||
'organization' => $org,
|
||
'statut' => "INVITED"]);
|
||
if (!$uo) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
$uo->setModifiedAt(new \DateTimeImmutable());
|
||
try {
|
||
$data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
|
||
$this->emailService->sendPasswordSetupEmail($user, $orgId);
|
||
$this->logger->info("Invitation email resent to user " . $user->getUserIdentifier() . " for organization " . $org->getName());
|
||
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
|
||
return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK);
|
||
} catch (\Exception $e) {
|
||
$this->logger->error("Error resending invitation email to user " . $user->getUserIdentifier() . " for organization " . $org->getName() . ": " . $e->getMessage());
|
||
return $this->json(['message' => 'Erreur lors de l\'envoie du mail.'], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||
}
|
||
|
||
#[Route(path: '/accept-invitation', name: 'accept', methods: ['GET'])]
|
||
public function acceptInvitation(Request $request): Response
|
||
{
|
||
$token = $request->get('token');
|
||
$userId = $request->get('id');
|
||
|
||
if (!$token || !$userId) {
|
||
throw $this->createNotFoundException('Invalid invitation link.');
|
||
}
|
||
$user = $this->userRepository->find($userId);
|
||
if (!$user) {
|
||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||
}
|
||
if (!$this->userService->isPasswordTokenValid($user, $token)) {
|
||
throw $this->createNotFoundException('Invalid or expired invitation token.');
|
||
}
|
||
$orgId = $this->userService->getOrgFromToken($token);
|
||
$uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $orgId]);
|
||
if (!$uo || $uo->getStatut() !== 'INVITED') {
|
||
$this->logger->warning("User " . $user->getUserIdentifier() . " tried to accept an invitation but no pending invitation was found for organization ID " . $orgId);
|
||
throw $this->createNotFoundException('No pending invitation found for this user and organization.');
|
||
}
|
||
$uo->setModifiedAt(new \DateTimeImmutable());
|
||
$uo->setStatut("ACCEPTED");
|
||
$uo->setIsActive(true);
|
||
$this->entityManager->persist($uo);
|
||
$this->entityManager->flush();
|
||
$this->logger->info("User " . $user->getUserIdentifier() . " accepted invitation for organization ID " . $orgId);
|
||
|
||
return $this->render('user/show.html.twig', ['user' => $user, 'orgId' => $orgId]);
|
||
}
|
||
}
|
||
|