Merge branch 'Olivier'

This commit is contained in:
Olivier PARPAILLON
2024-11-20 10:53:39 +01:00
10 changed files with 259 additions and 30 deletions

View File

@@ -20,5 +20,9 @@ services:
- '../src/Entity/' - '../src/Entity/'
- '../src/Kernel.php' - '../src/Kernel.php'
App\Service\FileUploader:
arguments:
$targetDirectory: '../public/upload/image/profile/'
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones # please note that last definitions always *replace* previous ones

View File

@@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
/** /**
* Auto-generated Migration: Please modify to your needs! * Auto-generated Migration: Please modify to your needs!
*/ */
final class Version20241119145530 extends AbstractMigration final class Version20241120092127 extends AbstractMigration
{ {
public function getDescription(): string public function getDescription(): string
{ {
@@ -20,12 +20,12 @@ final class Version20241119145530 extends AbstractMigration
public function up(Schema $schema): void public function up(Schema $schema): void
{ {
// this up() migration is auto-generated, please modify it to your needs // this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_PSEUDO ON participant (pseudo)'); $this->addSql('ALTER TABLE participant ADD file_name VARCHAR(255) DEFAULT NULL');
} }
public function down(Schema $schema): void public function down(Schema $schema): void
{ {
// this down() migration is auto-generated, please modify it to your needs // this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX UNIQ_IDENTIFIER_PSEUDO ON participant'); $this->addSql('ALTER TABLE participant DROP file_name');
} }
} }

View File

@@ -2,6 +2,8 @@
namespace App\Controller; namespace App\Controller;
use App\Entity\Participant;
use App\Service\FileUploader;
use App\Form\RegistrationFormType; use App\Form\RegistrationFormType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -11,26 +13,57 @@ use App\Repository\ParticipantRepository;
class ProfileController extends AbstractController class ProfileController extends AbstractController
{ {
private FileUploader $fileUploader;
private ParticipantRepository $profileRepo;
public function __construct(FileUploader $fileUploader, ParticipantRepository $profileRepo) {
$this->fileUploader = $fileUploader;
$this->profileRepo = $profileRepo;
}
#[Route('/profile/{uuid}', name: 'profile_view', methods: ['GET'])] #[Route('/profile/{uuid}', name: 'profile_view', methods: ['GET'])]
public function viewProfile(string $uuid, ParticipantRepository $participantRepository): Response public function viewProfile(string $uuid, ParticipantRepository $profileRepo): Response
{ {
$currentProfile = $participantRepository->findOneBy(['idParticipant' => $uuid]); $currentProfile = $profileRepo->findOneBy(['idParticipant' => $uuid]);
return $this->render('profile/view.html.twig', [ return $this->render('profile/view.html.twig', [
'profile' => $currentProfile, 'profile' => $currentProfile,
]); ]);
} }
#[Route('/profile/edit/{uuid}', name: 'profile_edit', methods: ['GET', 'POST'])] #[Route('/profile/edit/{uuid}', name: 'profile_edit', methods: ['GET', 'POST'])]
public function editProfile(string $uuid, ParticipantRepository $participantRepository, Request $request): Response public function editProfile(string $uuid, Request $request): Response
{ {
$currentProfile = $participantRepository->findOneBy(['idParticipant' => $uuid]); try {
$form = $this->createForm(RegistrationFormType::class, $currentProfile); $profile = $this->profileRepo->findOneBy(['idParticipant' => $uuid]);
$form->handleRequest($request); if (!$profile === $this->getUser()) {
if ($form->isSubmitted() && $form->isValid()) { $this->addFlash('error', "Vous ne pouvez pas modifier un profil qui n'est pas le votre");
return $this->redirectToRoute('profile_view',['uuid' => $profile->getIdParticipant()]);
}
$form = $this->createForm(RegistrationFormType::class, $profile);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$imageFile = $form->get('image')->getData();
if (($form->has('deleteImage') && $form['deleteImage']->getData()) || $imageFile) {
$this->fileUploader->delete($profile->getFileName(), '/upload/image/profile');
if ($imageFile) {
$imageFilename = $this->fileUploader->upload($imageFile);
$profile->setFileName($imageFilename);
} else {
$profile->setFileName('');
}
}
$profileToUpdate = $this->profileRepo->update($profile);
if (!$profileToUpdate) {
throw $this->createNotFoundException('No profile found');
}
$this->addFlash('success', 'Votre profile est bien à jour');
return $this->redirectToRoute('profile_view',['uuid' => $profile->getIdParticipant()]);
}
return $this->render('profile/edit.html.twig', [
'formProfile' => $form,
]);
} catch(\Exception $e) {
$formProfile = $this->createForm(RegistrationFormType::class, $profile);
$this->addFlash('error', $e->getMessage());
return $this->render('profile/edit.html.twig', ['formProfile' => $formProfile]);
} }
return $this->render('profile/view.html.twig', [
'profile' => $currentProfile,
]);
} }
} }

View File

@@ -0,0 +1,52 @@
<?php
namespace App\DataFixtures;
use App\Entity\Participant;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserFixtures extends Fixture
{
public function load(ObjectManager $manager, UserPasswordHasherInterface $userPasswordHasher): void
{
$olivier = new Participant();
$olivier->setPrenom('Olivier');
$olivier->setNom('Parpaillon');
$olivier->setPseudo('Parpaillax');
$olivier->setTelephone('0675794302');
$olivier->setEmail('olivier@gmail.com');
$olivier->setRoles(['ROLE_USER', 'ROLE_ADMIN']);
$olivier->setAdministrateur(true);
$olivier->setActif(false);
$olivier->setPassword($userPasswordHasher->hashPassword($olivier, 'test-44'));
$manager->persist($olivier);
$johan = new Participant();
$johan->setPrenom('Johan');
$johan->setNom('Leroy');
$johan->setPseudo('Jojo');
$johan->setTelephone('0785421565');
$johan->setEmail('johan@gmail.com');
$johan->setRoles(['ROLE_USER', 'ROLE_ADMIN']);
$johan->setAdministrateur(true);
$johan->setActif(false);
$johan->setPassword($userPasswordHasher->hashPassword($johan, 'test-44'));
$manager->persist($johan);
$marvin = new Participant();
$marvin->setPrenom('Marvin');
$marvin->setNom('Epiphana');
$marvin->setPseudo('Marv1');
$marvin->setTelephone('0645258535');
$marvin->setEmail('marvin@gmail.com');
$marvin->setRoles(['ROLE_USER', 'ROLE_ADMIN']);
$marvin->setAdministrateur(true);
$marvin->setActif(false);
$marvin->setPassword($userPasswordHasher->hashPassword($marvin, 'test-44'));
$manager->persist($marvin);
$manager->flush();
}
}

View File

@@ -47,6 +47,9 @@ class Participant implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column] #[ORM\Column]
private ?string $password = null; private ?string $password = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $fileName = null;
#[ORM\ManyToOne(targetEntity: Site::class, inversedBy: 'participants')] #[ORM\ManyToOne(targetEntity: Site::class, inversedBy: 'participants')]
#[ORM\JoinColumn(name: 'site_id', referencedColumnName: 'id_site', nullable: true)] #[ORM\JoinColumn(name: 'site_id', referencedColumnName: 'id_site', nullable: true)]
private ?Site $site = null; private ?Site $site = null;
@@ -249,4 +252,14 @@ class Participant implements UserInterface, PasswordAuthenticatedUserInterface
return $this; return $this;
} }
public function getFileName(): ?string
{
return $this->fileName;
}
public function setFileName(?string $fileName): void
{
$this->fileName = $fileName;
}
} }

