diff --git a/README.MD b/README.MD
index 642dff3..b0d5014 100644
--- a/README.MD
+++ b/README.MD
@@ -10,7 +10,7 @@
- Les icones sont gérées via symfony UX (https://ux.symfony.com/icons)
- Les icones sont prises en prioritées dans la bibliothèque bootstrap
- Les icones n'éxistants pas dans cette bibliothèques seront prises en priorité dans fontawesome regular (pour une cohérence visuelle)
- - Sinon privilégier la bibliothèque ayant le visuel le plus proche
+ - Sinon privilégier la bibliothèque ayant le visuel le plus proche
### Version 0.1 : (17/03/2025)
- Contient la logique de login mot de passe avec une entité user (email et password seuelement)
@@ -19,5 +19,12 @@
- Une base de template est gérée pour toutes les pages de l'application aya,t besoin de l'entête et du menu général
- Une ébauche de page d'accueil est en cours
+### Installation
+#### Choices.js
+```bash
+ php bin/console importmap:require choices.js
+ php bin/console importmap:require choices.js/public/assets/styles/choices.min.css
+```
+
diff --git a/assets/app.js b/assets/app.js
index 3f18426..3c3859d 100644
--- a/assets/app.js
+++ b/assets/app.js
@@ -15,4 +15,5 @@ import './js/template.js';
import './js/off_canvas.js';
import './js/hoverable-collapse.js';
import './js/cookies.js';
-
+import 'choices.js';
+import 'choices.js/public/assets/styles/choices.min.css';
diff --git a/assets/controllers.json b/assets/controllers.json
index 3ed9c65..480ad64 100644
--- a/assets/controllers.json
+++ b/assets/controllers.json
@@ -11,7 +11,7 @@
},
"@symfony/ux-turbo": {
"turbo-core": {
- "enabled": false,
+ "enabled": true,
"fetch": "eager"
},
"mercure-turbo-stream": {
diff --git a/assets/controllers/user_controller.js b/assets/controllers/user_controller.js
new file mode 100644
index 0000000..fd6eeca
--- /dev/null
+++ b/assets/controllers/user_controller.js
@@ -0,0 +1,99 @@
+import {Controller} from '@hotwired/stimulus';
+import Choices from 'choices.js';
+/*
+* The following line makes this controller "lazy": it won't be downloaded until needed
+* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
+*/
+
+/* stimulusFetch: 'lazy' */
+export default class extends Controller {
+ static values = {
+ rolesArray: Array,
+ selectedRoleIds: Array,
+ applicationsArray: Array,
+ selectedApplicationIds: Array
+ }
+ // {value: 'choice1', label: 'Choice 1'},
+ // {value: 'choice2', label: 'Choice 2'},
+ // {value: 'choice3', label: 'Choice 3'},
+
+ roleSelect() {
+ const element = document.getElementById('roles');
+ if (element) {
+ const choicesData = this.rolesArrayValue.map(role => ({
+ value: role.id,
+ label: role.name,
+ selected: this.selectedRoleIdsValue.includes(role.id)
+ }));
+
+ const choices = new Choices(element, {
+ choices: choicesData,
+ removeItemButton: true,
+ placeholder: true,
+ placeholderValue: 'Ajouter un ou plusieurs rôles'
+ });
+ }
+ }
+
+ appSelect() {
+ const element = document.getElementById('applications');
+ if (element) {
+
+ const choicesData = this.applicationsArrayValue.map(app => ({
+ value: app.id,
+ label: app.name,
+ customProperties: { icon: app.icon },
+ selected: this.selectedApplicationIdsValue.includes(app.id)
+ }));
+
+ const choices = new Choices(element, {
+ choices: choicesData,
+ removeItemButton: true,
+ placeholder: true,
+ placeholderValue: 'Ajouter une ou plusieurs applications',
+ // callbackOnCreateTemplates: function(template) {
+ // return {
+ // // Custom rendering for dropdown choices
+ // choice: (classNames, data) => {
+ // return template(`
+ //
0 ? 'role="treeitem"' : 'role="option"'} >
+ // ${data.customProperties && data.customProperties.icon ? `

` : ''}
+ // ${data.label}
+ //
+ // `);
+ // },
+ // // Custom rendering for selected items
+ // item: (classNames, data) => {
+ // return template(`
+ //
+ // ${data.customProperties && data.customProperties.icon ? `

` : ''}
+ // ${data.label}
+ //
+ // `);
+ // }
+ })
+ }
+ };
+ // }
+ // }
+
+ connect() {
+ this.roleSelect();
+ this.appSelect();
+
+ // Set choices after initialization
+ // choices.setValue(choicesData);
+ }
+
+
+// Add custom controller actions here
+// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
+
+ disconnect() {
+ // Called anytime its element is disconnected from the DOM
+ // (on page change, when it's removed from or moved in the DOM, etc.)
+
+ // Here you should remove all event listeners added in "connect()"
+ // this.fooTarget.removeEventListener('click', this._fooBar)
+ }
+}
diff --git a/composer.lock b/composer.lock
index 68ba93a..014e148 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2423,16 +2423,16 @@
},
{
"name": "phpstan/phpdoc-parser",
- "version": "2.1.0",
+ "version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68"
+ "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
- "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8",
+ "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8",
"shasum": ""
},
"require": {
@@ -2464,9 +2464,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0"
},
- "time": "2025-02-19T13:28:12+00:00"
+ "time": "2025-07-13T07:04:09+00:00"
},
{
"name": "psr/cache",
diff --git a/importmap.php b/importmap.php
index a5326d8..5111ef4 100644
--- a/importmap.php
+++ b/importmap.php
@@ -35,4 +35,11 @@ return [
'version' => '5.3.5',
'type' => 'css',
],
+ 'choices.js' => [
+ 'version' => '11.1.0',
+ ],
+ 'choices.js/public/assets/styles/choices.min.css' => [
+ 'version' => '11.1.0',
+ 'type' => 'css',
+ ],
];
diff --git a/migrations/Version20250724133531.php b/migrations/Version20250724133531.php
new file mode 100644
index 0000000..4532054
--- /dev/null
+++ b/migrations/Version20250724133531.php
@@ -0,0 +1,52 @@
+addSql('CREATE TABLE user_organization_app (id SERIAL NOT NULL, applications_id INT DEFAULT NULL, organization_id INT DEFAULT NULL, users_id INT DEFAULT NULL, PRIMARY KEY(id))');
+ $this->addSql('CREATE INDEX IDX_BEF66DF129A0022 ON user_organization_app (applications_id)');
+ $this->addSql('CREATE INDEX IDX_BEF66DF132C8A3DE ON user_organization_app (organization_id)');
+ $this->addSql('CREATE INDEX IDX_BEF66DF167B3B43D ON user_organization_app (users_id)');
+ $this->addSql('CREATE TABLE user_organization_roles (id SERIAL NOT NULL, role_id INT DEFAULT NULL, users_id INT DEFAULT NULL, organization_id INT DEFAULT NULL, PRIMARY KEY(id))');
+ $this->addSql('CREATE INDEX IDX_94FD2EFBD60322AC ON user_organization_roles (role_id)');
+ $this->addSql('CREATE INDEX IDX_94FD2EFB67B3B43D ON user_organization_roles (users_id)');
+ $this->addSql('CREATE INDEX IDX_94FD2EFB32C8A3DE ON user_organization_roles (organization_id)');
+ $this->addSql('ALTER TABLE user_organization_app ADD CONSTRAINT FK_BEF66DF129A0022 FOREIGN KEY (applications_id) REFERENCES apps (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_app ADD CONSTRAINT FK_BEF66DF132C8A3DE FOREIGN KEY (organization_id) REFERENCES organizations (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_app ADD CONSTRAINT FK_BEF66DF167B3B43D FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_roles ADD CONSTRAINT FK_94FD2EFBD60322AC FOREIGN KEY (role_id) REFERENCES roles (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_roles ADD CONSTRAINT FK_94FD2EFB67B3B43D FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_roles ADD CONSTRAINT FK_94FD2EFB32C8A3DE FOREIGN KEY (organization_id) REFERENCES organizations (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql('CREATE SCHEMA public');
+ $this->addSql('ALTER TABLE user_organization_app DROP CONSTRAINT FK_BEF66DF129A0022');
+ $this->addSql('ALTER TABLE user_organization_app DROP CONSTRAINT FK_BEF66DF132C8A3DE');
+ $this->addSql('ALTER TABLE user_organization_app DROP CONSTRAINT FK_BEF66DF167B3B43D');
+ $this->addSql('ALTER TABLE user_organization_roles DROP CONSTRAINT FK_94FD2EFBD60322AC');
+ $this->addSql('ALTER TABLE user_organization_roles DROP CONSTRAINT FK_94FD2EFB67B3B43D');
+ $this->addSql('ALTER TABLE user_organization_roles DROP CONSTRAINT FK_94FD2EFB32C8A3DE');
+ $this->addSql('DROP TABLE user_organization_app');
+ $this->addSql('DROP TABLE user_organization_roles');
+ }
+}
diff --git a/migrations/Version20250725065027.php b/migrations/Version20250725065027.php
new file mode 100644
index 0000000..6629ba3
--- /dev/null
+++ b/migrations/Version20250725065027.php
@@ -0,0 +1,56 @@
+addSql('DROP SEQUENCE user_organization_roles_id_seq CASCADE');
+ $this->addSql('DROP SEQUENCE user_organization_app_id_seq CASCADE');
+ $this->addSql('ALTER TABLE user_organization_app DROP CONSTRAINT fk_bef66df129a0022');
+ $this->addSql('ALTER TABLE user_organization_app DROP CONSTRAINT fk_bef66df132c8a3de');
+ $this->addSql('ALTER TABLE user_organization_app DROP CONSTRAINT fk_bef66df167b3b43d');
+ $this->addSql('ALTER TABLE user_organization_roles DROP CONSTRAINT fk_94fd2efb32c8a3de');
+ $this->addSql('ALTER TABLE user_organization_roles DROP CONSTRAINT fk_94fd2efb67b3b43d');
+ $this->addSql('ALTER TABLE user_organization_roles DROP CONSTRAINT fk_94fd2efbd60322ac');
+ $this->addSql('DROP TABLE user_organization_app');
+ $this->addSql('DROP TABLE user_organization_roles');
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql('CREATE SCHEMA public');
+ $this->addSql('CREATE SEQUENCE user_organization_roles_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
+ $this->addSql('CREATE SEQUENCE user_organization_app_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
+ $this->addSql('CREATE TABLE user_organization_app (id SERIAL NOT NULL, applications_id INT DEFAULT NULL, organization_id INT DEFAULT NULL, users_id INT DEFAULT NULL, PRIMARY KEY(id))');
+ $this->addSql('CREATE INDEX idx_bef66df129a0022 ON user_organization_app (applications_id)');
+ $this->addSql('CREATE INDEX idx_bef66df132c8a3de ON user_organization_app (organization_id)');
+ $this->addSql('CREATE INDEX idx_bef66df167b3b43d ON user_organization_app (users_id)');
+ $this->addSql('CREATE TABLE user_organization_roles (id SERIAL NOT NULL, role_id INT DEFAULT NULL, users_id INT DEFAULT NULL, organization_id INT DEFAULT NULL, PRIMARY KEY(id))');
+ $this->addSql('CREATE INDEX idx_94fd2efb32c8a3de ON user_organization_roles (organization_id)');
+ $this->addSql('CREATE INDEX idx_94fd2efb67b3b43d ON user_organization_roles (users_id)');
+ $this->addSql('CREATE INDEX idx_94fd2efbd60322ac ON user_organization_roles (role_id)');
+ $this->addSql('ALTER TABLE user_organization_app ADD CONSTRAINT fk_bef66df129a0022 FOREIGN KEY (applications_id) REFERENCES apps (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_app ADD CONSTRAINT fk_bef66df132c8a3de FOREIGN KEY (organization_id) REFERENCES organizations (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_app ADD CONSTRAINT fk_bef66df167b3b43d FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_roles ADD CONSTRAINT fk_94fd2efb32c8a3de FOREIGN KEY (organization_id) REFERENCES organizations (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_roles ADD CONSTRAINT fk_94fd2efb67b3b43d FOREIGN KEY (users_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE user_organization_roles ADD CONSTRAINT fk_94fd2efbd60322ac FOREIGN KEY (role_id) REFERENCES roles (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ }
+}
diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
index ec892bb..e2ccf64 100644
--- a/src/Controller/UserController.php
+++ b/src/Controller/UserController.php
@@ -2,12 +2,16 @@
namespace App\Controller;
+use App\Entity\Apps;
+use App\Entity\Roles;
use App\Entity\User;
use App\Form\UserForm;
+use App\Entity\UsersOrganizations;
use App\Service\UserOrganizationService;
use App\Service\UserService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
@@ -15,11 +19,12 @@ use Symfony\Component\Routing\Attribute\Route;
#[Route(path: '/user', name: 'user_')]
class UserController extends AbstractController
{
- private const NOT_FOUND = 'User not found';
+ private const NOT_FOUND = 'Entity not found';
+
public function __construct(
private readonly UserOrganizationService $userOrganizationService,
- private readonly EntityManagerInterface $entityManager,
- private readonly UserService $userService)
+ private readonly EntityManagerInterface $entityManager,
+ private readonly UserService $userService)
{
}
@@ -199,23 +204,84 @@ class UserController extends AbstractController
return new Response('Unauthorized', Response::HTTP_UNAUTHORIZED);
}
- #Route('/organizationsUserEdit/{id}', name: 'organization_user_edit', requirements: ['id' => '\d+'], methods: ['POST'])]
- public function organizationUserEdit(int $id, Request $request, EntityManagerInterface $entityManager): Response
+ /**
+ * Update organization user /userOrganizationEdit/{id} - Update organization user
+ * The id parameter is the ID of the UsersOrganizations entity.
+ */
+ #[Route('/userOrganizationEdit/{id}', name: 'organization_edit', requirements: ['id' => '\d+'], methods: ['GET', 'POST'])]
+ public function userOrganizationEdit(int $id, Request $request, EntityManagerInterface $entityManager, Packages $packages): Response
{
- if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
- throw $this->createAccessDeniedException('Access denied');
+ $this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN');
+
+// get the UsersOrganizations entity by ID and handle not found case same for user
+ $userOrganization = $entityManager->getRepository(UsersOrganizations::class)->find($id);
+ if (!$userOrganization) {
+ throw $this->createNotFoundException(self::NOT_FOUND);
+ }
+ $user = $userOrganization->getUsers() ?? throw $this->createNotFoundException(self::NOT_FOUND);
+ $organization = $userOrganization->getOrganization() ?? throw $this->createNotFoundException(self::NOT_FOUND);
+
+ //Handle the POST
+ if ($request->isMethod('POST')) {
+ // Get the selected roles and apps from the request
+ $selectedRoles = $request->request->all('roles');
+ $selectedApps = $request->request->all('applications');
+
+// order in important here. apps MUST be before roles
+ $this->userOrganizationService->setUserOrganizationsApps($user, $organization,$selectedApps);
+ $this->userOrganizationService->setUserOrganizations($user, $organization, $selectedRoles);
+
+
+
+ // Redirect to the user profile after successful update
+ return $this->redirectToRoute('user_show', ['id' => $user->getId()]);
}
- $user = $entityManager->getRepository(User::class)->find($id);
- if (!$user) {
+ //Overwrite the userOrganization with the userOrganizationsService for data consistency
+ // NULL pointer won't occur here because a valid UsersOrganizations entity was fetched above
+ $userOrganization = $this->userOrganizationService->getUserOrganizations($userOrganization->getUsers(), $userOrganization->getOrganization()->getId());
+
+ // Fetch all roles and apps
+ $roles = $entityManager->getRepository(Roles::class)->findAll();
+ $apps = $entityManager->getRepository(Apps::class)->findAll();
+ if (!$roles) {
+ throw $this->createNotFoundException(self::NOT_FOUND);
+ }
+ if (!$apps) {
throw $this->createNotFoundException(self::NOT_FOUND);
}
- // Handle organization user edit logic here
+ // Map roles and apps to arrays for rendering
+ $rolesArray = array_map(static function ($role) {
+ return [
+ 'id' => $role->getId(),
+ 'name' => $role->getName()
+ ];
+ }, $roles);
+ $appsArray = [];
+ foreach ($apps as $app) {
+ $appsArray[] = [
+ 'id' => $app->getId(),
+ 'name' => $app->getName(),
+ 'icon' => $packages->getUrl($app->getLogoUrl()),
+ ];
+ }
- return $this->redirectToRoute('user_show', ['id' => $user->getId()]);
+ // Map selected roles and apps to their IDs for the form
+ $selectedRoles = array_map(static function ($role) {
+ return $role->getId();
+ }, $userOrganization[0]["roles"]);
+ $selectedApps = array_map(static function ($app) {
+ return $app->getId();
+ }, $userOrganization[0]["apps"]);
+
+ return $this->render('user/organization/edit.html.twig', [
+ 'userOrganization' => $userOrganization,
+ 'user' => $user,
+ 'rolesArray' => $rolesArray,
+ 'selectedRoleIds' => $selectedRoles,
+ 'appsArray' => $appsArray,
+ 'selectedAppIds' => $selectedApps,]);
}
-
-
}
diff --git a/src/Form/UserOrganization.php b/src/Form/UserOrganization.php
new file mode 100644
index 0000000..d4fe7b9
--- /dev/null
+++ b/src/Form/UserOrganization.php
@@ -0,0 +1,27 @@
+add('admin' , CheckboxType::class, [
+ 'label' => 'Admin',
+ 'required' => false])
+ ->add('application' , ChoiceType::class, [
+ 'label' => 'Application',
+ 'choices' => [
+ 'Application 1' => 'app1',
+ 'Application 2' => 'app2',
+ 'Application 3' => 'app3',
+ ]]);
+ }
+
+}
diff --git a/src/Repository/UserOrganizationAppRepository.php b/src/Repository/UserOrganizationAppRepository.php
new file mode 100644
index 0000000..8776484
--- /dev/null
+++ b/src/Repository/UserOrganizationAppRepository.php
@@ -0,0 +1,43 @@
+
+ */
+class UserOrganizationAppRepository extends ServiceEntityRepository
+{
+ public function __construct(ManagerRegistry $registry)
+ {
+ parent::__construct($registry, UserOrganizationApp::class);
+ }
+
+// /**
+// * @return UserOrganizationApp[] Returns an array of UserOrganizationApp objects
+// */
+// public function findByExampleField($value): array
+// {
+// return $this->createQueryBuilder('u')
+// ->andWhere('u.exampleField = :val')
+// ->setParameter('val', $value)
+// ->orderBy('u.id', 'ASC')
+// ->setMaxResults(10)
+// ->getQuery()
+// ->getResult()
+// ;
+// }
+
+// public function findOneBySomeField($value): ?UserOrganizationApp
+// {
+// return $this->createQueryBuilder('u')
+// ->andWhere('u.exampleField = :val')
+// ->setParameter('val', $value)
+// ->getQuery()
+// ->getOneOrNullResult()
+// ;
+// }
+}
diff --git a/src/Repository/UserOrganizationRolesRepository.php b/src/Repository/UserOrganizationRolesRepository.php
new file mode 100644
index 0000000..bcad976
--- /dev/null
+++ b/src/Repository/UserOrganizationRolesRepository.php
@@ -0,0 +1,43 @@
+
+ */
+class UserOrganizationRolesRepository extends ServiceEntityRepository
+{
+ public function __construct(ManagerRegistry $registry)
+ {
+ parent::__construct($registry, UserOrganizationRoles::class);
+ }
+
+ // /**
+ // * @return UserOrganizationRoles[] Returns an array of UserOrganizationRoles objects
+ // */
+ // public function findByExampleField($value): array
+ // {
+ // return $this->createQueryBuilder('u')
+ // ->andWhere('u.exampleField = :val')
+ // ->setParameter('val', $value)
+ // ->orderBy('u.id', 'ASC')
+ // ->setMaxResults(10)
+ // ->getQuery()
+ // ->getResult()
+ // ;
+ // }
+
+ // public function findOneBySomeField($value): ?UserOrganizationRoles
+ // {
+ // return $this->createQueryBuilder('u')
+ // ->andWhere('u.exampleField = :val')
+ // ->setParameter('val', $value)
+ // ->getQuery()
+ // ->getOneOrNullResult()
+ // ;
+ // }
+}
diff --git a/src/Service/UserOrganizationService.php b/src/Service/UserOrganizationService.php
index f927ee8..49ddf19 100644
--- a/src/Service/UserOrganizationService.php
+++ b/src/Service/UserOrganizationService.php
@@ -2,6 +2,8 @@
namespace App\Service;
+use App\Entity\Apps;
+use App\Entity\Organizations;
use App\Entity\Roles;
use App\Entity\User;
use App\Entity\UsersOrganizations;
@@ -9,16 +11,19 @@ use Doctrine\ORM\EntityManagerInterface;
readonly class UserOrganizationService
{
- public function __construct(private readonly EntityManagerInterface $entityManager) {}
+ public function __construct(private readonly EntityManagerInterface $entityManager)
+ {
+ }
/**
* Returns all organizations the given user belongs to,
* including the unique roles and apps the user has in each organization.
*
- * @param User $user The user whose organizations are being fetched
+ * @param User $user The user whose organizations are being fetched
+ * @param int|null $organizationsId Optional organization ID to filter by
* @return array
*/
- public function getUserOrganizations(User $user): array
+ public function getUserOrganizations(User $user, int $organizationsId = null): array
{
$userOrganizations = $this->entityManager
->getRepository(UsersOrganizations::class)
@@ -28,8 +33,14 @@ readonly class UserOrganizationService
foreach ($userOrganizations as $uo) {
$orgId = $uo->getOrganization()->getId();
+ // If $organizationsId is provided, skip other organizations
+ if ($organizationsId !== null && $orgId !== $organizationsId) {
+ continue;
+ }
+
// Initialize the organization entry if it doesn't exist
$organizations[$orgId] = $organizations[$orgId] ?? $this->createEmptyOrganizationBucket($uo);
+ $organizations[$orgId]['uoId'] = $uo->getId();
// Aggregate roles & apps
$this->addRole($organizations[$orgId]['roles'], $uo->getRole());
@@ -44,23 +55,23 @@ readonly class UserOrganizationService
/**
* Build the initial array structure for a fresh organization entry.
*
- * @param UsersOrganizations $link
+ * @param UsersOrganizations $link
* @return array{organization:object, roles:Roles[], apps:array}
*/
private function createEmptyOrganizationBucket(UsersOrganizations $link): array
{
return [
'organization' => $link->getOrganization(),
- 'roles' => [],
- 'apps' => [],
+ 'roles' => [],
+ 'apps' => [],
];
}
/**
* Add a Role entity to the roles array only if it is not already present (by ID).
*
- * @param Roles[] &$roles
- * @param Roles|null $role
+ * @param Roles[] &$roles
+ * @param Roles|null $role
*/
private function addRole(array &$roles, ?Roles $role): void
{
@@ -78,8 +89,8 @@ readonly class UserOrganizationService
/**
* Merge one or many apps into the apps map, keeping only one entry per id.
*
- * @param array &$apps
- * @param iterable $appsToAdd Collection returned by $userOrganizations->getApps()
+ * @param array &$apps
+ * @param iterable $appsToAdd Collection returned by $userOrganizations->getApps()
*/
private function addApps(array &$apps, iterable $appsToAdd): void
{
@@ -92,7 +103,7 @@ readonly class UserOrganizationService
* Convert apps from associative maps (keyed by id) to plain indexed arrays,
* so the final output is clean JSON-able.
*
- * @param array &$organizations
+ * @param array &$organizations
*/
private function normalizeAppsIndexes(array &$organizations): void
{
@@ -100,4 +111,79 @@ readonly class UserOrganizationService
$org['apps'] = array_values($org['apps']);
}
}
+
+ public function setUserOrganizations(User $user, Organizations $organization, array $selectedRoles): void
+ {
+ $repo = $this->entityManager->getRepository(UsersOrganizations::class);
+
+ // 1. Get all current UsersOrganizations for this user/org
+ $currentUserOrgs = $repo->findBy([
+ 'users' => $user,
+ 'organization' => $organization
+ ]);
+
+ // 2. Build a map: roleId => UsersOrganizations entity
+ $currentRolesMap = [];
+ foreach ($currentUserOrgs as $uo) {
+ $currentRolesMap[$uo->getRole()->getId()] = $uo;
+ }
+
+ // 3. Add new roles that are selected but not present
+ foreach ($selectedRoles as $roleId) {
+ if (!isset($currentRolesMap[$roleId])) {
+ $roleEntity = $this->entityManager->getRepository(Roles::class)->find($roleId);
+ if ($roleEntity) {
+ $newUserOrganization = new UsersOrganizations();
+ $newUserOrganization->setUsers($user);
+ $newUserOrganization->setRole($roleEntity);
+ $newUserOrganization->setOrganization($organization);
+ $this->entityManager->persist($newUserOrganization);
+ }
+ }
+ // Remove from map so we know which ones to delete later
+ unset($currentRolesMap[$roleId]);
+ }
+
+ // 4. Remove roles that are present but not selected (deactivate them)
+ foreach ($currentRolesMap as $uo) {
+ $uo->setIsActive(false);
+ $this->entityManager->persist($uo);
+ }
+
+ $this->entityManager->flush();
+ }
+
+ public function setUserOrganizationsApps(User $user, Organizations $organization, array $selectedApps)
+ {
+ $apps = [];
+ $userOrganizations = $this->entityManager
+ ->getRepository(UsersOrganizations::class)
+ ->findAllDistinctOrganizationsByUserId($user->getId());
+ foreach ($userOrganizations as $uo) {
+ $this->addApps($apps, $uo->getApps());
+ }
+
+ $roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']);
+ $uoEntity = $this->entityManager
+ ->getRepository(UsersOrganizations::class)
+ ->findOneBy(['users' => $user, 'organization' => $organization, 'role' => $roleUser]);
+// dd($roleUser);
+ // 1. Remove apps that are no longer selected
+ foreach ($uoEntity->getApps() as $existingApp) {
+ if (!in_array($existingApp->getId(), $selectedApps)) {
+ $uoEntity->removeApp($existingApp);
+ }
+ }
+
+ // 2. Add newly selected apps (your existing logic)
+ foreach ($selectedApps as $appId) {
+ $appEntity = $this->entityManager->getRepository(Apps::class)->find($appId);
+ if ($appEntity && !$uoEntity->getApps()->contains($appEntity)) {
+ $uoEntity->addApp($appEntity);
+ }
+ }
+
+ $this->entityManager->persist($uoEntity);
+ $this->entityManager->flush();
+ }
}
diff --git a/symfony.lock b/symfony.lock
index 0993096..781a3e1 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -291,7 +291,8 @@
"assets/bootstrap.js",
"assets/controllers.json",
"assets/controllers/csrf_protection_controller.js",
- "assets/controllers/hello_controller.js"
+ "assets/controllers/hello_controller.js",
+ "assets/controllers/igg_controller.js"
]
},
"symfony/translation": {
diff --git a/templates/elements/userOrganizationInformation.html.twig b/templates/elements/userOrganizationInformation.html.twig
index 277ed42..b1cfc72 100644
--- a/templates/elements/userOrganizationInformation.html.twig
+++ b/templates/elements/userOrganizationInformation.html.twig
@@ -1,18 +1,21 @@
{% block body %}
-
-
-
+
+
+ {# Affichage du nom de l'organisation et de l'icône de flèche #}
+
{{ organization.name|capitalize }}
{{ ux_icon('fa6-regular:circle-down', {height: '25px', width: '25px'}) }}
-{#
Modifier#}
-
+ {% if is_granted("ROLE_ADMIN") %}
+
Modifier
+ {% endif %}
{# Information principale sur l'utilisateur dans l'organisation#}
@@ -35,10 +38,7 @@
{% if apps is not empty %}
{% for app in apps %}
-
- {{ app.name }}
-
 }})
-
+
 }})
{% endfor %}
{% else %}
Aucune application associée.
@@ -79,6 +79,7 @@
arrowEl.innerHTML = `{{ ux_icon('fa6-regular:circle-down', {height: '25px', width: '25px'})|e('js') }}`;
});
}
-});
+ });
+
{% endblock %}
\ No newline at end of file
diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig
index 5a20742..a4e0e39 100644
--- a/templates/user/index.html.twig
+++ b/templates/user/index.html.twig
@@ -3,7 +3,7 @@
{% block title %}User Profile{% endblock %}
{% block body %}
-
+
Gestion Utilisateurs
Ajouter un utilisateur
diff --git a/templates/user/organization/edit.html.twig b/templates/user/organization/edit.html.twig
new file mode 100644
index 0000000..43e0880
--- /dev/null
+++ b/templates/user/organization/edit.html.twig
@@ -0,0 +1,50 @@
+{% extends 'base.html.twig' %}
+
+
+
+{% block body %}
+
+
+
+ {% for uo in userOrganization %}
+
+
+ Modification de : {{ user.name|capitalize }} {{ user.surname|capitalize }} chez
+ {{ uo.organization.name }}
+
+
Supprimer
+
+
+
+ {% endfor %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/user/profile.html.twig b/templates/user/profile.html.twig
index 041ac60..2ad40f4 100644
--- a/templates/user/profile.html.twig
+++ b/templates/user/profile.html.twig
@@ -18,7 +18,7 @@
{% else %}
{% for organization in userOrganizations %}
{% include 'elements/userOrganizationInformation.html.twig'
- with {'organization': organization.organization, 'roles': organization.roles, 'apps': organization.apps} %}
+ with {'organization': organization.organization, 'roles': organization.roles, 'apps': organization.apps, 'uoId': organization.uoId} %}
{% endfor %}
{% endif %}