From b57236c4e77a4fe2557bb2cd2a38ed39facb4e37 Mon Sep 17 00:00:00 2001 From: maurine Date: Wed, 1 Apr 2026 10:00:26 +0200 Subject: [PATCH 1/6] =?UTF-8?q?feat=20:=20refonte=20compl=C3=A8te=20des=20?= =?UTF-8?q?migrations=20de=20base=20de=20donn=C3=A9es=20et=20amorce=20de?= =?UTF-8?q?=20mise=20en=20page=20de=20la=20page=20de=20profil=20utilisateu?= =?UTF-8?q?r=20(pseudo=20+=20autres=20infos=20en=20mode=20"modification".?= =?UTF-8?q?=20=20Suppression=20des=20commentaires=20inutiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 17 +- assets/styles/app.css | 2 +- config/services.yaml | 2 +- migrations/Version20260313151403.php | 35 --- migrations/Version20260316103235.php | 31 -- migrations/Version20260316104254.php | 31 -- migrations/Version20260316104335.php | 31 -- migrations/Version20260316104505.php | 31 -- migrations/Version20260316104557.php | 33 --- migrations/Version20260316114715.php | 49 ---- migrations/Version20260326214353.php | 31 -- migrations/Version20260326231417.php | 31 -- migrations/Version20260328101039.php | 35 --- migrations/Version20260328101220.php | 35 --- ...29084928.php => Version20260331084216.php} | 6 +- src/Controller/UserController.php | 94 +++--- src/Entity/User.php | 30 +- src/Form/UserProfileType.php | 44 ++- src/Service/FileUploader.php | 55 +++- src/Service/KazApiService.php | 2 +- templates/home/home.html.twig | 4 +- templates/user/index.html.twig | 268 ++++++++++-------- templates/user/profil_infos.html.twig | 2 +- 23 files changed, 307 insertions(+), 592 deletions(-) delete mode 100644 migrations/Version20260313151403.php delete mode 100644 migrations/Version20260316103235.php delete mode 100644 migrations/Version20260316104254.php delete mode 100644 migrations/Version20260316104335.php delete mode 100644 migrations/Version20260316104505.php delete mode 100644 migrations/Version20260316104557.php delete mode 100644 migrations/Version20260316114715.php delete mode 100644 migrations/Version20260326214353.php delete mode 100644 migrations/Version20260326231417.php delete mode 100644 migrations/Version20260328101039.php delete mode 100644 migrations/Version20260328101220.php rename migrations/{Version20260329084928.php => Version20260331084216.php} (90%) diff --git a/.env b/.env index 45d04f9..8867c0c 100644 --- a/.env +++ b/.env @@ -1,10 +1,11 @@ -APP_ENV=dev -APP_SECRET=je_te_remplis_parce_que_tu_me_mets_des_messages_d_erreur -APP_SHARE_DIR=var/share -APP_VERSION=0.0.1 -DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" -MESSENGER_TRANSPORT_DSN="doctrine://default" -MAILER_DSN="smtp://localhost:1025" -DEFAULT_URI="http://localhost:8000" +APP_ENV= +APP_SECRET= +APP_SHARE_DIR= +APP_VERSION= +DATABASE_URL= +MESSENGER_TRANSPORT_DSN= +MAILER_DSN= +DEFAULT_URI= +KAZ_API_BASE_URL= KAZ_API_USER= KAZ_API_PASSWORD= diff --git a/assets/styles/app.css b/assets/styles/app.css index e6b1010..d760871 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -30,6 +30,6 @@ --color-gris-fonce: #4B5563; /* Polices */ - --font-sora: "Sora", system-ui, sans-serif; + --font-sora: "Sora", sans-serif; --font-caveat: "Caveat", cursive; } diff --git a/config/services.yaml b/config/services.yaml index b37d953..421e277 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -16,7 +16,7 @@ services: $apiUser: '%env(KAZ_API_USER)%' $apiPassword: '%env(KAZ_API_PASSWORD)%' - # Gestion de l'enregistrement de la photo de profil + # Gestion de l'enregistrement de l'image de profil App\Service\FileUploader: arguments: $targetDirectory: '%images_directory%' diff --git a/migrations/Version20260313151403.php b/migrations/Version20260313151403.php deleted file mode 100644 index de8ff23..0000000 --- a/migrations/Version20260313151403.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, lastname VARCHAR(255) NOT NULL, firstname VARCHAR(255) NOT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/migrations/Version20260316103235.php b/migrations/Version20260316103235.php deleted file mode 100644 index 4094fd5..0000000 --- a/migrations/Version20260316103235.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ALTER alternate_email DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER alternate_email SET NOT NULL'); - } -} diff --git a/migrations/Version20260316104254.php b/migrations/Version20260316104254.php deleted file mode 100644 index baf28d0..0000000 --- a/migrations/Version20260316104254.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ALTER identifiant_kaz DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER identifiant_kaz SET NOT NULL'); - } -} diff --git a/migrations/Version20260316104335.php b/migrations/Version20260316104335.php deleted file mode 100644 index ebcb3db..0000000 --- a/migrations/Version20260316104335.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ALTER quota DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER quota SET NOT NULL'); - } -} diff --git a/migrations/Version20260316104505.php b/migrations/Version20260316104505.php deleted file mode 100644 index c68c5fd..0000000 --- a/migrations/Version20260316104505.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ALTER has_agora_access DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER has_agora_access SET NOT NULL'); - } -} diff --git a/migrations/Version20260316104557.php b/migrations/Version20260316104557.php deleted file mode 100644 index bcd88dc..0000000 --- a/migrations/Version20260316104557.php +++ /dev/null @@ -1,33 +0,0 @@ -addSql('ALTER TABLE "user" ALTER has_nextcloud_access DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_mobilizon DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_mobilizon SET NOT NULL'); - } -} diff --git a/migrations/Version20260316114715.php b/migrations/Version20260316114715.php deleted file mode 100644 index d0ff32d..0000000 --- a/migrations/Version20260316114715.php +++ /dev/null @@ -1,49 +0,0 @@ -addSql('ALTER TABLE "user" ADD last_name VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE "user" ADD first_name VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE "user" DROP lastname'); - $this->addSql('ALTER TABLE "user" DROP firstname'); - $this->addSql('ALTER TABLE "user" ALTER alternate_email SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER identifiant_kaz SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER quota SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_mobilizon SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_agora_access SET NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ADD lastname VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE "user" ADD firstname VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE "user" DROP last_name'); - $this->addSql('ALTER TABLE "user" DROP first_name'); - $this->addSql('ALTER TABLE "user" ALTER alternate_email DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER identifiant_kaz DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER quota DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_mobilizon DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_agora_access DROP NOT NULL'); - } -} diff --git a/migrations/Version20260326214353.php b/migrations/Version20260326214353.php deleted file mode 100644 index a2eddd8..0000000 --- a/migrations/Version20260326214353.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ADD photo VARCHAR(255) DEFAULT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" DROP photo'); - } -} diff --git a/migrations/Version20260326231417.php b/migrations/Version20260326231417.php deleted file mode 100644 index d7e8987..0000000 --- a/migrations/Version20260326231417.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ADD telephone VARCHAR(20) DEFAULT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" DROP telephone'); - } -} diff --git a/migrations/Version20260328101039.php b/migrations/Version20260328101039.php deleted file mode 100644 index 3c11da4..0000000 --- a/migrations/Version20260328101039.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/migrations/Version20260328101220.php b/migrations/Version20260328101220.php deleted file mode 100644 index d51320f..0000000 --- a/migrations/Version20260328101220.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/migrations/Version20260329084928.php b/migrations/Version20260331084216.php similarity index 90% rename from migrations/Version20260329084928.php rename to migrations/Version20260331084216.php index aa29b31..8f5111f 100644 --- a/migrations/Version20260329084928.php +++ b/migrations/Version20260331084216.php @@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20260329084928 extends AbstractMigration +final class Version20260331084216 extends AbstractMigration { public function getDescription(): string { @@ -20,8 +20,8 @@ final class Version20260329084928 extends AbstractMigration public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); + $this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)'); $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 972912f..52e32c9 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -7,10 +7,11 @@ use App\Form\UserProfileType; use App\Service\FileUploader; use App\Service\KazApiService; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpFoundation\Request; +use Exception; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormError; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Attribute\Route; @@ -45,8 +46,6 @@ class UserController extends AbstractController 'user' => $user, ]); } - - /* TODO : Param l'API avec un Serializer pour la lecture du fichier JSON ? */ #[Route('/mon-profil', name: 'app_user', methods: ['GET', 'POST'])] #[IsGranted('ROLE_USER')] public function showProfile( @@ -54,66 +53,54 @@ class UserController extends AbstractController EntityManagerInterface $entityManager, FileUploader $fileUploader, KazApiService $apiKazService - ): Response { - # Récupération de l'utilisateur actuellement connecté + ): Response + { + // Récupération de l'utilisateur actuellement connecté $user = $this->getUser(); + // Vérification si l'URL est en mode édition + $isEditMode = $request->query->getBoolean('edit', false); - $kazUser = $apiKazService->getUserData($user->getEmail()); + try { + // Récupération des données de l'utilisateur sur l'API grâce à son email + $kazUser = $apiKazService->getUserData($user->getEmail()); + // Initialisation de la variable $userData + $user = $user->updateFromKazUser($kazUser); + } catch (Exception $e) { + $this->addFlash('error', 'Impossible de charger vos données.'); + } - $user = $user->updateFromKazUser($kazUser); - - //TODO: modifier pour que ça communique avec l'API */ - - # Création du formulaire lié à l'utilisateur connecté + // Création du formulaire lié à l'utilisateur connecté $form = $this->createForm(UserProfileType::class, $user); $form->handleRequest($request); - # Traitement si l'utilisateur clique sur "Valider" + // Affichage du formulaire si les données sont valides if ($form->isSubmitted() && $form->isValid()) { - - /** @var UploadedFile $imageFile */ + /** @var UploadedFile|null $imageFile */ $imageFile = $form->get('image')->getData(); + // --- Gestion de l'image de profil --- if ($imageFile) { - # Suppression de l'ancienne photo du serveur - $fileUploader->delete($user->getPhoto()); - - # Dépot de la nouvelle photo + // Suppression de l'ancienne image via le service + if ($user->getImage()) { + $fileUploader->delete($user->getImage()); + } + // Dépôt de la nouvelle image et mise à jour de son nom dans l'entité $newFilename = $fileUploader->upload($imageFile); - - # Mise à jour de l'utilisateur avec le nouveau nom - $user->setPhoto($newFilename); + $user->setImage($newFilename); } - $alternateEmail = $form->get('alternateEmail')->getData(); - $regexEmail = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/'; - if(isset($alternateEmail) && preg_match($regexEmail, $alternateEmail)) { - $user->setAlternateEmail($form->get('alternateEmail')->getData()); - } else { - $alternateEmail->addError(new FormError('L\'adresse e-mail n\'est pas valide.')); - } - - $telephone = $form->get('telephone')->getData(); - $regexTelephone = '/^[0-9\+\s\.\-\(\)]+$/'; - if(isset($telephone) && preg_match($regexTelephone, $telephone)) { - $user->setTelephone($telephone); - } else { - $telephone->addError(new FormError('Le numéro de téléphone n\'est pas valide.')); - } - - # Sauvegarde en base de données + // Sauvegarde en base de données $entityManager->flush(); - - # Message de confirmation et rechargement de la page + // Message de confirmation et rechargement de la page $this->addFlash('success', 'Votre profil a été mis à jour avec succès !'); - + // Redirection de l'utilisateur return $this->redirectToRoute('app_user'); } - - # Affichage de la page + // Affichage de la page return $this->render('user/index.html.twig', [ 'form' => $form->createView(), - 'userData' => $user, # TODO : Mettre $userData quand connexion avec API OK + 'userData' => $user, + 'isEditMode' => $isEditMode, ]); } @@ -124,31 +111,30 @@ class UserController extends AbstractController EntityManagerInterface $entityManager ): Response { - # Création du formulaire + // Création du formulaire $form = $this->createForm(ChangePasswordType::class); - # Liaison du formulaire à la requête HTTP + // Liaison du formulaire à la requête HTTP $form->handleRequest($request); - # Vérification du formulaire, s'il est bien soumis et valide + // Vérification du formulaire, s'il est bien soumis et valide if ($form->isSubmitted() && $form->isValid()) { - # Récupération des données du formulaire + // Récupération des données du formulaire $user = $this->getUser(); $plainOldPassword = $form->get('oldPassword')->getData(); $newPassword = $form->get('newPassword')->getData(); - # Vérification de l'ancien mot de passe + // Vérification de l'ancien mot de passe if (!$hasher->isPasswordValid($user, $plainOldPassword)) { $form->get('oldPassword')->addError(new FormError('L\'ancien mot de passe est incorrect.')); } else { - # Si tout est OK : Hachage du mot de passe + // Si tout est OK : Hachage du mot de passe $hashedPassword = $hasher->hashPassword($user, $newPassword); $user->setPassword($hashedPassword); - # Sauvegarde en BDD + // Sauvegarde en BDD $entityManager->flush(); - - # Message de succès pour l'utilisateur + // Message de succès pour l'utilisateur $this->addFlash('success', 'Votre mot de passe a bien été mis à jour !'); return $this->redirectToRoute('app_user_edit_password'); diff --git a/src/Entity/User.php b/src/Entity/User.php index 88e6bf1..e381c84 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -3,7 +3,6 @@ namespace App\Entity; use App\Repository\UserRepository; -use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; @@ -69,9 +68,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(length: 255, name: 'first_name')] private ?string $firstName = null; - // TODO: Modifier "photo" par "image" - #[ORM\Column(length: 255, nullable: true, name: 'photo')] - private ?string $photo = null; + #[ORM\Column(length: 255, nullable: true, name: 'image')] + private ?string $image = null; #[ORM\Column(length: 20, nullable: true, name: 'telephone')] private ?string $telephone = null; @@ -290,14 +288,14 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } - public function getPhoto(): ?string + public function getImage(): ?string { - return $this->photo; + return $this->image; } - public function setPhoto(?string $photo): static + public function setImage(?string $image): static { - $this->photo = $photo; + $this->image = $image; return $this; } @@ -314,17 +312,27 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + // Fonction qui permet d'afficher les données de l'API sur la page de profil public function updateFromKazUser($kazUser) : User { + // Récupération et conversion des données de l'API pour les afficher $this->setEmail($kazUser['mail']); - // Création du firstname et lastname + // Création du firstname et lastname (une seule donnée sur l'API) $name = explode(' ', $kazUser['sn']); $this->setFirstName($name[0]); // Récupération des valeurs du tableau moins la première $aLastname = array_slice($name, 1); $this->setLastName(implode(' ', $aLastname)); - - //TODO: Ajouter les champs manquants de l'objet User dans l'api kaz. + // Récupération du mail de secours + $this->setAlternateEmail($kazUser['mailDeSecours']); + $this->setEmailQuota($kazUser['mailQuota']); + $this->setHasAgoraAccess($kazUser['agoraEnabled']); + $this->setHasMobilizon($kazUser['mobilizonEnabled']); + $this->setHasNextcloudAccess($kazUser['nextcloudEnabled']); + $this->setNextcloudQuota($kazUser['nextcloudQuota']); + $this->setQuota($kazUser['quota']); + $this->setIdentifiantKaz($kazUser['identifiantKaz']); + $this->setTelephone($kazUser['telephone'] ?? null); return $this; } diff --git a/src/Form/UserProfileType.php b/src/Form/UserProfileType.php index 566ca32..89eb761 100644 --- a/src/Form/UserProfileType.php +++ b/src/Form/UserProfileType.php @@ -3,11 +3,12 @@ namespace App\Form; use App\Entity\User; -use Symfony\Component\Form\Extension\Core\Type\TelType; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\TelType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\Image; @@ -27,22 +28,53 @@ class UserProfileType extends AbstractType 'label' => 'Nom', 'disabled' => true, ]) + ->add('identifiantKaz', TextType::class, [ + 'label' => 'Identifiant KAZ : ', + 'disabled' => true, + ]) ->add('email', EmailType::class, [ 'label' => 'E-mail', 'disabled' => true, ]) - ->add('alternateEmail', EmailType::class, ['label' => 'E-mail de secours']) + ->add('alternateEmail', EmailType::class, [ + 'label' => 'E-mail de secours', + 'constraints' => [ + new Regex( + pattern: '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', + message: 'L\'adresse e-mail n\'est pas valide.' + ) + ] + ]) + ->add('emailQuota', TextType::class, [ + 'label' => 'Espace de stockage de votre boîte mail : ', + 'disabled' => true, + ]) + ->add('hasNextcloudAccess', CheckboxType::class, [ + 'label' => 'Accès au Nextcloud : ', + 'disabled' => true, + ]) + ->add('nextcloudQuota', TextType::class, [ + 'label' => 'Espace de stockage de votre Nextcloud : ', + 'disabled' => true, + ]) + ->add('hasMobilizon', CheckboxType::class, [ + 'label' => 'Accès à Mobilizon : ', + 'disabled' => true, + ]) + ->add('hasAgoraAccess', CheckboxType::class, [ + 'label' => 'Accès à l\'Agora : ', + 'disabled' => true, + ]) ->add('telephone', TelType::class, [ 'label'=>'Téléphone', 'required' => false, 'attr' => [ 'placeholder'=>'06 00 00 00 00', - 'class'=> 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton transition-shadow' ], 'constraints' => [ new Regex( pattern: '/^[0-9\+\s\.\-\(\)]+$/', - message: 'Le numéro de téléphone contient des caractères non valides' + message: 'Le numéro de téléphone n\'est pas valide.' ), new Length( max: 20, @@ -51,7 +83,7 @@ class UserProfileType extends AbstractType ], ]) ->add('image', FileType::class, [ - 'label' => 'Ma photo de profil', + 'label' => 'Mon image de profil', 'mapped' => false, 'required' => false, 'constraints' => [ diff --git a/src/Service/FileUploader.php b/src/Service/FileUploader.php index 627af1d..144216f 100644 --- a/src/Service/FileUploader.php +++ b/src/Service/FileUploader.php @@ -2,45 +2,76 @@ namespace App\Service; +use RuntimeException; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\String\Slugger\SluggerInterface; +/** + * Service de gestion des envois et suppressions de fichiers. + */ class FileUploader { - // On utilise la promotion de constructeur (PHP 8) : ultra moderne et concis + /** + * @param string $targetDirectory Le chemin absolu vers le dossier de dépôt. + * @param SluggerInterface $slugger Le service de nettoyage des chaînes de caractères. + */ public function __construct( - private string $targetDirectory, - private SluggerInterface $slugger, - ) { + #[Autowire('%kernel.project_dir%/public/uploads/img')] + private readonly string $targetDirectory, + private readonly SluggerInterface $slugger, + ) + { } + /** + * Traite, sécurise et déplace un fichier déposé. + * + * @param UploadedFile $file Le fichier physique à déposer. + * @return string Le nom final sécurisé et unique du fichier. + * @throws RuntimeException En cas d'échec de l'écriture sur le disque. + */ public function upload(UploadedFile $file): string { $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); $safeFilename = $this->slugger->slug($originalFilename); - $fileName = $safeFilename . '-' . uniqid() . '.' . $file->guessExtension(); + + // Utilisation de uniqid('', true) pour garantir une unicité absolue en production + $fileName = sprintf('%s-%s.%s', $safeFilename, uniqid('', true), $file->guessExtension()); try { $file->move($this->getTargetDirectory(), $fileName); } catch (FileException $e) { - // Ici tu peux logguer l'erreur si besoin - throw new \Exception('Erreur lors du transfert de l\'image : ' . $e->getMessage()); + throw new RuntimeException('Erreur lors du transfert de l\'image : ' . $e->getMessage(), 0, $e); } return $fileName; } + /** + * Supprime physiquement un fichier du serveur. + * + * @param string|null $fileName Le nom du fichier à supprimer. + */ public function delete(?string $fileName): void { - if ($fileName) { - $filePath = $this->getTargetDirectory() . 'FileUploader.php/' . $fileName; - if (file_exists($filePath)) { - unlink($filePath); - } + if (null === $fileName) { + return; + } + + $filePath = rtrim($this->getTargetDirectory(), '/') . '/' . $fileName; + + if (file_exists($filePath)) { + unlink($filePath); } } + /** + * Retourne le chemin du répertoire de dépôt. + * + * @return string + */ public function getTargetDirectory(): string { return $this->targetDirectory; diff --git a/src/Service/KazApiService.php b/src/Service/KazApiService.php index e754325..9725f83 100644 --- a/src/Service/KazApiService.php +++ b/src/Service/KazApiService.php @@ -50,7 +50,7 @@ class KazApiService } $data = $response->toArray(); - $this->token = $data['access_token']; // Ajustez la clé selon le format de votre API + $this->token = $data['access_token']; return $this->token; } diff --git a/templates/home/home.html.twig b/templates/home/home.html.twig index b37ef47..8b42895 100644 --- a/templates/home/home.html.twig +++ b/templates/home/home.html.twig @@ -16,8 +16,8 @@ Bienvenue sur ton espace kaznaute {{ app.user ? app.user.userIdentifier : 'visiteur' }} ! - {# Zone réservée pour les futures données de l'API - TODO : Gérer les données de l'API #} + {# Zone réservée pour les futures données de l'API et Pahéko + TODO : Gérer les données avec Pahéko, mise en service en cours par un des développeurs de l'association. Cela sera vu à posteriori du stage #}