View File

@@ -4,31 +4,31 @@ namespace App\Form;
use App\Entity\Participant; use App\Entity\Participant;
use Symfony\Component\Form\AbstractType; 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\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\File;
class RegistrationFormType extends AbstractType class RegistrationFormType extends AbstractType
{ {
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
$builder $builder
->add('email', EmailType::class, [ ->add('nom', TextType::class, [
'label' => 'Email', 'label' => 'Nom',
'label_attr' => ['class' => 'text-gray-700 font-bold'], 'label_attr' => ['class' => 'text-gray-700 font-bold'],
'attr' => [ 'attr' => [
'class' => 'w-full mb-4 px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500', 'class' => 'w-full mb-4 px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500',
'placeholder' => 'Adresse e-mail', 'placeholder' => 'Nom',
],
'constraints' => [
new NotBlank([
'message' => 'Please enter an email address',
]),
], ],
]) ])
->add('prenom', TextType::class, [ ->add('prenom', TextType::class, [
@@ -47,15 +47,20 @@ class RegistrationFormType extends AbstractType
'placeholder' => 'Pseudo', 'placeholder' => 'Pseudo',
], ],
]) ])
->add('nom', TextType::class, [ ->add('email', EmailType::class, [
'label' => 'Nom', 'label' => 'Email',
'label_attr' => ['class' => 'text-gray-700 font-bold'], 'label_attr' => ['class' => 'text-gray-700 font-bold'],
'attr' => [ 'attr' => [
'class' => 'w-full mb-4 px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500', 'class' => 'w-full mb-4 px-4 py-2 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500',
'placeholder' => 'Nom', 'placeholder' => 'Adresse e-mail',
],
'constraints' => [
new NotBlank([
'message' => 'Please enter an email address',
]),
], ],
]) ])
->add('telephone', IntegerType::class, [ ->add('telephone', TextType::class, [
'label' => 'Numéro de téléphone', 'label' => 'Numéro de téléphone',
'label_attr' => ['class' => 'text-gray-700 font-bold'], 'label_attr' => ['class' => 'text-gray-700 font-bold'],
'attr' => [ 'attr' => [
@@ -85,6 +90,39 @@ class RegistrationFormType extends AbstractType
]), ]),
], ],
]) ])
->add('image', FileType::class, [
'label' => 'Image',
'mapped' => false,
'required' => false,
'attr' => [
'class' => 'w-full mb-4 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500',
],
'label_attr' => ['class' => 'text-gray-700 font-bold'],
'constraints' => [
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'image/png',
'image/jpeg',
],
'mimeTypesMessage' => 'Please upload a valid image',
])
],
])
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$profile = $event->getData();
if ($profile && $profile->getFileName()) {
$form = $event->getForm();
$form->add('deleteImage', CheckboxType::class, [
'required' => false,
'mapped' => false,
'label' => 'Supprimer l\'image',
'attr' => [
'class' => 'w-4 h-4 mb-4 border-gray-300 rounded mx-2',
], 'label_attr' => ['class' => 'text-gray-700 font-bold px-4']
]);
}
})
; ;
} }

