denyAccessUnlessGranted('ROLE_ADMIN'); $totalUsers = $this->userRepository->count(['isDeleted' => false, 'isActive' => true]); return $this->render('user/index.html.twig', [ 'users' => $totalUsers ]); } #[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()); // Chargement de l'utilisateur cible à afficher $user = $this->userRepository->find($id); if (!$user) { $this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId()); $this->addFlash('danger', "L'utilisateur demandé n'existe pas."); throw $this->createNotFoundException(self::NOT_FOUND); } //if hasAccessTo is false, turn to true and denie access if (!$this->userService->hasAccessTo($user)) { $this->loggerService->logAccessDenied($actingUser->getId()); $this->addFlash('danger', "Vous n'avez pas accès à cette information."); throw new AccessDeniedHttpException (self::ACCESS_DENIED); } try { // Paramètre optionnel de contexte organisationnel $orgId = $request->query->get('organizationId'); if ($orgId) { // TODO: afficher les projets de l'organisation } else { // Afficher tous les projets de l'utilisateur } } catch (\Exception $e) { $this->loggerService->logError('error while loading user information', [ 'target_user_id' => $id, 'acting_user_id' => $actingUser->getId(), 'error' => $e->getMessage(), ]); $this->addFlash('danger', 'Une erreur est survenue lors du chargement des informations utilisateur.'); $referer = $request->headers->get('referer'); return $this->redirect($referer ?? $this->generateUrl('app_index')); } return $this->render('user/show.html.twig', [ 'user' => $user, 'organizationId' => $orgId ?? null, ]); } #[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()); $user = $this->userRepository->find($id); if (!$user) { $this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId()); $this->addFlash('danger', "L'utilisateur demandé n'existe pas."); throw $this->createNotFoundException(self::NOT_FOUND); } try { if ($this->userService->hasAccessTo($user)) { $form = $this->createForm(UserForm::class, $user); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // Handle user edit $picture = $form->get('pictureUrl')->getData();; $this->userService->formatUserData($user, $picture); $user->setModifiedAt(new \DateTimeImmutable('now')); $this->entityManager->persist($user); $this->entityManager->flush(); //log and action $this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User information edited'); $orgId = $request->get('organizationId'); if ($orgId) { $org = $this->organizationRepository->find($orgId); if ($org) { $this->actionService->createAction("Edit user information", $actingUser, $org, $user->getUserIdentifier()); $this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User information edited'); if ($this->isGranted('ROLE_SUPER_ADMIN')) { $this->loggerService->logSuperAdmin( $user->getId(), $actingUser->getId(), "Super Admin accessed user edit page", ); } $this->addFlash('success', 'Information modifié avec success.'); return $this->redirectToRoute('user_show', ['id' => $user->getId(), 'organizationId' => $orgId]); } $this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getId()); $this->addFlash('danger', "L'organisation n'existe pas."); throw $this->createNotFoundException(self::NOT_FOUND); } if ($this->isGranted('ROLE_SUPER_ADMIN')) { $this->loggerService->logSuperAdmin( $user->getId(), $actingUser->getId(), "Super Admin accessed user edit page", ); } $this->addFlash('success', 'Information modifié avec success.'); $this->actionService->createAction("Edit user information", $actingUser, null, $user->getUserIdentifier()); return $this->redirectToRoute('user_show', ['id' => $user->getId()]); } return $this->render('user/edit.html.twig', [ 'user' => $user, 'form' => $form->createView(), 'organizationId' => $request->get('organizationId') ]); } $this->loggerService->logAccessDenied($actingUser->getId()); $this->addFlash('danger', "Accès non autorisé."); throw $this->createAccessDeniedException(self::ACCESS_DENIED); } catch (\Exception $e) { $this->addFlash('danger', 'Une erreur est survenue lors de la modification des informations utilisateur.'); $this->errorLogger->critical($e->getMessage()); } // Default deny access. shouldn't reach here normally. throw $this->createAccessDeniedException(self::ACCESS_DENIED); } #[Route('/new', name: 'new', methods: ['GET', 'POST'])] public function new(Request $request): Response { $this->denyAccessUnlessGranted('ROLE_USER'); try { $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $user = new User(); $form = $this->createForm(UserForm::class, $user); $form->handleRequest($request); $orgId = $request->query->get('organizationId') ?? $request->request->get('organizationId'); if ($orgId) { $org = $this->organizationRepository->find($orgId); if (!$org) { $this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getId()); $this->addFlash('danger', "L'organisation n'existe pas."); throw $this->createNotFoundException(self::NOT_FOUND); } if (!$this->isGranted('ROLE_ADMIN') && !$this->userService->isAdminOfOrganization($org)) { $this->loggerService->logAccessDenied($actingUser->getId()); $this->addFlash('danger', "Accès non autorisé."); throw $this->createAccessDeniedException(self::ACCESS_DENIED); } } else{ $this->loggerService->logAccessDenied($actingUser->getId()); $this->addFlash('danger', "Accès non autorisé."); throw $this->createAccessDeniedException(self::ACCESS_DENIED); } if ($form->isSubmitted() && $form->isValid()) { $existingUser = $this->userRepository->findOneBy(['email' => $user->getEmail()]); // Case : User exists -> link him to given organization if not already linked, else error message if ($existingUser && $org) { $this->userService->addExistingUserToOrganization( $existingUser, $org, ); if ($this->isGranted('ROLE_ADMIN')) { $this->loggerService->logSuperAdmin( $existingUser->getId(), $actingUser->getId(), "Super Admin linked user to organization", $org->getId(), ); } $this->addFlash('success', 'Utilisateur ajouté avec succès à l\'organisation. '); return $this->redirectToRoute('organization_show', ['id' => $orgId]); } // Case : user doesn't already exist $picture = $form->get('pictureUrl')->getData(); $this->userService->createNewUser($user, $actingUser, $picture); $this->userService->linkUserToOrganization( $user, $org, ); $this->addFlash('success', 'Nouvel utilisateur créé et ajouté à l\'organisation avec succès. '); return $this->redirectToRoute('organization_show', ['id' => $orgId]); } return $this->render('user/new.html.twig', [ 'user' => $user, 'form' => $form->createView(), 'organizationId' => $orgId, ]); } catch (\Exception $e) { $this->errorLogger->critical($e->getMessage()); if ($orgId) { $this->addFlash('danger', 'Une erreur est survenue lors de la création de l\'utilisateur pour l\'organisation .'); return $this->redirectToRoute('organization_show', ['id' => $orgId]); } $this->addFlash('danger', 'Une erreur est survenue lors de la création de l\'utilisateur.'); return $this->redirectToRoute('user_index'); } } /** * Endpoint to activate/deactivate a user (soft delete) * If deactivating, also deactivate all org links and revoke tokens */ #[Route('/activeStatus/{id}', name: 'active_status', methods: ['GET', 'POST'])] public function activeStatus(int $id, Request $request): JsonResponse { $this->denyAccessUnlessGranted('ROLE_ADMIN'); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); $status = $request->get('status'); try { // Access control if (!$this->userService->hasAccessTo($actingUser, true)) { $this->loggerService->logAccessDenied($actingUser->getId()); throw $this->createAccessDeniedException(self::ACCESS_DENIED); } // Load target user $user = $this->userRepository->find($id); if (!$user) { $this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId()); throw $this->createNotFoundException(self::NOT_FOUND); } // Deactivate if ($status === 'deactivate') { $user->setIsActive(false); $this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user); if ($this->userService->isUserConnected($user->getUserIdentifier())) { $this->accessTokenService->revokeUserTokens($user->getUserIdentifier()); } $user->setModifiedAt(new \DateTimeImmutable('now')); $this->entityManager->persist($user); $this->entityManager->flush(); $this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User deactivated'); if ($this->isGranted('ROLE_SUPER_ADMIN')) { $this->loggerService->logSuperAdmin( $user->getId(), $actingUser->getId(), 'Super admin deactivated user' ); } $this->actionService->createAction('Deactivate user', $actingUser, null, $user->getUserIdentifier()); return new JsonResponse(['status' => 'deactivated'], Response::HTTP_OK); } // Activate if ($status === 'activate') { $user->setIsActive(true); $user->setModifiedAt(new \DateTimeImmutable('now')); $this->entityManager->persist($user); $this->entityManager->flush(); $this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User activated'); if ($this->isGranted('ROLE_SUPER_ADMIN')) { $this->loggerService->logSuperAdmin( $user->getId(), $actingUser->getId(), 'Super admin activated user' ); } $this->actionService->createAction('Activate user', $actingUser, null, $user->getUserIdentifier()); return new JsonResponse(['status' => 'activated'], Response::HTTP_OK); } // Invalid status $this->loggerService->logError('Invalid status provided for activeStatus', [ 'requested_status' => $status, 'target_user_id' => $id, ]); return new JsonResponse(['error' => 'Status invalide'], Response::HTTP_BAD_REQUEST); } catch (\Throwable $e) { // Application-level error logging → error.log (via error channel) $this->errorLogger->critical($e->getMessage()); // Preserve 403/404 semantics, 500 for everything else if ($e instanceof NotFoundHttpException || $e instanceof AccessDeniedException) { throw $e; } return new JsonResponse(['error' => 'Une erreur est survenue'], Response::HTTP_INTERNAL_SERVER_ERROR); } } #[Route('/organization/activateStatus/{id}', name: 'activate_organization', methods: ['GET', 'POST'])] public function activateStatusOrganization(int $id, Request $request): JsonResponse { $this->denyAccessUnlessGranted('ROLE_ADMIN'); $actingUser = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier()); try { if ($this->userService->hasAccessTo($actingUser, true)) { $orgId = $request->get('organizationId'); $org = $this->organizationRepository->find($orgId); if (!$org) { $this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getId()); throw $this->createNotFoundException(self::NOT_FOUND); } $user = $this->userRepository->find($id); if (!$user) { $this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId()); throw $this->createNotFoundException(self::NOT_FOUND); } $uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $org]); if (!$uo) { $this->loggerService->logEntityNotFound('UsersOrganization', ['user_id' => $user->getId(), 'organization_id' => $org->getId()], $actingUser->getId()); throw $this->createNotFoundException(self::NOT_FOUND); } $status = $request->get('status'); if ($status === 'deactivate') { $uo->setIsActive(false); $this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo); $this->entityManager->persist($uo); $this->entityManager->flush(); $data = ['user' => $user, 'organization' => $org]; $this->organizationsService->notifyOrganizationAdmins($data, "USER_DEACTIVATED"); $this->loggerService->logOrganizationInformation($org->getId(), $actingUser->getId(), "UO link deactivated with uo id : {$uo->getId()}"); $this->actionService->createAction("Deactivate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier()); return new JsonResponse(['status' => 'deactivated'], Response::HTTP_OK); } if ($status === "activate") { $uo->setIsActive(true); $this->entityManager->persist($uo); $this->entityManager->flush(); $this->loggerService->logOrganizationInformation($orgId, $actingUser->getId(), "UO link activated with uo id : {$uo->getId()}"); $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 new JsonResponse(['status' => 'activated'], Response::HTTP_OK); } //invalid status $this->loggerService->logError('Invalid status provided for activateStatusOrganization', [ 'requested_status' => $status, 'target_user_id' => $id, 'organization_id' => $orgId, ]); throw $this->createNotFoundException(self::NOT_FOUND); } } catch (\Exception $exception) { $this->loggerService->logCritical($exception->getMessage()); } throw $this->createNotFoundException(self::NOT_FOUND); } #[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()); try { $user = $this->userRepository->find($id); if (!$user) { // Security/audit log for missing user $this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getId()); $this->addFlash('danger', "L'utilisateur demandé n'existe pas."); throw $this->createNotFoundException(self::NOT_FOUND); } // Soft delete the user $user->setIsActive(false); $user->setIsDeleted(true); $this->userService->deleteProfilePicture($user); $user->setModifiedAt(new \DateTimeImmutable('now')); // Deactivate all org links $this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user); $this->loggerService->logOrganizationInformation($user->getId(), $actingUser->getId(), 'All user organization links deactivated'); // Revoke tokens if connected if ($this->userService->isUserConnected($user->getUserIdentifier())) { $this->accessTokenService->revokeUserTokens($user->getUserIdentifier()); } $this->entityManager->flush(); // User management log $this->loggerService->logUserAction($user->getId(), $actingUser->getId(), 'User deleted'); // Super admin log (standardized style) if ($this->isGranted('ROLE_SUPER_ADMIN')) { $this->loggerService->logSuperAdmin( $user->getId(), $actingUser->getId(), 'Super admin deleted user' ); } $this->actionService->createAction('Delete user', $actingUser, null, $user->getUserIdentifier()); // Notify organization admins (user may belong to multiple organizations) try { $data = [ 'user' => $user, 'organization' => null, ]; $this->organizationsService->notifyOrganizationAdmins($data, 'USER_DELETED'); } catch (\Throwable $e) { $this->loggerService->logCritical($e->getMessage(), [ 'target_user_id' => $id, 'acting_user_id' => $actingUser?->getId(), ]); } $this->addFlash('success', 'Utilisateur supprimé avec succès.'); return $this->redirectToRoute('user_index'); } catch (\Exception $e) { // Route-level error logging → error.log $this->loggerService->logCritical('error while deleting user', [ 'target_user_id' => $id, 'acting_user_id' => $actingUser?->getId(), 'error' => $e->getMessage(), ]); if ($e instanceof NotFoundHttpException) { throw $e; // keep 404 semantics } $this->addFlash('danger', 'Erreur lors de la suppression de l\'utilisateur\.'); return $this->redirectToRoute('user_index'); } } #[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->entityManager->getRepository(UsersOrganizations::class)->find($id); if (!$uo) { $this->loggerService->logEntityNotFound('UsersOrganization', ['id' => $id], $actingUser->getId()); $this->addFlash('danger', "La liaison utilisateur-organisation n'existe pas."); throw new NotFoundHttpException("UserOrganization not found"); } $application = $this->entityManager->getRepository(Apps::class)->find($request->get('appId')); if (!$application) { $this->loggerService->logEntityNotFound('Application', ['id' => $request->get('appId')], $actingUser->getId()); $this->addFlash('danger', "L'application demandée n'existe pas."); throw $this->createNotFoundException(self::NOT_FOUND); } $selectedRolesIds = $request->get('roles', []); $roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']); if (!$roleUser) { $this->loggerService->logEntityNotFound('Role', ['name' => 'USER'], $actingUser->getId()); $this->addFlash('danger', "Le role de l'utilisateur n'existe pas."); throw $this->createNotFoundException('User role not found'); } if (!empty($selectedRolesIds)) { // Si le role User n'est pas sélectionné, on désactive tous les liens (affiché comme 'accès' dans l'UI) 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(); $this->addFlash('success', 'Rôles mis à jour avec succès.'); 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) { return [ 'id' => $user->getId(), 'pictureUrl' => $user->getPictureUrl(), '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, ]); } /* * 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(); $initials = $user->getName()[0] . $user->getSurname()[0]; return [ 'pictureUrl' => $user->getPictureUrl(), '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(); $initials = $user->getName()[0] . $user->getSurname()[0]; return [ 'pictureUrl' => $user->getPictureUrl(), '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) { $this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getId()); throw $this->createNotFoundException(self::NOT_FOUND); } $user = $this->userRepository->find($userId); if (!$user) { $this->loggerService->logEntityNotFound('User', ['id' => $user->getId()], $actingUser->getId()); throw $this->createNotFoundException(self::NOT_FOUND); } $token = $this->userService->generatePasswordToken($user, $org->getId()); if ($user->getLastConnection() !== null) { $this->userService->sendExistingUserNotifications($user, $org, $actingUser); } else { $uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $org, 'statut' => "INVITED"]); if (!$uo) { $this->loggerService->logEntityNotFound('UsersOrganization', [ 'user_id' => $user->getId(), 'organization_id' => $orgId], $actingUser->getId()); throw $this->createNotFoundException(self::NOT_FOUND); } $uo->setModifiedAt(new \DateTimeImmutable()); try { $data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()]; $this->emailService->sendPasswordSetupEmail($user, $token); $this->loggerService->logEmailSent($userId, $org->getId(), 'Invitation Resent'); $this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED'); return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK); } catch (\Exception $e) { $this->loggerService->logCritical('Error while resending invitation', [ 'target_user_id' => $user->getId(), 'organization_id' => $orgId, 'acting_user_id' => $actingUser->getId(), 'error' => $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) { $this->loggerService->logEntityNotFound('Token or UserId missing in accept invitation', [ 'token' => $token, 'user_id' => $userId ], null); throw $this->createNotFoundException('Invalid invitation link.'); } $user = $this->userRepository->find($userId); if (!$user) { $this->loggerService->logEntityNotFound('User not found in accept invitation', [ 'user_id' => $userId ], null); throw $this->createNotFoundException(self::NOT_FOUND); } if (!$this->userService->isPasswordTokenValid($user, $token)) { $this->loggerService->logError('Token or UserId mismatch in accept invitation', [ 'token' => $token, 'user_id' => $userId ]); throw $this->createNotFoundException('Invalid or expired invitation token.'); } $orgId = $this->userService->getOrgFromToken($token); if ($orgId) { $uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $orgId]); if (!$uo || $uo->getStatut() !== 'INVITED') { $this->loggerService->logEntityNotFound('UsersOrganization not found or not in INVITED status in accept invitation', [ 'user_id' => $user->getId(), 'organization_id' => $orgId ], null); 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->loggerService->logUserAction($user->getId(), $user->getId(), "User accepted invitation for organization id : {$orgId}"); $this->loggerService->logOrganizationInformation($orgId, $user->getId(), "User accepted invitation with uo id : {$uo->getId()}"); } return $this->render('security/login.html.twig', ['error'=> null, 'last_username' => $user->getEmail()]); } }