Votre abonnement actuellement : diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig index aacef5f..f2f6632 100644 --- a/templates/user/index.html.twig +++ b/templates/user/index.html.twig @@ -1,135 +1,165 @@ {% extends 'base.html.twig' %} -{% block title %}Accueil | {{ parent() }}{% endblock %} +{% block title %}Ma page de profil | {{ parent() }}{% endblock %} {% block body %}
- + {# Affichage du formulaire (seulement en mode édition) #} + {% if isEditMode %} {{ form_start(form, {'attr': {'class': 'max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 grid md:grid-cols-3 gap-8'}}) }} + {% else %} +
+ {% endif %} - {# TODO: voir si c'est pertinent avec l'API et s'il y a l'utilité d'une photo de profil #} - {# Gestion de la colone avec le choix de la photo de profil #} -
+ {# Colonne Photo #} +
+
+
+ {% if userData.image %} + Photo de profil + {% else %} +
👤 +
+ {% endif %} +
+
- {# Affichage de la photo de profil #} -
-
- {% if userData.photo %} - Photo de profil - {% else %} -
- 👤 +

Ma photo

+ + {# Affichage du champs "choisir en fichier" (seulement en mode édition) #} + {% if isEditMode %} +
+ {{ form_label(form.image, 'Choisir un fichier', {'label_attr': {'class': 'block mb-2.5 text-sm font-medium text-gris-fonce'}}) }} + {{ form_widget(form.image, { + 'attr': {'class': 'cursor-pointer bg-white border border-gris-clair text-text text-sm rounded-lg focus:outline-none focus:ring-1 focus:ring-bouton focus:border-bouton block w-full shadow-sm placeholder-gris-moyen file:mr-4 file:py-2.5 file:px-4 file:border-0 file:border-r file:border-gris-clair file:bg-gris-clair file:text-gris-fonce hover:file:bg-gris-moyen transition-colors'} + }) }} +

JPG, JPEG ou PNG (Taille max : 2Mo).

+
{{ form_errors(form.image) }}
+
+ {% endif %} +
+ + {# Colonne Infos Persos #} +
+ + {# Gestion des boutons d'action (Modifier / Annuler) et des titres #} +
+

Mon profil

+ +
+ {% if isEditMode %} + + ❌ Annuler + + {% else %} + + ✏️ Modifier + + {% endif %} +
+
+ +

Mes informations personnelles

+ +
+ {# Identifiant #} +
+ + {% if isEditMode %} + {{ form_widget(form.identifiantKaz, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} +
{{ form_errors(form.identifiantKaz) }}
+ {% else %} +
{{ userData.identifiantKaz ?? 'Non renseigné' }}
+ {% endif %} +
+ + {# --- NOM et Prénom --- #} +
+ {# NOM #} +
+ + {% if isEditMode %} + {{ form_widget(form.lastName, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} +
{{ form_errors(form.lastName) }}
+ {% else %} +
{{ userData.lastName }}
+ {% endif %} +
+ + {# Prénom #} +
+ + {% if isEditMode %} + {{ form_widget(form.firstName, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} +
{{ form_errors(form.firstName) }}
+ {% else %} +
{{ userData.firstName }}
+ {% endif %} +
+
+ + {# Téléphone #} +
+ + {% if isEditMode %} + {{ form_widget(form.telephone, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} +
{{ form_errors(form.telephone) }}
+ {% else %} +
{{ userData.telephone ?? 'Non renseigné' }}
+ {% endif %} +
+ + {# E-mail #} +
+ + {% if isEditMode %} + {{ form_widget(form.email, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} +
{{ form_errors(form.email) }}
+ {% else %} +
{{ userData.email }}
+ {% endif %} +
+ + {# E-mail de secours #} +
+ + {% if isEditMode %} + {{ form_widget(form.alternateEmail, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} +
{{ form_errors(form.alternateEmail) }}
+ {% else %} +
{{ userData.alternateEmail ?? 'Non renseigné' }}
+ {% endif %} +
+ + {# Affichage du bouton "Valider" (seulement en mode édition) #} + {% if isEditMode %} +
+
{% endif %}
-

Ma photo

- - {# Gestion du dépôt d'un fichier image #} -
- {{ form_label(form.image, 'Choisir un fichier', { - 'label_attr': {'class': 'block mb-2.5 text-sm font-medium text-gris-fonce'} - }) }} - {{ form_widget(form.image, { - 'attr': { - 'class': 'cursor-pointer bg-white border border-gris-clair text-text text-sm rounded-lg focus:outline-none focus:ring-1 focus:ring-bouton focus:border-bouton block w-full shadow-sm placeholder-gris-moyen file:mr-4 file:py-2.5 file:px-4 file:border-0 file:border-r file:border-gris-clair file:bg-gris-clair file:text-gris-fonce hover:file:bg-gris-moyen file:cursor-pointer transition-colors', - 'aria-describedby': 'file_input_help' - } - }) }} -

- JPG, JPEG ou PNG (Taille max : 2Mo). -

-
+ {# Fermuture du formulaire (seulement en mode édition) #} + {% if isEditMode %} + {{ form_end(form) }} + {% else %}
- - {# Gestion de la colonne avec les "infos persos" #} -
-

Mon profil

-

Mes informations personnelles

- - {# Gestion du formulaire qui regroupe toutes les infos perso #} -
- - {# Champ NOM et Prénom #} -
-
- {{ form_label(form.firstName, 'NOM :', { - 'label_attr': {'class': 'block text-sm font-semibold text-text'} - }) }} - {{ form_widget(form.firstName, { - 'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'} - }) }} - {# Implémentation d'un message d'errer en cas de problème #} -
- {{ form_errors(form.firstName) }} -
-
- -
- {{ form_label(form.lastName, 'Prénom :', { - 'label_attr': {'class': 'block text-sm font-semibold text-text'} - }) }} - {{ form_widget(form.lastName, { - 'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'} - }) }} - {# Implémentation d'un message d'errer en cas de problème #} -
- {{ form_errors(form.lastName) }} -
-
-
- - {# Champ Téléphone #} -
- {{ form_label(form.telephone, 'Numéro de téléphone', { - 'label_attr': {'class': 'block text-sm font-semibold text-text'} - }) }} - {{ form_widget(form.telephone) }} - {# Implémentation d'un message d'errer en cas de problème #} -
- {{ form_errors(form.telephone) }} -
-
- - {# Champ E-mail #} -
- {{ form_label(form.email, 'E-mail :', { - 'label_attr': {'class': 'block text-sm font-semibold text-text'} - }) }} - {{ form_widget(form.email, { - 'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'} - }) }} - {# Implémentation d'un message d'errer en cas de problème #} -
- {{ form_errors(form.email) }} -
-
- - {# Champ E-mail de secours #} -
- {{ form_label(form.alternateEmail, 'E-mail de secours :', { - 'label_attr': {'class': 'block text-sm font-semibold text-text'} - }) }} - {{ form_widget(form.alternateEmail, { - 'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'} - }) }} - {# Implémentation d'un message d'errer en cas de problème #} -
- {{ form_errors(form.alternateEmail) }} -
-
- -
- -
-
-
- {{ form_end(form) }} + {% endif %}
{% endblock %} diff --git a/templates/user/profil_infos.html.twig b/templates/user/profil_infos.html.twig index cc4c326..509b430 100644 --- a/templates/user/profil_infos.html.twig +++ b/templates/user/profil_infos.html.twig @@ -1,6 +1,6 @@ {% extends 'base.html.twig' %} -{% block title %}Accueil | {{ parent() }}{% endblock %} +{% block title %}Ma page de profil | {{ parent() }}{% endblock %} {% block body %}
-- 2.49.1 From 7a25779c9c04aa42b4f1a7cbc10e87b877de2f70 Mon Sep 17 00:00:00 2001 From: maurine Date: Wed, 1 Apr 2026 11:58:01 +0200 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20am=C3=A9lioration=20de=20la=20gesti?= =?UTF-8?q?on=20des=20profils=20utilisateurs=20(suppression=20de=20`profil?= =?UTF-8?q?=5Finfos.html.twig`,=20mise=20=C3=A0=20jour=20des=20formulaires?= =?UTF-8?q?,=20meilleur=20affichage=20des=20quotas=20et=20permissions,=20e?= =?UTF-8?q?t=20support=20des=20fichiers=20GIF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller/UserController.php | 8 - src/Form/UserProfileType.php | 6 +- templates/user/edit_password.html.twig | 4 +- templates/user/index.html.twig | 224 +++++++++++++++++++------ templates/user/profil_infos.html.twig | 11 -- 5 files changed, 177 insertions(+), 76 deletions(-) delete mode 100644 templates/user/profil_infos.html.twig diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 52e32c9..5d4b870 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -38,14 +38,6 @@ class UserController extends AbstractController * @throws TransportExceptionInterface */ - #[Route('/user/{email}', name: 'app_user_by_mail', methods: ['GET'])] - public function index(string $email, KazApiService $apiKazService): Response - { - $user = $apiKazService->getUserData($email); - return $this->render('user/profil_infos.html.twig', [ - 'user' => $user, - ]); - } #[Route('/mon-profil', name: 'app_user', methods: ['GET', 'POST'])] #[IsGranted('ROLE_USER')] public function showProfile( diff --git a/src/Form/UserProfileType.php b/src/Form/UserProfileType.php index 89eb761..ea9d01e 100644 --- a/src/Form/UserProfileType.php +++ b/src/Form/UserProfileType.php @@ -88,9 +88,9 @@ class UserProfileType extends AbstractType 'required' => false, 'constraints' => [ new Image( - maxSize: '2M', - extensions: ['jpg', 'jpeg', 'png'], - extensionsMessage: 'Veuillez déposer une image JPG, JPEG ou PNG valide',) + maxSize: '8M', + extensions: ['jpg', 'jpeg', 'png', 'gif'], + extensionsMessage: 'Veuillez déposer une image JPG, JPEG, GIF ou PNG valide',) ], ]) ; diff --git a/templates/user/edit_password.html.twig b/templates/user/edit_password.html.twig index 7f91286..4deec57 100644 --- a/templates/user/edit_password.html.twig +++ b/templates/user/edit_password.html.twig @@ -62,10 +62,12 @@
{# Bouton de validation #} +
+
{{ form_end(form) }} diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig index f2f6632..7b7dadf 100644 --- a/templates/user/index.html.twig +++ b/templates/user/index.html.twig @@ -1,41 +1,61 @@ {% extends 'base.html.twig' %} +{# @var userData \App\Entity\User #} +{# @var form \Symfony\Component\Form\FormView #} +{# @var isEditMode bool #} + {% block title %}Ma page de profil | {{ parent() }}{% endblock %} {% block body %}
{# Affichage du formulaire (seulement en mode édition) #} - {% if isEditMode %} - {{ form_start(form, {'attr': {'class': 'max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 grid md:grid-cols-3 gap-8'}}) }} - {% else %}
+ {% if isEditMode %} + {{ form_start(form, {'attr': {'class': 'contents'}}) }} {% endif %} {# Colonne Photo #}
-
-
- {% if userData.image %} - Photo de profil - {% else %} -
👤 -
- {% endif %} -
+ +
+ {% if userData.image %} + {# Design avec photo #} + Photo de profil + {% else %} + {# Design sans photo #} +
+ + + +
+ {% endif %}
-

Ma photo

+

Ma photo

- {# Affichage du champs "choisir en fichier" (seulement en mode édition) #} + {# Gestion du dépôt d'un fichier image (Uniquement en mode édition) #} {% if isEditMode %} -
- {{ form_label(form.image, 'Choisir un fichier', {'label_attr': {'class': 'block mb-2.5 text-sm font-medium text-gris-fonce'}}) }} - {{ form_widget(form.image, { - 'attr': {'class': 'cursor-pointer bg-white border border-gris-clair text-text text-sm rounded-lg focus:outline-none focus:ring-1 focus:ring-bouton focus:border-bouton block w-full shadow-sm placeholder-gris-moyen file:mr-4 file:py-2.5 file:px-4 file:border-0 file:border-r file:border-gris-clair file:bg-gris-clair file:text-gris-fonce hover:file:bg-gris-moyen transition-colors'} +
+ {{ form_label(form.image, 'Choisir un fichier', { + 'label_attr': {'class': 'block mb-2.5 text-sm font-medium text-gris-fonce'} }) }} -

JPG, JPEG ou PNG (Taille max : 2Mo).

-
{{ form_errors(form.image) }}
+ {{ form_widget(form.image, { + 'attr': { + 'class': 'cursor-pointer bg-white border border-gris-clair text-text text-sm rounded-lg focus:outline-none focus:ring-1 focus:ring-bouton focus:border-bouton block w-full shadow-sm placeholder-gris-moyen file:mr-4 file:py-2.5 file:px-4 file:border-0 file:border-r file:border-gris-clair file:bg-gris-clair file:text-gris-fonce hover:file:bg-gris-moyen transition-colors', + 'aria-describedby': 'file_input_help' + } + }) }} +

+ JPG, JPEG, GIF ou PNG (Taille max : 8Mo). +

+
+ {{ form_errors(form.image) }} +
{% endif %}
@@ -64,16 +84,20 @@

Mes informations personnelles

+ {# --- Gestion de l'affichage des informations personnelles --- #}
{# Identifiant #}
- + {% if isEditMode %} - {{ form_widget(form.identifiantKaz, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} -
{{ form_errors(form.identifiantKaz) }}
+ {{ form_widget(form.identifiantKaz, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }} {% else %}
{{ userData.identifiantKaz ?? 'Non renseigné' }}
+ class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text"> + {{ userData.identifiantKaz ?? 'Non défini' }} +
{% endif %}
@@ -81,73 +105,167 @@
{# NOM #}
- + {% if isEditMode %} - {{ form_widget(form.lastName, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} -
{{ form_errors(form.lastName) }}
+ {{ form_widget(form.lastName, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }} +
+ {{ form_errors(form.lastName) }} +
{% else %}
{{ userData.lastName }}
+ class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text"> + {{ userData.lastName }} +
{% endif %}
{# Prénom #}
- + {% if isEditMode %} - {{ form_widget(form.firstName, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} -
{{ form_errors(form.firstName) }}
+ {{ form_widget(form.firstName, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }} +
+ {{ form_errors(form.firstName) }} +
{% else %}
{{ userData.firstName }}
+ class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text"> + {{ userData.firstName }} +
{% endif %}
{# Téléphone #}
- + {% if isEditMode %} - {{ form_widget(form.telephone, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} -
{{ form_errors(form.telephone) }}
+ {{ form_widget(form.telephone, {'attr': {'class': 'w-full px-4 py-3 text-base bg-white border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} +
+ {{ form_errors(form.telephone) }} +
{% else %}
{{ userData.telephone ?? 'Non renseigné' }}
+ class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text"> + {{ userData.telephone ?? 'Non renseigné' }} +
{% endif %}
{# E-mail #}
- + {% if isEditMode %} - {{ form_widget(form.email, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} -
{{ form_errors(form.email) }}
+ {{ form_widget(form.email, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }} +
+ {{ form_errors(form.email) }} +
{% else %}
{{ userData.email }}
+ class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text"> + {{ userData.email }} +
{% endif %}
{# E-mail de secours #}
- + {% if isEditMode %} - {{ form_widget(form.alternateEmail, {'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} -
{{ form_errors(form.alternateEmail) }}
+ {{ form_widget(form.alternateEmail, {'attr': {'class': 'w-full px-4 py-3 text-base bg-white border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }} +
+ {{ form_errors(form.alternateEmail) }} +
{% else %}
{{ userData.alternateEmail ?? 'Non renseigné' }}
+ class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text"> + {{ userData.alternateEmail ?? 'Non renseigné' }} +
{% endif %}
+ {# Quota Email #} +
+ + {% if isEditMode %} + {{ form_widget(form.emailQuota, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }} + {% else %} +
+ {{ userData.emailQuota ?? 'Non défini' }} +
+ {% endif %} +
+ + {# Quota Nextcloud #} +
+ + {% if isEditMode %} + {{ form_widget(form.nextcloudQuota, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }} + {% else %} +
+ {{ userData.nextcloudQuota ?? 'Non défini' }} +
+ {% endif %} +
+ + {# --- Gestion de l'affichage des checkbox des différents accès --- #} +
+ + {# Accès Nextcloud #} +
+ {% if isEditMode %} + {{ form_widget(form.hasNextcloudAccess, {'attr': {'class': 'w-5 h-5 text-bouton border-gris-clair rounded focus:ring-bouton cursor-not-allowed opacity-60'}}) }} + {{ form_label(form.hasNextcloudAccess, null, {'label_attr': {'class': 'text-base font-medium text-text'}}) }} + {% else %} + {% if userData.hasNextcloudAccess %}✅{% else %}❌{% endif %} + Accès Nextcloud + {% endif %} +
+ + {# Accès Mobilizon #} +
+ {% if isEditMode %} + {{ form_widget(form.hasMobilizon, {'attr': {'class': 'w-5 h-5 text-bouton border-gris-clair rounded focus:ring-bouton cursor-not-allowed opacity-60'}}) }} + {{ form_label(form.hasMobilizon, null, {'label_attr': {'class': 'text-base font-medium text-text'}}) }} + {% else %} + {% if userData.hasMobilizon %}✅{% else %}❌{% endif %} + Accès Mobilizon + {% endif %} +
+ + {# Accès Agora #} +
+ {% if isEditMode %} + {{ form_widget(form.hasAgoraAccess, {'attr': {'class': 'w-5 h-5 text-bouton border-gris-clair rounded focus:ring-bouton cursor-not-allowed opacity-60'}}) }} + {{ form_label(form.hasAgoraAccess, null, {'label_attr': {'class': 'text-base font-medium text-text'}}) }} + {% else %} + {% if userData.hasAgoraAccess %}✅{% else %}❌{% endif %} + Accès Agora + {% endif %} +
+
+ {# Affichage du bouton "Valider" (seulement en mode édition) #} {% if isEditMode %} -
+
@@ -157,9 +275,9 @@ {# Fermuture du formulaire (seulement en mode édition) #} {% if isEditMode %} - {{ form_end(form) }} - {% else %} + {{ form_end(form) }} + {% endif %} +
- {% endif %}
{% endblock %} diff --git a/templates/user/profil_infos.html.twig b/templates/user/profil_infos.html.twig deleted file mode 100644 index 509b430..0000000 --- a/templates/user/profil_infos.html.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block title %}Ma page de profil | {{ parent() }}{% endblock %} - -{% block body %} -
-
- Identifiant : {{ user.identifiantKaz }} -
-
-{% endblock %} -- 2.49.1 From 7400d0d4182ccb13ba25fbdb5c90ee41329e8ef0 Mon Sep 17 00:00:00 2001 From: maurine Date: Sat, 4 Apr 2026 12:53:48 +0200 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20mise=20=C3=A0=20jour=20du=20profil?= =?UTF-8?q?=20utilisateur=20et=20int=C3=A9gration=20API=20Kaz=20(synchro?= =?UTF-8?q?=20des=20donn=C3=A9es,=20gestion=20des=20erreurs=20et=20succ?= =?UTF-8?q?=C3=A8s)=20+=20divers=20correctifs=20(liens=20externes,=20simpl?= =?UTF-8?q?ifications=20de=20commentaires)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.lock | 12 ++++---- src/Controller/UserController.php | 17 ++++++++++- src/DataFixtures/AppFixtures.php | 50 ++++++++++++++++++++----------- src/Entity/User.php | 23 ++++++++++++++ src/Form/UserProfileType.php | 2 +- src/Service/KazApiService.php | 16 ++++++++++ templates/base.html.twig | 6 ++-- templates/home/home.html.twig | 4 +-- 8 files changed, 100 insertions(+), 30 deletions(-) diff --git a/composer.lock b/composer.lock index 7d7666c..c4512b7 100644 --- a/composer.lock +++ b/composer.lock @@ -3686,16 +3686,16 @@ }, { "name": "symfony/http-client", - "version": "v8.0.5", + "version": "v8.0.8", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "f9fdd372473e66469c6d32a4ed12efcffdea38c4" + "reference": "356e43d6994ae9d7761fd404d40f78691deabe0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/f9fdd372473e66469c6d32a4ed12efcffdea38c4", - "reference": "f9fdd372473e66469c6d32a4ed12efcffdea38c4", + "url": "https://api.github.com/repos/symfony/http-client/zipball/356e43d6994ae9d7761fd404d40f78691deabe0e", + "reference": "356e43d6994ae9d7761fd404d40f78691deabe0e", "shasum": "" }, "require": { @@ -3758,7 +3758,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v8.0.5" + "source": "https://github.com/symfony/http-client/tree/v8.0.8" }, "funding": [ { @@ -3778,7 +3778,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:18:07+00:00" + "time": "2026-03-30T15:14:47+00:00" }, { "name": "symfony/http-client-contracts", diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 5d4b870..04ef9b0 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -58,7 +58,7 @@ class UserController extends AbstractController // Initialisation de la variable $userData $user = $user->updateFromKazUser($kazUser); } catch (Exception $e) { - $this->addFlash('error', 'Impossible de charger vos données.'); + $this->addFlash('error', 'Impossible de charger vos données.' . $e->getMessage()); } // Création du formulaire lié à l'utilisateur connecté @@ -80,6 +80,21 @@ class UserController extends AbstractController $newFilename = $fileUploader->upload($imageFile); $user->setImage($newFilename); } + // --- Fin gestion de l'image de profil --- + + // Synchronisation des données avec l'API + $kazUser = [ + 'telephone' => $form->get('telephone')->getData(), + 'alternateEmail' => $form->get('alternateEmail')->getData(), + ]; + + try { + $apiKazService->updateUserData($user->getEmail(), $kazUser); + + $this->addFlash('success', 'Votre profil a été mis à jour avec succès !'); + } catch (Exception $e) { + $this->addFlash('error', 'Impossible de mettre à jour votre profil' . $e->getMessage()); + } // Sauvegarde en base de données $entityManager->flush(); diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index cf35c5b..1e31748 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -10,35 +10,36 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class AppFixtures extends Fixture { - # Initialisation de l'outil de hachage de Symfony + // Initialisation de l'outil de hachage de Symfony private UserPasswordHasherInterface $hasher; - # Injection de dépendance pour récupérer le service de sécurité + // Injection de dépendance pour récupérer le service de sécurité public function __construct(UserPasswordHasherInterface $hasher) { $this->hasher = $hasher; } - # Méthode principale qui génère les données en base + // Méthode principale qui génère des données de test en BDD locale public function load(ObjectManager $manager): void { - # Initialisation de Faker en français + // --- Création de 10 utilisateurs avec Faker --- // + // Initialisation de Faker en français $faker = Factory::create('fr_FR'); - # Boucle pour créer 10 utilisateurs + // Boucle pour créer 10 utilisateurs for ($i = 0; $i < 10; $i++) { - # Instanciation d'un nouvel utilisateur (Adhérent) + // Instanciation d'un nouvel utilisateur (Adhérent) $user = new User(); - # Attribution d'un email aléatoire et unique + // Attribution d'un email aléatoire et unique $user->setEmail($faker->unique()->safeEmail()); - # Définition des droits d'accès de l'utilisateur + // Définition des droits d'accès de l'utilisateur $user->setRoles(['ROLE_USER', 'ROLE_ORGANISATION']); - # Hachage sécurisé du mot de passe "password" + // Hachage sécurisé du mot de passe "password" $user->setPassword($this->hasher->hashPassword($user, 'password')); - # Définition d'un NOM et Prénom + // Définition d'un NOM et Prénom $user->setFirstname($faker->firstName()); $user->setLastname($faker->lastName()); - # autres fixtures à modifier plus tard + // autres fixtures à modifier plus tard $user->setNextcloudQuota($faker->numberBetween(1, 20) . 'G'); $user->setQuota($faker->numberBetween(1, 10) . 'G'); $user->setEmailQuota('1G'); @@ -47,7 +48,7 @@ class AppFixtures extends Fixture $user->setHasMobilizon($faker->boolean(50)); $user->setHasNextcloudAccess($faker->boolean(90)); $user->setIdentifiantKaz($faker->uuid()); - # Préparation de l'enregistrement de l'objet en base de données + // Préparation de l'enregistrement de l'objet en base de données $manager->persist($user); } @@ -56,10 +57,9 @@ class AppFixtures extends Fixture $admin->setEmail('admin@kaz.bzh'); $admin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']); $admin->setPassword($this->hasher->hashPassword($admin, 'password')); + // Remplissage des champs obligatoires restants pour éviter les erreurs SQL $admin->setFirstName('Admin'); $admin->setLastName('KAZ'); - - // Remplissage des champs obligatoires restants pour éviter les erreurs SQL $admin->setAlternateEmail('secours@kaz.bzh'); $admin->setIdentifiantKaz('ADMIN-KAZ-001'); $admin->setQuota('5G'); @@ -71,14 +71,13 @@ class AppFixtures extends Fixture $manager->persist($admin); - // Création d'un compte de test fixe + // Création d'un compte de test fixe présent dans le LDAP $melvin = new User(); $melvin->setEmail('melvin.leveque@kazkouil.fr'); $melvin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']); $melvin->setPassword($this->hasher->hashPassword($melvin, 'password')); $melvin->setFirstName(''); $melvin->setLastName(''); - $melvin->setAlternateEmail(''); $melvin->setIdentifiantKaz('MELVIN-KAZ-001'); $melvin->setQuota('5G'); @@ -90,7 +89,24 @@ class AppFixtures extends Fixture $manager->persist($melvin); - # Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie + // Création d'un compte de test fixe présent dans le LDAP pour ma présentation + $toto = new User(); + $toto->setEmail('toto@kazkouil.fr'); + $toto->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']); + $toto->setPassword($this->hasher->hashPassword($toto, 'password')); + $toto->setFirstName(''); + $toto->setLastName(''); + $toto->setAlternateEmail(''); + $toto->setIdentifiantKaz(''); + $toto->setQuota('5G'); + $toto->setEmailQuota('1G'); + $toto->setNextcloudQuota('10G'); + $toto->setHasNextcloudAccess(true); + $toto->setHasMobilizon(true); + $toto->setHasAgoraAccess(true); + $manager->persist($toto); + + // Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie $manager->flush(); } } diff --git a/src/Entity/User.php b/src/Entity/User.php index e381c84..7612ce4 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -336,4 +336,27 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + // Fonction qui permet de convertir les données de l'API vers $kazUser + public function convertToKazUser() : array + { + $fullName = implode(' ', array_filter([ + $this->getFirstName(), + $this->getLastName() + ])); + + return [ + 'mail' => $this->getEmail(), + 'sn' => $fullName, + 'mailDeSecours' => $this->getAlternateEmail(), + 'mailQuota' => $this->getEmailQuota(), + 'agoraEnabled' => $this->hasAgoraAccess(), + 'mobilizonEnabled' => $this->hasMobilizon(), + 'nextcloudEnabled' => $this->hasNextcloudAccess(), + 'nextcloudQuota' => $this->getNextcloudQuota(), + 'quota' => $this->getQuota(), + 'identifiantKaz' => $this->getIdentifiantKaz(), + 'telephone' => $this->getTelephone(), + ]; + } } diff --git a/src/Form/UserProfileType.php b/src/Form/UserProfileType.php index ea9d01e..10f2aa0 100644 --- a/src/Form/UserProfileType.php +++ b/src/Form/UserProfileType.php @@ -90,7 +90,7 @@ class UserProfileType extends AbstractType new Image( maxSize: '8M', extensions: ['jpg', 'jpeg', 'png', 'gif'], - extensionsMessage: 'Veuillez déposer une image JPG, JPEG, GIF ou PNG valide',) + extensionsMessage: 'Veuillez déposer une image JPG, JPEG, GIF ou PNG valide') ], ]) ; diff --git a/src/Service/KazApiService.php b/src/Service/KazApiService.php index 9725f83..274de14 100644 --- a/src/Service/KazApiService.php +++ b/src/Service/KazApiService.php @@ -4,6 +4,8 @@ namespace App\Service; use Exception; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\FormDataPart; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; @@ -80,4 +82,18 @@ class KazApiService return $response->toArray(); } + + /** + * Envoie les nouvelles données saisies par l'utilisateur vers l'API + */ + public function updateUserData(string $email, array $kazUser): void + { + $options['headers']['Authorization'] = 'Bearer ' . $this->getToken(); + $options['body'] = json_encode($kazUser); +// $response = $this->kazApiClient->request('PUT', "/ldap/user/add/$email", $options); + + if ($response->getStatusCode() !== 200) { + throw new Exception('Erreur lors de l\'appel API : ' . $response->getStatusCode()); + } + } } diff --git a/templates/base.html.twig b/templates/base.html.twig index 50788c4..9b74520 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -80,13 +80,13 @@
diff --git a/templates/home/home.html.twig b/templates/home/home.html.twig index 8b42895..409cd2b 100644 --- a/templates/home/home.html.twig +++ b/templates/home/home.html.twig @@ -16,7 +16,7 @@ Bienvenue sur ton espace kaznaute {{ app.user ? app.user.userIdentifier : 'visiteur' }} !

- {# Zone réservée pour les futures données de l'API et Pahéko + {# Zone réservée pour les futures données de Pahéko TODO : Gérer les données avec Pahéko, mise en service en cours par un des développeurs de l'association. Cela sera vu à posteriori du stage #}

@@ -91,7 +91,7 @@

{% else %} - +
✉️
-- 2.49.1 From 6f9523f9e746a482f98e1194fa0bed7f52419b1b Mon Sep 17 00:00:00 2001 From: MLeveque Date: Sun, 5 Apr 2026 14:07:50 +0200 Subject: [PATCH 4/6] =?UTF-8?q?fix(user):=20Appel=20=C3=A0=20la=20route=20?= =?UTF-8?q?de=20modification=20d'un=20utilisateur=20dans=20l'API.=20(PATCH?= =?UTF-8?q?=20/ldap/user/update/{email})?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adaptation de l'objet kazUser pour qu'il corresponde a l'attente de l'api. --- src/Controller/UserController.php | 12 ++---- src/Entity/User.php | 67 +++++++++++++++++++++++-------- src/Service/KazApiService.php | 12 +++++- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 04ef9b0..d51e365 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -82,20 +82,14 @@ class UserController extends AbstractController } // --- Fin gestion de l'image de profil --- - // Synchronisation des données avec l'API - $kazUser = [ - 'telephone' => $form->get('telephone')->getData(), - 'alternateEmail' => $form->get('alternateEmail')->getData(), - ]; + // Synchronisation des données avec l'API + $kazUser = $user->convertToKazUser(); try { $apiKazService->updateUserData($user->getEmail(), $kazUser); - - $this->addFlash('success', 'Votre profil a été mis à jour avec succès !'); - } catch (Exception $e) { + } catch (Exception $e) { $this->addFlash('error', 'Impossible de mettre à jour votre profil' . $e->getMessage()); } - // Sauvegarde en base de données $entityManager->flush(); // Message de confirmation et rechargement de la page diff --git a/src/Entity/User.php b/src/Entity/User.php index 7612ce4..d52b38d 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -74,6 +74,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(length: 20, nullable: true, name: 'telephone')] private ?string $telephone = null; + private ?string $numeroMembre = null; + + private ?bool $mailEnabled = null; + + private ?string $mailAlias = null; + public function __construct() { $this->emailQuota = self::EMAIL_QUOTA_DEFAULT; } @@ -312,6 +318,42 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + public function getNumeroMembre(): ?string + { + return $this->numeroMembre; + } + + public function setNumeroMembre(?string $numeroMembre): static + { + $this->numeroMembre = $numeroMembre; + + return $this; + } + + public function isMailEnabled(): ?bool + { + return $this->mailEnabled; + } + + public function setMailEnabled(?bool $mailEnabled): static + { + $this->mailEnabled = $mailEnabled; + + return $this; + } + + public function getMailAlias(): ?string + { + return $this->mailAlias; + } + + public function setMailAlias(?string $mailAlias): static + { + $this->mailAlias = $mailAlias; + + return $this; + } + // Fonction qui permet d'afficher les données de l'API sur la page de profil public function updateFromKazUser($kazUser) : User { @@ -332,31 +374,24 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface $this->setNextcloudQuota($kazUser['nextcloudQuota']); $this->setQuota($kazUser['quota']); $this->setIdentifiantKaz($kazUser['identifiantKaz']); - $this->setTelephone($kazUser['telephone'] ?? null); - return $this; } // Fonction qui permet de convertir les données de l'API vers $kazUser public function convertToKazUser() : array { - $fullName = implode(' ', array_filter([ - $this->getFirstName(), - $this->getLastName() - ])); - - return [ - 'mail' => $this->getEmail(), - 'sn' => $fullName, + $data = [ + 'numeroMembre' => $this->getNumeroMembre(), 'mailDeSecours' => $this->getAlternateEmail(), - 'mailQuota' => $this->getEmailQuota(), - 'agoraEnabled' => $this->hasAgoraAccess(), - 'mobilizonEnabled' => $this->hasMobilizon(), + 'mailEnabled' => $this->isMailEnabled(), 'nextcloudEnabled' => $this->hasNextcloudAccess(), - 'nextcloudQuota' => $this->getNextcloudQuota(), - 'quota' => $this->getQuota(), + 'mobilizonEnabled' => $this->hasMobilizon(), + 'agoraEnabled' => $this->hasAgoraAccess(), 'identifiantKaz' => $this->getIdentifiantKaz(), - 'telephone' => $this->getTelephone(), + 'mailAlias' => $this->getMailAlias(), + 'quota' => $this->getQuota(), ]; + + return array_filter($data, fn($value) => $value !== null); } } diff --git a/src/Service/KazApiService.php b/src/Service/KazApiService.php index 274de14..10a80c9 100644 --- a/src/Service/KazApiService.php +++ b/src/Service/KazApiService.php @@ -85,12 +85,20 @@ class KazApiService /** * Envoie les nouvelles données saisies par l'utilisateur vers l'API + * + * @throws ClientExceptionInterface + * @throws DecodingExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + * @throws Exception */ public function updateUserData(string $email, array $kazUser): void { $options['headers']['Authorization'] = 'Bearer ' . $this->getToken(); - $options['body'] = json_encode($kazUser); -// $response = $this->kazApiClient->request('PUT', "/ldap/user/add/$email", $options); + $options['headers']['Content-Type'] = 'application/json'; + $options['json'] = $kazUser; + $response = $this->kazApiClient->request('PATCH', "/ldap/user/update/$email", $options); if ($response->getStatusCode() !== 200) { throw new Exception('Erreur lors de l\'appel API : ' . $response->getStatusCode()); -- 2.49.1 From 1bb651b7e4944011b6eac0aa60b25d28c24e1f48 Mon Sep 17 00:00:00 2001 From: maurine Date: Thu, 9 Apr 2026 19:36:55 +0200 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20suppression=20d'une=20migration=20e?= =?UTF-8?q?t=20am=C3=A9lioration=20des=20formulaires=20+=20messages=20d'er?= =?UTF-8?q?reur=20(r=C3=A9vision=20design=20+=20placeholders=20ajout=C3=A9?= =?UTF-8?q?s)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- migrations/Version20260331084216.php | 35 ----------------- src/DataFixtures/AppFixtures.php | 18 --------- templates/home/home.html.twig | 10 ++--- templates/user/edit_password.html.twig | 54 ++++++++++++++------------ translations/security.fr.yaml | 2 + 5 files changed, 36 insertions(+), 83 deletions(-) delete mode 100644 migrations/Version20260331084216.php create mode 100644 translations/security.fr.yaml diff --git a/migrations/Version20260331084216.php b/migrations/Version20260331084216.php deleted file mode 100644 index 8f5111f..0000000 --- a/migrations/Version20260331084216.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 1e31748..cec78c9 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -71,24 +71,6 @@ class AppFixtures extends Fixture $manager->persist($admin); - // Création d'un compte de test fixe présent dans le LDAP - $melvin = new User(); - $melvin->setEmail('melvin.leveque@kazkouil.fr'); - $melvin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']); - $melvin->setPassword($this->hasher->hashPassword($melvin, 'password')); - $melvin->setFirstName(''); - $melvin->setLastName(''); - $melvin->setAlternateEmail(''); - $melvin->setIdentifiantKaz('MELVIN-KAZ-001'); - $melvin->setQuota('5G'); - $melvin->setEmailQuota('1G'); - $melvin->setNextcloudQuota('10G'); - $melvin->setHasNextcloudAccess(true); - $melvin->setHasMobilizon(true); - $melvin->setHasAgoraAccess(true); - - $manager->persist($melvin); - // Création d'un compte de test fixe présent dans le LDAP pour ma présentation $toto = new User(); $toto->setEmail('toto@kazkouil.fr'); diff --git a/templates/home/home.html.twig b/templates/home/home.html.twig index 409cd2b..633a0de 100644 --- a/templates/home/home.html.twig +++ b/templates/home/home.html.twig @@ -20,19 +20,19 @@ TODO : Gérer les données avec Pahéko, mise en service en cours par un des développeurs de l'association. Cela sera vu à posteriori du stage #}

- Votre abonnement actuellement : + Ton abonnement actuellement :

  • Formule souscrite : - {# TODO API : Remplacer par la vraie variable #} - Ajouter la vraie valeur + {# TODO API : Remplacer par la vraie variable quand connexion Pahéko OK #} + Ici s'affichera la donnée récupérée grâce à l'API
  • Date de validité : - {# TODO API : Remplacer par la vraie variable #} - Ajouter la vraie valeur + {# TODO API : Remplacer par la vraie variable quand connexion Pahéko OK #} + Ici s'affichera la donnée récupérée grâce à l'API
diff --git a/templates/user/edit_password.html.twig b/templates/user/edit_password.html.twig index 4deec57..a1426c1 100644 --- a/templates/user/edit_password.html.twig +++ b/templates/user/edit_password.html.twig @@ -6,19 +6,22 @@
-

- Sécurité du compte +

+ Modifier le mot de passe de mon espace kaznaute

{{ form_start(form) }} -
+
{# Champ Ancien Mot de Passe #}
- {{ form_label(form.oldPassword, 'Mot de passe actuel', { + {{ form_label(form.oldPassword, 'Mon mot de passe actuel', { 'label_attr': {'class': 'block text-sm font-semibold text-text'} }) }} {{ form_widget(form.oldPassword, { - 'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'} + 'attr': { + 'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow', + 'placeholder':'Saisissez votre ancien mot de passe' + } }) }} {# Affichage message pour les erreurs de saisie de l'ancien mot de passe #} @@ -28,45 +31,46 @@
{# Champs Nouveau Mot de Passe #} -
- {# Affichage de l'erreur si les deux champs ne correspondent pas #} +
+ {# Affichage d'un message d'erreur si les deux champs ne correspondent pas #}
{{ form_errors(form.newPassword) }}
-
- {{ form_label(form.newPassword.first, 'Nouveau mot de passe', { + +
+ {{ form_label(form.newPassword.first, 'Mon nouveau mot de passe', { 'label_attr': {'class': 'block text-sm font-semibold text-text'} }) }} {{ form_widget(form.newPassword.first, { - 'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton transition-shadow'} + 'attr': { + 'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-all', + 'placeholder':'Saisissez un nouveau mot de passe' + } }) }} - - {# Affichage de l'erreur de longueur (min 8 caractères) #} -
+
{{ form_errors(form.newPassword.first) }}
-
- {{ form_label(form.newPassword.second, 'Confirmer le nouveau mot de passe', { +
+ {{ form_label(form.newPassword.second, 'Confirmer mon nouveau mot de passe', { 'label_attr': {'class': 'block text-sm font-semibold text-text'} }) }} {{ form_widget(form.newPassword.second, { - 'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton transition-shadow'} + 'attr': { + 'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-all', + 'placeholder': 'Confirmez votre saisie' + } }) }}
- -
- {{ form_errors(form.newPassword) }} -
{# Bouton de validation #} -
- +
+
{{ form_end(form) }} diff --git a/translations/security.fr.yaml b/translations/security.fr.yaml new file mode 100644 index 0000000..2b71776 --- /dev/null +++ b/translations/security.fr.yaml @@ -0,0 +1,2 @@ +# translations/security.fr.yaml +"Invalid credentials.": "Identifiants invalides. Veuillez vérifier votre email ou votre mot de passe." -- 2.49.1 From 4f7dc49f134e38479de29f57a91dcd2eb2519384 Mon Sep 17 00:00:00 2001 From: maurine Date: Thu, 9 Apr 2026 19:46:01 +0200 Subject: [PATCH 6/6] feat: refonte du design global (messages flash, navbar, pied-de-page) et simplification du code Twig --- templates/base.html.twig | 79 ++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/templates/base.html.twig b/templates/base.html.twig index 9b74520..5b315f1 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -12,11 +12,11 @@ {% endblock %} {% block javascripts %} - {% block importmap %}{{ importmap('app') }}{% endblock %} + {{ importmap('app') }} {% endblock %} - + {% block navbar %} {{ include('_navbar.html.twig') }} @@ -28,67 +28,58 @@