View File

@@ -5,15 +5,32 @@ namespace App\Repository;
use App\Entity\Participant; use App\Entity\Participant;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
/** /**
* @extends ServiceEntityRepository<Participant> * @extends ServiceEntityRepository<Participant>
*/ */
class ParticipantRepository extends ServiceEntityRepository class ParticipantRepository extends ServiceEntityRepository
{ {
public function __construct(ManagerRegistry $registry) private UserPasswordHasherInterface $userPasswordHasher;
public function __construct(ManagerRegistry $registry, UserPasswordHasherInterface $userPasswordHasher)
{ {
parent::__construct($registry, Participant::class); parent::__construct($registry, Participant::class);
$this->userPasswordHasher = $userPasswordHasher;
}
public function update(Participant $profile): ?Participant
{
$newProfile = $this->findOneBy(['idParticipant' => $profile->getIdParticipant()]);
$newProfile->setPrenom($profile->getPrenom());
$newProfile->setNom($profile->getNom());
$newProfile->setEmail($profile->getEmail());
$newProfile->setTelephone($profile->getTelephone());
$newProfile->setPseudo($profile->getPseudo());
$newProfile->setFileName($profile->getFileName());
$newProfile->setPassword($this->userPasswordHasher->hashPassword($newProfile, $profile->getPassword()));
$this->getEntityManager()->flush();
return $newProfile;
} }
// /** // /**

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\String\Slugger\SluggerInterface;
class FileUploader
{
public function __construct(
private string $targetDirectory,
private SluggerInterface $slugger,
) {
}
public function upload(UploadedFile $file): string
{
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$safeFilename = $this->slugger->slug($originalFilename);
$fileName = $safeFilename.'-'.uniqid().'.'.$file->guessExtension();
try {
$file->move($this->getTargetDirectory(), $fileName);
} catch (FileException $e) {
// ... handle exception if something happens during file upload
}
return $fileName;
}
public function getTargetDirectory(): string
{
return $this->targetDirectory;
}
public function delete(?string $filename, string $rep): void
{
if (null != $filename) {
if (file_exists($rep . '/' . $filename)) {
unlink($rep . '/' . $filename);
}
}
}
}

View File

@@ -0,0 +1,24 @@
{% extends 'main/base.html.twig' %}
{% block head %}
<head>
<meta charset="UTF-8">
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
</head>
{% endblock %}
{% block content %}
<div class="flex flex-row justify-center items-stretch py-24">
<div class="bg-white shadow-lg p-8 max-w-sm text-center flex-1">
<h2 class="text-2xl font-bold text-center pb-6">Modifier votre profile</h2>
{{ form_start(formProfile) }}
{{ form_widget(formProfile) }}
{% if formProfile.vars.data.imageFilename != null %}
<img src="{{ asset('/upload/image/profile/' ~ formProfile.vars.value.imageFilename) }}" height="128" width="128">
{% endif %}
<button class="btnRegister text-white font-bold py-2 px-4 border-b-4 rounded mx-auto" type="submit">Actualiser les informations</button>
{{ form_end(formProfile) }}
</div>
</div>
{% endblock %}

View File

@@ -13,13 +13,16 @@
<div class="px-6 bg-white shadow-lg rounded-lg py-2"> <div class="px-6 bg-white shadow-lg rounded-lg py-2">
<div class="flex flex-wrap justify-center"> <div class="flex flex-wrap justify-center">
<div class="w-full flex justify-center"> <div class="w-full flex justify-center">
{# <div class="relative">#} <div class="relative">
{# <img src="{{ profile.imageFilename ? asset('upload/image/profile/' ~ profile.imageFilename) : "" }}" class="shadow-xl rounded-full align-middle border-none absolute -m-16 -ml-20 lg:-ml-16 max-w-[150px]"/>#} <img src="{{ profile.fileName ? asset('upload/image/profile/' ~ profile.fileName) : "" }}" class="shadow-xl rounded-full align-middle border-none absolute -m-16 -ml-20 lg:-ml-16 max-w-[150px]"/>
{# </div>#} </div>
</div> </div>
</div> </div>
<div class="text-center mt-2"> <div class="text-center mt-2">
<h3 class="text-2xl text-slate-700 font-bold leading-normal mb-1">{{ profile.prenom }} {{ profile.nom }}</h3> <h3 class="text-2xl text-slate-700 font-bold leading-normal mb-1">{{ profile.prenom }} {{ profile.nom }}</h3>
<div class="text-sm mt-0 mb-2 text-slate-400 font-bold uppercase">
<i class="fas fa-map-marker-alt mr-2 text-slate-400 opacity-75"></i>{{ profile.pseudo }}
</div>
<div class="text-xs mt-0 mb-2 text-slate-400 font-bold uppercase"> <div class="text-xs mt-0 mb-2 text-slate-400 font-bold uppercase">
<i class="fas fa-map-marker-alt mr-2 text-slate-400 opacity-75"></i>{{ profile.telephone }} - {{ profile.email }} <i class="fas fa-map-marker-alt mr-2 text-slate-400 opacity-75"></i>{{ profile.telephone }} - {{ profile.email }}
</div> </div>