first commit
This commit is contained in:
66
src/Command/InitCommand.php
Normal file
66
src/Command/InitCommand.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:init',
|
||||
description: 'Initialisation of the app',
|
||||
)]
|
||||
class InitCommand extends Command
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
private ParameterBagInterface $params;
|
||||
private UserPasswordHasherInterface $passwordHasher;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, ParameterBagInterface $params, UserPasswordHasherInterface $passwordHasher)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->params = $params;
|
||||
$this->passwordHasher = $passwordHasher;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('APP:INIT');
|
||||
$io->text('Initialisation of the app');
|
||||
$io->text('');
|
||||
|
||||
$admins = array_map('trim', explode(',', $this->params->get('appAdmin')));
|
||||
foreach ($admins as $admin) {
|
||||
$user = $this->em->getRepository("App\Entity\User")->findOneBy(['username' => $admin]);
|
||||
if (!$user) {
|
||||
$io->text('> Création du compte admin par defaut = '.$admin);
|
||||
$user = new User();
|
||||
|
||||
$hashedPassword = $this->passwordHasher->hashPassword(
|
||||
$user,
|
||||
$this->params->get('appSecret')
|
||||
);
|
||||
|
||||
$user->setUsername($admin);
|
||||
$user->setPassword($hashedPassword);
|
||||
$user->setAvatar('medias/avatar/admin.jpg');
|
||||
$user->setEmail($this->params->get('appNoreply'));
|
||||
$this->em->persist($user);
|
||||
}
|
||||
|
||||
$user->setRoles(['ROLE_ADMIN']);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
0
src/Controller/.gitignore
vendored
Normal file
0
src/Controller/.gitignore
vendored
Normal file
38
src/Controller/HomeController.php
Normal file
38
src/Controller/HomeController.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class HomeController extends AbstractController
|
||||
{
|
||||
#[Route('/', name: 'app_home')]
|
||||
public function home(Request $request): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
if (!$user instanceof User) {
|
||||
throw new AccessDeniedException('Vous n\'avez pas accès à cette ressource.');
|
||||
}
|
||||
$projects = $user->getProjects();
|
||||
|
||||
return $this->render('home/home.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => false,
|
||||
'projects' => $projects,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin', name: 'app_admin')]
|
||||
public function admin(): Response
|
||||
{
|
||||
return $this->render('home/blank.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
]);
|
||||
}
|
||||
}
|
113
src/Controller/ProjectController.php
Normal file
113
src/Controller/ProjectController.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use App\Form\ProjectType;
|
||||
use App\Repository\ProjectRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class ProjectController extends AbstractController
|
||||
{
|
||||
#[Route('/admin/project', name: 'app_admin_project')]
|
||||
public function list(ProjectRepository $projectRepository): Response
|
||||
{
|
||||
$projects = $projectRepository->findAll();
|
||||
|
||||
return $this->render('project/list.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Liste des Projets',
|
||||
'routesubmit' => 'app_admin_project_submit',
|
||||
'routeupdate' => 'app_admin_project_update',
|
||||
'projects' => $projects,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/project/submit', name: 'app_admin_project_submit')]
|
||||
public function submit(Request $request, EntityManagerInterface $em): Response
|
||||
{
|
||||
$project = new Project();
|
||||
$project->addUser($this->getUser());
|
||||
|
||||
$form = $this->createForm(ProjectType::class, $project, ['mode' => 'submit']);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em->persist($project);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('app_admin_project');
|
||||
}
|
||||
|
||||
return $this->render('project/edit.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Création Projet',
|
||||
'routecancel' => 'app_admin_project',
|
||||
'routedelete' => 'app_admin_project_delete',
|
||||
'mode' => 'submit',
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/project/update/{id}', name: 'app_admin_project_update')]
|
||||
public function update(int $id, Request $request, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
|
||||
{
|
||||
$project = $projectRepository->find($id);
|
||||
if (!$project) {
|
||||
throw new NotFoundHttpException('La ressource demandée est introuvable.');
|
||||
}
|
||||
|
||||
$form = $this->createForm(ProjectType::class, $project, ['mode' => 'update']);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('app_admin_project');
|
||||
}
|
||||
|
||||
return $this->render('project/edit.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Modification Projet = '.$project->getTitle(),
|
||||
'routecancel' => 'app_admin_project',
|
||||
'routedelete' => 'app_admin_project_delete',
|
||||
'mode' => 'update',
|
||||
'form' => $form,
|
||||
'project' => $project,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/project/delete/{id}', name: 'app_admin_project_delete')]
|
||||
public function delete(int $id, ProjectRepository $projectRepository, EntityManagerInterface $em): Response
|
||||
{
|
||||
$project = $projectRepository->find($id);
|
||||
if (!$project) {
|
||||
throw new NotFoundHttpException('La ressource demandée est introuvable.');
|
||||
}
|
||||
|
||||
$users = $em->getRepository(User::class)->findBy(['project' => $project]);
|
||||
foreach ($users as $user) {
|
||||
$user->setProject(null);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
// Tentative de suppression
|
||||
try {
|
||||
$em->remove($project);
|
||||
$em->flush();
|
||||
} catch (\Exception $e) {
|
||||
$this->addflash('error', $e->getMessage());
|
||||
|
||||
return $this->redirectToRoute('app_admin_project_update', ['id' => $id]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('app_admin_project');
|
||||
}
|
||||
}
|
32
src/Controller/SecurityController.php
Normal file
32
src/Controller/SecurityController.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/login', name: 'app_login')]
|
||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
// get the login error if there is one
|
||||
$error = $authenticationUtils->getLastAuthenticationError();
|
||||
|
||||
// last username entered by the user
|
||||
$lastUsername = $authenticationUtils->getLastUsername();
|
||||
|
||||
return $this->render('security/login.html.twig', [
|
||||
'last_username' => $lastUsername,
|
||||
'error' => $error,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/logout', name: 'app_logout')]
|
||||
public function logout(): void
|
||||
{
|
||||
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
||||
}
|
||||
}
|
80
src/Controller/UploadController.php
Normal file
80
src/Controller/UploadController.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Service\ImageService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class UploadController extends AbstractController
|
||||
{
|
||||
private ImageService $imageService;
|
||||
|
||||
public function __construct(ImageService $imageService)
|
||||
{
|
||||
$this->imageService = $imageService;
|
||||
}
|
||||
|
||||
#[Route('/user/upload/crop01/{endpoint}', name: 'app_user_upload_crop01')]
|
||||
public function crop01(string $endpoint, Request $request): Response
|
||||
{
|
||||
$reportThumb = $request->get('reportThumb');
|
||||
|
||||
return $this->render('upload\crop01.html.twig', [
|
||||
'useheader' => false,
|
||||
'usemenu' => false,
|
||||
'usesidebar' => false,
|
||||
'endpoint' => $endpoint,
|
||||
'reportThumb' => $reportThumb,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/user/upload/crop02', name: 'app_user_upload_crop02')]
|
||||
public function crop02(Request $request): Response
|
||||
{
|
||||
$reportThumb = $request->get('reportThumb');
|
||||
$path = $request->get('path');
|
||||
$file = $request->get('file');
|
||||
$image = $this->getParameter('kernel.project_dir').'/public/'.$path.'/'.$file;
|
||||
$thumb = $this->getParameter('kernel.project_dir').'/public/'.$path.'/thumb_'.$file;
|
||||
|
||||
// Redimentionner
|
||||
$this->imageService->resizeImage($image, 700, 700);
|
||||
|
||||
// Construction du formulaire
|
||||
$form = $this->createFormBuilder()
|
||||
->add('submit', SubmitType::class, ['label' => 'Valider', 'attr' => ['class' => 'btn btn-success']])
|
||||
->add('x1', HiddenType::class)
|
||||
->add('y1', HiddenType::class)
|
||||
->add('x2', HiddenType::class)
|
||||
->add('y2', HiddenType::class)
|
||||
->add('w', HiddenType::class)
|
||||
->add('h', HiddenType::class)
|
||||
->getForm();
|
||||
|
||||
// Récupération des data du formulaire
|
||||
$form->handleRequest($request);
|
||||
$toReport = false;
|
||||
// Sur validation on généère la miniature croppée
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$data = $form->getData();
|
||||
$toReport = true;
|
||||
$this->imageService->cropImage($image, $thumb, $data['x1'], $data['y1'], $data['w'], $data['h'], 150, 150);
|
||||
}
|
||||
|
||||
return $this->render('upload\crop02.html.twig', [
|
||||
'useheader' => false,
|
||||
'usemenu' => false,
|
||||
'usesidebar' => false,
|
||||
'reportThumb' => $reportThumb,
|
||||
'image' => $path.'/'.$file,
|
||||
'thumb' => $path.'/thumb_'.$file,
|
||||
'form' => $form,
|
||||
'toReport' => $toReport,
|
||||
]);
|
||||
}
|
||||
}
|
191
src/Controller/UserController.php
Normal file
191
src/Controller/UserController.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use App\Form\UserType;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
#[Route('/admin/user', name: 'app_admin_user')]
|
||||
public function list(UserRepository $userRepository): Response
|
||||
{
|
||||
$users = $userRepository->findAll();
|
||||
|
||||
return $this->render('user/list.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Liste des Utilisateurs',
|
||||
'routesubmit' => 'app_admin_user_submit',
|
||||
'routeupdate' => 'app_admin_user_update',
|
||||
'users' => $users,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/user/submit', name: 'app_admin_user_submit')]
|
||||
public function submit(Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
$form = $this->createForm(UserType::class, $user, ['mode' => 'submit', 'modeAuth' => $this->getParameter('modeAuth')]);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$user = $form->getData();
|
||||
$password = $user->getPassword();
|
||||
if ('CAS' === $this->getParameter('modeAuth')) {
|
||||
$password = Uuid::uuid4();
|
||||
}
|
||||
|
||||
$hashedPassword = $passwordHasher->hashPassword(
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
$user->setPassword($hashedPassword);
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
|
||||
return $this->render('user/edit.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Création Utilisateur',
|
||||
'routecancel' => 'app_admin_user',
|
||||
'routedelete' => 'app_admin_user_delete',
|
||||
'mode' => 'submit',
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/user/update/{id}', name: 'app_admin_user_update')]
|
||||
public function update(int $id, Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = $em->getRepository(User::class)->find($id);
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
$hashedPassword = $user->getPassword();
|
||||
|
||||
$form = $this->createForm(UserType::class, $user, ['mode' => 'update', 'modeAuth' => $this->getParameter('modeAuth')]);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$user = $form->getData();
|
||||
if ($user->getPassword()) {
|
||||
$hashedPassword = $passwordHasher->hashPassword(
|
||||
$user,
|
||||
$user->getPassword()
|
||||
);
|
||||
}
|
||||
$user->setPassword($hashedPassword);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
|
||||
return $this->render('user/edit.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => true,
|
||||
'title' => 'Modification Utilisateur = '.$user->getUsername(),
|
||||
'routecancel' => 'app_admin_user',
|
||||
'routedelete' => 'app_admin_user_delete',
|
||||
'mode' => 'update',
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/user/delete/{id}', name: 'app_admin_user_delete')]
|
||||
public function delete(int $id, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = $em->getRepository(User::class)->find($id);
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
|
||||
// Tentative de suppression
|
||||
try {
|
||||
$em->remove($user);
|
||||
$em->flush();
|
||||
} catch (\Exception $e) {
|
||||
$this->addflash('error', $e->getMessage());
|
||||
|
||||
return $this->redirectToRoute('app_admin_user_update', ['id' => $id]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('app_admin_user');
|
||||
}
|
||||
|
||||
#[Route('/user', name: 'app_user_profil')]
|
||||
public function profil(Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $em): Response
|
||||
{
|
||||
$user = $em->getRepository(User::class)->find($this->getUser());
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_home');
|
||||
}
|
||||
$hashedPassword = $user->getPassword();
|
||||
|
||||
$form = $this->createForm(UserType::class, $user, ['mode' => 'profil', 'modeAuth' => $this->getParameter('modeAuth')]);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$user = $form->getData();
|
||||
if ($user->getPassword()) {
|
||||
$hashedPassword = $passwordHasher->hashPassword(
|
||||
$user,
|
||||
$user->getPassword()
|
||||
);
|
||||
}
|
||||
$user->setPassword($hashedPassword);
|
||||
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('app_home');
|
||||
}
|
||||
|
||||
return $this->render('user/edit.html.twig', [
|
||||
'usemenu' => true,
|
||||
'usesidebar' => false,
|
||||
'title' => 'Profil = '.$user->getUsername(),
|
||||
'routecancel' => 'app_home',
|
||||
'routedelete' => '',
|
||||
'mode' => 'profil',
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/user/selectproject', name: 'app_user_selectproject')]
|
||||
public function selectproject(Request $request, EntityManagerInterface $em): JsonResponse
|
||||
{
|
||||
$id = $request->get('id');
|
||||
|
||||
$project = $em->getRepository(Project::class)->find($id);
|
||||
if (!$project) {
|
||||
return new JsonResponse(['status' => 'KO', 'message' => 'ID non fourni'], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $this->getUser();
|
||||
if (!$user instanceof User) {
|
||||
throw new \LogicException('L\'utilisateur actuel n\'est pas une instance de App\Entity\User.');
|
||||
}
|
||||
|
||||
$projects = $user->getProjects();
|
||||
if (!$projects->contains($project)) {
|
||||
return new JsonResponse(['status' => 'KO', 'message' => 'Projet non autorisée'], Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
$user->setProject($project);
|
||||
$em->flush();
|
||||
|
||||
return new JsonResponse(['status' => 'OK', 'message' => 'Projet selectionnée'], Response::HTTP_OK);
|
||||
}
|
||||
}
|
0
src/Entity/.gitignore
vendored
Normal file
0
src/Entity/.gitignore
vendored
Normal file
112
src/Entity/Project.php
Normal file
112
src/Entity/Project.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\ProjectRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: ProjectRepository::class)]
|
||||
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_project_title', fields: ['title'])]
|
||||
class Project
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255, unique: true)]
|
||||
private ?string $title = null;
|
||||
|
||||
#[ORM\Column()]
|
||||
private int $status;
|
||||
|
||||
#[ORM\Column(type: 'datetime', nullable: true)]
|
||||
private ?\DateTimeInterface $updateAt = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'projects')]
|
||||
private Collection $users;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->users = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(int $id): static
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): static
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdateAt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->updateAt;
|
||||
}
|
||||
|
||||
public function setUpdateAt(?\DateTimeInterface $updateAt): static
|
||||
{
|
||||
$this->updateAt = $updateAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): ?int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(int $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, User>
|
||||
*/
|
||||
public function getUsers(): Collection
|
||||
{
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
public function addUser(User $user): static
|
||||
{
|
||||
if (!$this->users->contains($user)) {
|
||||
$this->users->add($user);
|
||||
$user->addProject($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeUser(User $user): static
|
||||
{
|
||||
if ($this->users->removeElement($user)) {
|
||||
$user->removeProject($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
207
src/Entity/User.php
Normal file
207
src/Entity/User.php
Normal file
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])]
|
||||
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
|
||||
#[UniqueEntity(fields: ['email'], message: 'Cet email est déjà utilisé.')]
|
||||
#[UniqueEntity(fields: ['username'], message: 'Ce nom d’utilisateur est déjà pris.')]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 180, unique: true)]
|
||||
private ?string $username = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $apikey = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private array $roles = [];
|
||||
|
||||
#[ORM\Column]
|
||||
private ?string $password = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $avatar = null;
|
||||
|
||||
#[ORM\Column(length: 255, unique: true)]
|
||||
#[Assert\Email(message: 'Veuillez entrer un email valide.')]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\ManyToMany(targetEntity: Project::class, inversedBy: 'users')]
|
||||
private ?Collection $projects;
|
||||
|
||||
#[ORM\ManyToOne()]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?Project $project = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->projects = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function setUsername(string $username): static
|
||||
{
|
||||
$this->username = $username;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A visual identifier that represents this user.
|
||||
*
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return (string) $this->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getRoles(): array
|
||||
{
|
||||
$roles = $this->roles;
|
||||
// guarantee every user at least has ROLE_USER
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
return array_unique($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
public function setRoles(array $roles): static
|
||||
{
|
||||
$this->roles = $roles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PasswordAuthenticatedUserInterface
|
||||
*/
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function setPassword(?string $password): static
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
public function eraseCredentials(): void
|
||||
{
|
||||
// If you store any temporary, sensitive data on the user, clear it here
|
||||
// $this->plainPassword = null;
|
||||
}
|
||||
|
||||
public function getApikey(): ?string
|
||||
{
|
||||
return $this->apikey;
|
||||
}
|
||||
|
||||
public function setApikey(?string $apikey): static
|
||||
{
|
||||
$this->apikey = $apikey;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAvatar(): ?string
|
||||
{
|
||||
return $this->avatar ? $this->avatar : 'medias/avatar/noavatar.png';
|
||||
}
|
||||
|
||||
public function setAvatar(?string $avatar): static
|
||||
{
|
||||
$this->avatar = $avatar;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(string $email): static
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Project>
|
||||
*/
|
||||
public function getProjects(): ?Collection
|
||||
{
|
||||
return $this->projects;
|
||||
}
|
||||
|
||||
public function addProject(Project $project): static
|
||||
{
|
||||
if (!$this->projects->contains($project)) {
|
||||
$this->projects->add($project);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeProject(Project $project): static
|
||||
{
|
||||
$this->projects->removeElement($project);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProject(): ?Project
|
||||
{
|
||||
if (!$this->projects) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
public function setProject(?Project $project): static
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
34
src/EventListener/LogoutListener.php
Normal file
34
src/EventListener/LogoutListener.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\Security\Http\Event\LogoutEvent;
|
||||
|
||||
final class LogoutListener
|
||||
{
|
||||
private ParameterBagInterface $parameterBag;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameterBag)
|
||||
{
|
||||
$this->parameterBag = $parameterBag;
|
||||
}
|
||||
|
||||
#[AsEventListener(event: LogoutEvent::class)]
|
||||
public function onLogoutEvent(LogoutEvent $event): void
|
||||
{
|
||||
if ('CAS' == $this->parameterBag->get('modeAuth')) {
|
||||
$request = $event->getRequest();
|
||||
$host = $request->headers->get('host');
|
||||
$scheme = $request->headers->get('X-Forwarded-Proto') ?? $request->getScheme();
|
||||
$url = $scheme.'://'.$host;
|
||||
\phpCAS::client(CAS_VERSION_2_0, $this->parameterBag->get('casHost'), (int) $this->parameterBag->get('casPort'), $this->parameterBag->get('casPath'), $url, false);
|
||||
\phpCAS::setNoCasServerValidation();
|
||||
|
||||
$url.=$request->getBaseUrl();
|
||||
\phpCAS::logoutWithRedirectService($url);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
41
src/EventListener/UploadListener.php
Normal file
41
src/EventListener/UploadListener.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Oneup\UploaderBundle\Event\PostPersistEvent;
|
||||
use Oneup\UploaderBundle\Event\ValidationEvent;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
final class UploadListener
|
||||
{
|
||||
private string $projectDir;
|
||||
|
||||
public function __construct(KernelInterface $kernel)
|
||||
{
|
||||
// Utiliser le projectDir pour construire le chemin
|
||||
$this->projectDir = $kernel->getProjectDir();
|
||||
}
|
||||
|
||||
#[AsEventListener(event: 'oneup_uploader.validation')]
|
||||
public function onOneupUploaderValidation(ValidationEvent $event): void
|
||||
{
|
||||
// On s'assure que le repertoire de destination existe bien
|
||||
$fs = new Filesystem();
|
||||
$fs->mkdir($this->projectDir.'/public/uploads');
|
||||
$fs->mkdir($this->projectDir.'/public/uploads/'.$event->getType());
|
||||
}
|
||||
|
||||
#[AsEventListener(event: 'oneup_uploader.post_persist')]
|
||||
public function onOneupUploaderPostPersit(PostPersistEvent $event): void
|
||||
{
|
||||
$file = $event->getFile();
|
||||
$type = $event->getType();
|
||||
$filename = $file->getFilename();
|
||||
$response = $event->getResponse();
|
||||
$response['file'] = $filename;
|
||||
$response['path'] = 'uploads/'.$type;
|
||||
$response['filepath'] = 'uploads/'.$type.'/'.$filename;
|
||||
}
|
||||
}
|
46
src/Form/ProjectType.php
Normal file
46
src/Form/ProjectType.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProjectType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('submit', SubmitType::class, [
|
||||
'label' => 'Valider',
|
||||
'attr' => ['class' => 'btn btn-success no-print'],
|
||||
])
|
||||
|
||||
->add('title', TextType::class, [
|
||||
'label' => 'Titre',
|
||||
])
|
||||
|
||||
->add('users', EntityType::class, [
|
||||
'label' => 'Propriétaires',
|
||||
'class' => User::class,
|
||||
'choice_label' => 'username',
|
||||
'multiple' => true,
|
||||
'attr' => ['class' => 'select2'],
|
||||
'required' => false,
|
||||
'by_reference' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Project::class,
|
||||
'mode' => 'submit',
|
||||
]);
|
||||
}
|
||||
}
|
999
src/Form/Type/FontawsomeType.php
Normal file
999
src/Form/Type/FontawsomeType.php
Normal file
@ -0,0 +1,999 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class FontawsomeType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'choices' => $this->getFontAwesomeIcons(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
private function getFontAwesomeIcons(): array
|
||||
{
|
||||
$icons = [
|
||||
'ad',
|
||||
'address-book',
|
||||
'address-card',
|
||||
'adjust',
|
||||
'air-freshener',
|
||||
'align-center',
|
||||
'align-justify',
|
||||
'align-left',
|
||||
'align-right',
|
||||
'allergies',
|
||||
'ambulance',
|
||||
'american-sign-language-interpreting',
|
||||
'anchor',
|
||||
'angle-double-down',
|
||||
'angle-double-left',
|
||||
'angle-double-right',
|
||||
'angle-double-up',
|
||||
'angle-down',
|
||||
'angle-left',
|
||||
'angle-right',
|
||||
'angle-up',
|
||||
'angry',
|
||||
'ankh',
|
||||
'apple-alt',
|
||||
'archive',
|
||||
'archway',
|
||||
'arrow-alt-circle-down',
|
||||
'arrow-alt-circle-left',
|
||||
'arrow-alt-circle-right',
|
||||
'arrow-alt-circle-up',
|
||||
'arrow-circle-down',
|
||||
'arrow-circle-left',
|
||||
'arrow-circle-right',
|
||||
'arrow-circle-up',
|
||||
'arrow-down',
|
||||
'arrow-left',
|
||||
'arrow-right',
|
||||
'arrow-up',
|
||||
'arrows-alt',
|
||||
'arrows-alt-h',
|
||||
'arrows-alt-v',
|
||||
'assistive-listening-systems',
|
||||
'asterisk',
|
||||
'at',
|
||||
'atlas',
|
||||
'atom',
|
||||
'audio-description',
|
||||
'award',
|
||||
'baby',
|
||||
'baby-carriage',
|
||||
'backspace',
|
||||
'backward',
|
||||
'bacon',
|
||||
'bahai',
|
||||
'balance-scale',
|
||||
'balance-scale-left',
|
||||
'balance-scale-right',
|
||||
'ban',
|
||||
'band-aid',
|
||||
'barcode',
|
||||
'bars',
|
||||
'baseball-ball',
|
||||
'basketball-ball',
|
||||
'bath',
|
||||
'battery-empty',
|
||||
'battery-full',
|
||||
'battery-half',
|
||||
'battery-quarter',
|
||||
'battery-three-quarters',
|
||||
'bed',
|
||||
'beer',
|
||||
'bell',
|
||||
'bell-slash',
|
||||
'bezier-curve',
|
||||
'bible',
|
||||
'bicycle',
|
||||
'biking',
|
||||
'binoculars',
|
||||
'biohazard',
|
||||
'birthday-cake',
|
||||
'blender',
|
||||
'blender-phone',
|
||||
'blind',
|
||||
'blog',
|
||||
'bold',
|
||||
'bolt',
|
||||
'bomb',
|
||||
'bone',
|
||||
'bong',
|
||||
'book',
|
||||
'book-dead',
|
||||
'book-medical',
|
||||
'book-open',
|
||||
'book-reader',
|
||||
'bookmark',
|
||||
'border-all',
|
||||
'border-none',
|
||||
'border-style',
|
||||
'bowling-ball',
|
||||
'box',
|
||||
'box-open',
|
||||
'boxes',
|
||||
'braille',
|
||||
'brain',
|
||||
'bread-slice',
|
||||
'briefcase',
|
||||
'briefcase-medical',
|
||||
'broadcast-tower',
|
||||
'broom',
|
||||
'brush',
|
||||
'bug',
|
||||
'building',
|
||||
'bullhorn',
|
||||
'bullseye',
|
||||
'burn',
|
||||
'bus',
|
||||
'bus-alt',
|
||||
'business-time',
|
||||
'calculator',
|
||||
'calendar',
|
||||
'calendar-alt',
|
||||
'calendar-check',
|
||||
'calendar-day',
|
||||
'calendar-minus',
|
||||
'calendar-plus',
|
||||
'calendar-times',
|
||||
'calendar-week',
|
||||
'camera',
|
||||
'camera-retro',
|
||||
'campground',
|
||||
'candy-cane',
|
||||
'cannabis',
|
||||
'capsules',
|
||||
'car',
|
||||
'car-alt',
|
||||
'car-battery',
|
||||
'car-crash',
|
||||
'car-side',
|
||||
'caravan',
|
||||
'caret-down',
|
||||
'caret-left',
|
||||
'caret-right',
|
||||
'caret-square-down',
|
||||
'caret-square-left',
|
||||
'caret-square-right',
|
||||
'caret-square-up',
|
||||
'caret-up',
|
||||
'carrot',
|
||||
'cart-arrow-down',
|
||||
'cart-plus',
|
||||
'cash-register',
|
||||
'cat',
|
||||
'certificate',
|
||||
'chair',
|
||||
'chalkboard',
|
||||
'chalkboard-teacher',
|
||||
'charging-station',
|
||||
'chart-area',
|
||||
'chart-bar',
|
||||
'chart-line',
|
||||
'chart-pie',
|
||||
'check',
|
||||
'check-circle',
|
||||
'check-double',
|
||||
'check-square',
|
||||
'cheese',
|
||||
'chess',
|
||||
'chess-bishop',
|
||||
'chess-board',
|
||||
'chess-king',
|
||||
'chess-knight',
|
||||
'chess-pawn',
|
||||
'chess-queen',
|
||||
'chess-rook',
|
||||
'chevron-circle-down',
|
||||
'chevron-circle-left',
|
||||
'chevron-circle-right',
|
||||
'chevron-circle-up',
|
||||
'chevron-down',
|
||||
'chevron-left',
|
||||
'chevron-right',
|
||||
'chevron-up',
|
||||
'child',
|
||||
'church',
|
||||
'circle',
|
||||
'circle-notch',
|
||||
'city',
|
||||
'clinic-medical',
|
||||
'clipboard',
|
||||
'clipboard-check',
|
||||
'clipboard-list',
|
||||
'clock',
|
||||
'clone',
|
||||
'closed-captioning',
|
||||
'cloud',
|
||||
'cloud-download-alt',
|
||||
'cloud-meatball',
|
||||
'cloud-moon',
|
||||
'cloud-moon-rain',
|
||||
'cloud-rain',
|
||||
'cloud-showers-heavy',
|
||||
'cloud-sun',
|
||||
'cloud-sun-rain',
|
||||
'cloud-upload-alt',
|
||||
'cocktail',
|
||||
'code',
|
||||
'code-branch',
|
||||
'coffee',
|
||||
'cog',
|
||||
'cogs',
|
||||
'coins',
|
||||
'columns',
|
||||
'comment',
|
||||
'comment-alt',
|
||||
'comment-dollar',
|
||||
'comment-dots',
|
||||
'comment-medical',
|
||||
'comment-slash',
|
||||
'comments',
|
||||
'comments-dollar',
|
||||
'compact-disc',
|
||||
'compass',
|
||||
'compress',
|
||||
'compress-alt',
|
||||
'compress-arrows-alt',
|
||||
'concierge-bell',
|
||||
'cookie',
|
||||
'cookie-bite',
|
||||
'copy',
|
||||
'copyright',
|
||||
'couch',
|
||||
'credit-card',
|
||||
'crop',
|
||||
'crop-alt',
|
||||
'cross',
|
||||
'crosshairs',
|
||||
'crow',
|
||||
'crown',
|
||||
'crutch',
|
||||
'cube',
|
||||
'cubes',
|
||||
'cut',
|
||||
'database',
|
||||
'deaf',
|
||||
'democrat',
|
||||
'desktop',
|
||||
'dharmachakra',
|
||||
'diagnoses',
|
||||
'dice',
|
||||
'dice-d20',
|
||||
'dice-d6',
|
||||
'dice-five',
|
||||
'dice-four',
|
||||
'dice-one',
|
||||
'dice-six',
|
||||
'dice-three',
|
||||
'dice-two',
|
||||
'digital-tachograph',
|
||||
'directions',
|
||||
'divide',
|
||||
'dizzy',
|
||||
'dna',
|
||||
'dog',
|
||||
'dollar-sign',
|
||||
'dolly',
|
||||
'dolly-flatbed',
|
||||
'donate',
|
||||
'door-closed',
|
||||
'door-open',
|
||||
'dot-circle',
|
||||
'dove',
|
||||
'download',
|
||||
'drafting-compass',
|
||||
'dragon',
|
||||
'draw-polygon',
|
||||
'drum',
|
||||
'drum-steelpan',
|
||||
'drumstick-bite',
|
||||
'dumbbell',
|
||||
'dumpster',
|
||||
'dumpster-fire',
|
||||
'dungeon',
|
||||
'edit',
|
||||
'egg',
|
||||
'eject',
|
||||
'ellipsis-h',
|
||||
'ellipsis-v',
|
||||
'envelope',
|
||||
'envelope-open',
|
||||
'envelope-open-text',
|
||||
'envelope-square',
|
||||
'equals',
|
||||
'eraser',
|
||||
'ethernet',
|
||||
'euro-sign',
|
||||
'exchange-alt',
|
||||
'exclamation',
|
||||
'exclamation-circle',
|
||||
'exclamation-triangle',
|
||||
'expand',
|
||||
'expand-alt',
|
||||
'expand-arrows-alt',
|
||||
'external-link-alt',
|
||||
'external-link-square-alt',
|
||||
'eye',
|
||||
'eye-dropper',
|
||||
'eye-slash',
|
||||
'fan',
|
||||
'fast-backward',
|
||||
'fast-forward',
|
||||
'fax',
|
||||
'feather',
|
||||
'feather-alt',
|
||||
'female',
|
||||
'fighter-jet',
|
||||
'file',
|
||||
'file-alt',
|
||||
'file-archive',
|
||||
'file-audio',
|
||||
'file-code',
|
||||
'file-contract',
|
||||
'file-csv',
|
||||
'file-download',
|
||||
'file-excel',
|
||||
'file-export',
|
||||
'file-image',
|
||||
'file-import',
|
||||
'file-invoice',
|
||||
'file-invoice-dollar',
|
||||
'file-medical',
|
||||
'file-medical-alt',
|
||||
'file-pdf',
|
||||
'file-powerpoint',
|
||||
'file-prescription',
|
||||
'file-signature',
|
||||
'file-upload',
|
||||
'file-video',
|
||||
'file-word',
|
||||
'fill',
|
||||
'fill-drip',
|
||||
'film',
|
||||
'filter',
|
||||
'fingerprint',
|
||||
'fire',
|
||||
'fire-alt',
|
||||
'fire-extinguisher',
|
||||
'first-aid',
|
||||
'fish',
|
||||
'fist-raised',
|
||||
'flag',
|
||||
'flag-checkered',
|
||||
'flag-usa',
|
||||
'flask',
|
||||
'flushed',
|
||||
'folder',
|
||||
'folder-minus',
|
||||
'folder-open',
|
||||
'folder-plus',
|
||||
'font',
|
||||
'football-ball',
|
||||
'forward',
|
||||
'frog',
|
||||
'frown',
|
||||
'frown-open',
|
||||
'funnel-dollar',
|
||||
'futbol',
|
||||
'gamepad',
|
||||
'gas-pump',
|
||||
'gavel',
|
||||
'gem',
|
||||
'genderless',
|
||||
'ghost',
|
||||
'gift',
|
||||
'gifts',
|
||||
'glass-cheers',
|
||||
'glass-martini',
|
||||
'glass-martini-alt',
|
||||
'glass-whiskey',
|
||||
'glasses',
|
||||
'globe',
|
||||
'globe-africa',
|
||||
'globe-americas',
|
||||
'globe-asia',
|
||||
'globe-europe',
|
||||
'golf-ball',
|
||||
'gopuram',
|
||||
'graduation-cap',
|
||||
'greater-than',
|
||||
'greater-than-equal',
|
||||
'grimace',
|
||||
'grin',
|
||||
'grin-alt',
|
||||
'grin-beam',
|
||||
'grin-beam-sweat',
|
||||
'grin-hearts',
|
||||
'grin-squint',
|
||||
'grin-squint-tears',
|
||||
'grin-stars',
|
||||
'grin-tears',
|
||||
'grin-tongue',
|
||||
'grin-tongue-squint',
|
||||
'grin-tongue-wink',
|
||||
'grin-wink',
|
||||
'grip-horizontal',
|
||||
'grip-lines',
|
||||
'grip-lines-vertical',
|
||||
'grip-vertical',
|
||||
'guitar',
|
||||
'h-square',
|
||||
'hamburger',
|
||||
'hammer',
|
||||
'hamsa',
|
||||
'hand-holding',
|
||||
'hand-holding-heart',
|
||||
'hand-holding-usd',
|
||||
'hand-lizard',
|
||||
'hand-middle-finger',
|
||||
'hand-paper',
|
||||
'hand-peace',
|
||||
'hand-point-down',
|
||||
'hand-point-left',
|
||||
'hand-point-right',
|
||||
'hand-point-up',
|
||||
'hand-pointer',
|
||||
'hand-rock',
|
||||
'hand-scissors',
|
||||
'hand-spock',
|
||||
'hands',
|
||||
'hands-helping',
|
||||
'handshake',
|
||||
'hanukiah',
|
||||
'hard-hat',
|
||||
'hashtag',
|
||||
'hat-cowboy',
|
||||
'hat-cowboy-side',
|
||||
'hat-wizard',
|
||||
'hdd',
|
||||
'heading',
|
||||
'headphones',
|
||||
'headphones-alt',
|
||||
'headset',
|
||||
'heart',
|
||||
'heart-broken',
|
||||
'heartbeat',
|
||||
'helicopter',
|
||||
'highlighter',
|
||||
'hiking',
|
||||
'hippo',
|
||||
'history',
|
||||
'hockey-puck',
|
||||
'holly-berry',
|
||||
'home',
|
||||
'horse',
|
||||
'horse-head',
|
||||
'hospital',
|
||||
'hospital-alt',
|
||||
'hospital-symbol',
|
||||
'hot-tub',
|
||||
'hotdog',
|
||||
'hotel',
|
||||
'hourglass',
|
||||
'hourglass-end',
|
||||
'hourglass-half',
|
||||
'hourglass-start',
|
||||
'house-damage',
|
||||
'hryvnia',
|
||||
'i-cursor',
|
||||
'ice-cream',
|
||||
'icicles',
|
||||
'icons',
|
||||
'id-badge',
|
||||
'id-card',
|
||||
'id-card-alt',
|
||||
'igloo',
|
||||
'image',
|
||||
'images',
|
||||
'inbox',
|
||||
'indent',
|
||||
'industry',
|
||||
'infinity',
|
||||
'info',
|
||||
'info-circle',
|
||||
'italic',
|
||||
'jedi',
|
||||
'joint',
|
||||
'journal-whills',
|
||||
'kaaba',
|
||||
'key',
|
||||
'keyboard',
|
||||
'khanda',
|
||||
'kiss',
|
||||
'kiss-beam',
|
||||
'kiss-wink-heart',
|
||||
'kiwi-bird',
|
||||
'landmark',
|
||||
'language',
|
||||
'laptop',
|
||||
'laptop-code',
|
||||
'laptop-medical',
|
||||
'laugh',
|
||||
'laugh-beam',
|
||||
'laugh-squint',
|
||||
'laugh-wink',
|
||||
'layer-group',
|
||||
'leaf',
|
||||
'lemon',
|
||||
'less-than',
|
||||
'less-than-equal',
|
||||
'level-down-alt',
|
||||
'level-up-alt',
|
||||
'life-ring',
|
||||
'lightbulb',
|
||||
'link',
|
||||
'lira-sign',
|
||||
'list',
|
||||
'list-alt',
|
||||
'list-ol',
|
||||
'list-ul',
|
||||
'location-arrow',
|
||||
'lock',
|
||||
'lock-open',
|
||||
'long-arrow-alt-down',
|
||||
'long-arrow-alt-left',
|
||||
'long-arrow-alt-right',
|
||||
'long-arrow-alt-up',
|
||||
'low-vision',
|
||||
'luggage-cart',
|
||||
'magic',
|
||||
'magnet',
|
||||
'mail-bulk',
|
||||
'male',
|
||||
'map',
|
||||
'map-marked',
|
||||
'map-marked-alt',
|
||||
'map-marker',
|
||||
'map-marker-alt',
|
||||
'map-pin',
|
||||
'map-signs',
|
||||
'marker',
|
||||
'mars',
|
||||
'mars-double',
|
||||
'mars-stroke',
|
||||
'mars-stroke-h',
|
||||
'mars-stroke-v',
|
||||
'mask',
|
||||
'medal',
|
||||
'medkit',
|
||||
'meh',
|
||||
'meh-blank',
|
||||
'meh-rolling-eyes',
|
||||
'memory',
|
||||
'menorah',
|
||||
'mercury',
|
||||
'meteor',
|
||||
'microchip',
|
||||
'microphone',
|
||||
'microphone-alt',
|
||||
'microphone-alt-slash',
|
||||
'microphone-slash',
|
||||
'microscope',
|
||||
'minus',
|
||||
'minus-circle',
|
||||
'minus-square',
|
||||
'mitten',
|
||||
'mobile',
|
||||
'mobile-alt',
|
||||
'money-bill',
|
||||
'money-bill-alt',
|
||||
'money-bill-wave',
|
||||
'money-bill-wave-alt',
|
||||
'money-check',
|
||||
'money-check-alt',
|
||||
'monument',
|
||||
'moon',
|
||||
'mortar-pestle',
|
||||
'mosque',
|
||||
'motorcycle',
|
||||
'mountain',
|
||||
'mouse',
|
||||
'mouse-pointer',
|
||||
'mug-hot',
|
||||
'music',
|
||||
'network-wired',
|
||||
'neuter',
|
||||
'newspaper',
|
||||
'not-equal',
|
||||
'notes-medical',
|
||||
'object-group',
|
||||
'object-ungroup',
|
||||
'oil-can',
|
||||
'om',
|
||||
'otter',
|
||||
'outdent',
|
||||
'pager',
|
||||
'paint-brush',
|
||||
'paint-roller',
|
||||
'palette',
|
||||
'pallet',
|
||||
'paper-plane',
|
||||
'paperclip',
|
||||
'parachute-box',
|
||||
'paragraph',
|
||||
'parking',
|
||||
'passport',
|
||||
'pastafarianism',
|
||||
'paste',
|
||||
'pause',
|
||||
'pause-circle',
|
||||
'paw',
|
||||
'peace',
|
||||
'pen',
|
||||
'pen-alt',
|
||||
'pen-fancy',
|
||||
'pen-nib',
|
||||
'pen-square',
|
||||
'pencil-alt',
|
||||
'pencil-ruler',
|
||||
'people-carry',
|
||||
'pepper-hot',
|
||||
'percent',
|
||||
'percentage',
|
||||
'person-booth',
|
||||
'phone',
|
||||
'phone-alt',
|
||||
'phone-slash',
|
||||
'phone-square',
|
||||
'phone-square-alt',
|
||||
'phone-volume',
|
||||
'photo-video',
|
||||
'piggy-bank',
|
||||
'pills',
|
||||
'pizza-slice',
|
||||
'place-of-worship',
|
||||
'plane',
|
||||
'plane-arrival',
|
||||
'plane-departure',
|
||||
'play',
|
||||
'play-circle',
|
||||
'plug',
|
||||
'plus',
|
||||
'plus-circle',
|
||||
'plus-square',
|
||||
'podcast',
|
||||
'poll',
|
||||
'poll-h',
|
||||
'poo',
|
||||
'poo-storm',
|
||||
'poop',
|
||||
'portrait',
|
||||
'pound-sign',
|
||||
'power-off',
|
||||
'pray',
|
||||
'praying-hands',
|
||||
'prescription',
|
||||
'prescription-bottle',
|
||||
'prescription-bottle-alt',
|
||||
'print',
|
||||
'procedures',
|
||||
'project-diagram',
|
||||
'puzzle-piece',
|
||||
'qrcode',
|
||||
'question',
|
||||
'question-circle',
|
||||
'quidditch',
|
||||
'quote-left',
|
||||
'quote-right',
|
||||
'quran',
|
||||
'radiation',
|
||||
'radiation-alt',
|
||||
'rainbow',
|
||||
'random',
|
||||
'receipt',
|
||||
'record-vinyl',
|
||||
'recycle',
|
||||
'redo',
|
||||
'redo-alt',
|
||||
'registered',
|
||||
'remove-format',
|
||||
'reply',
|
||||
'reply-all',
|
||||
'republican',
|
||||
'restroom',
|
||||
'retweet',
|
||||
'ribbon',
|
||||
'ring',
|
||||
'road',
|
||||
'robot',
|
||||
'rocket',
|
||||
'route',
|
||||
'rss',
|
||||
'rss-square',
|
||||
'ruble-sign',
|
||||
'ruler',
|
||||
'ruler-combined',
|
||||
'ruler-horizontal',
|
||||
'ruler-vertical',
|
||||
'running',
|
||||
'rupee-sign',
|
||||
'sad-cry',
|
||||
'sad-tear',
|
||||
'satellite',
|
||||
'satellite-dish',
|
||||
'save',
|
||||
'school',
|
||||
'screwdriver',
|
||||
'scroll',
|
||||
'sd-card',
|
||||
'search',
|
||||
'search-dollar',
|
||||
'search-location',
|
||||
'search-minus',
|
||||
'search-plus',
|
||||
'seedling',
|
||||
'server',
|
||||
'shapes',
|
||||
'share',
|
||||
'share-alt',
|
||||
'share-alt-square',
|
||||
'share-square',
|
||||
'shekel-sign',
|
||||
'shield-alt',
|
||||
'ship',
|
||||
'shipping-fast',
|
||||
'shoe-prints',
|
||||
'shopping-bag',
|
||||
'shopping-basket',
|
||||
'shopping-cart',
|
||||
'shower',
|
||||
'shuttle-van',
|
||||
'sign',
|
||||
'sign-in-alt',
|
||||
'sign-language',
|
||||
'sign-out-alt',
|
||||
'signal',
|
||||
'signature',
|
||||
'sim-card',
|
||||
'sitemap',
|
||||
'skating',
|
||||
'skiing',
|
||||
'skiing-nordic',
|
||||
'skull',
|
||||
'skull-crossbones',
|
||||
'slash',
|
||||
'sleigh',
|
||||
'sliders-h',
|
||||
'smile',
|
||||
'smile-beam',
|
||||
'smile-wink',
|
||||
'smog',
|
||||
'smoking',
|
||||
'smoking-ban',
|
||||
'sms',
|
||||
'snowboarding',
|
||||
'snowflake',
|
||||
'snowman',
|
||||
'snowplow',
|
||||
'socks',
|
||||
'solar-panel',
|
||||
'sort',
|
||||
'sort-alpha-down',
|
||||
'sort-alpha-down-alt',
|
||||
'sort-alpha-up',
|
||||
'sort-alpha-up-alt',
|
||||
'sort-amount-down',
|
||||
'sort-amount-down-alt',
|
||||
'sort-amount-up',
|
||||
'sort-amount-up-alt',
|
||||
'sort-down',
|
||||
'sort-numeric-down',
|
||||
'sort-numeric-down-alt',
|
||||
'sort-numeric-up',
|
||||
'sort-numeric-up-alt',
|
||||
'sort-up',
|
||||
'spa',
|
||||
'space-shuttle',
|
||||
'spell-check',
|
||||
'spider',
|
||||
'spinner',
|
||||
'splotch',
|
||||
'spray-can',
|
||||
'square',
|
||||
'square-full',
|
||||
'square-root-alt',
|
||||
'stamp',
|
||||
'star',
|
||||
'star-and-crescent',
|
||||
'star-half',
|
||||
'star-half-alt',
|
||||
'star-of-david',
|
||||
'star-of-life',
|
||||
'step-backward',
|
||||
'step-forward',
|
||||
'stethoscope',
|
||||
'sticky-note',
|
||||
'stop',
|
||||
'stop-circle',
|
||||
'stopwatch',
|
||||
'store',
|
||||
'store-alt',
|
||||
'stream',
|
||||
'street-view',
|
||||
'strikethrough',
|
||||
'stroopwafel',
|
||||
'subscript',
|
||||
'subway',
|
||||
'suitcase',
|
||||
'suitcase-rolling',
|
||||
'sun',
|
||||
'superscript',
|
||||
'surprise',
|
||||
'swatchbook',
|
||||
'swimmer',
|
||||
'swimming-pool',
|
||||
'synagogue',
|
||||
'sync',
|
||||
'sync-alt',
|
||||
'syringe',
|
||||
'table',
|
||||
'table-tennis',
|
||||
'tablet',
|
||||
'tablet-alt',
|
||||
'tablets',
|
||||
'tachometer-alt',
|
||||
'tag',
|
||||
'tags',
|
||||
'tape',
|
||||
'tasks',
|
||||
'taxi',
|
||||
'teeth',
|
||||
'teeth-open',
|
||||
'temperature-high',
|
||||
'temperature-low',
|
||||
'tenge',
|
||||
'terminal',
|
||||
'text-height',
|
||||
'text-width',
|
||||
'th',
|
||||
'th-large',
|
||||
'th-list',
|
||||
'theater-masks',
|
||||
'thermometer',
|
||||
'thermometer-empty',
|
||||
'thermometer-full',
|
||||
'thermometer-half',
|
||||
'thermometer-quarter',
|
||||
'thermometer-three-quarters',
|
||||
'thumbs-down',
|
||||
'thumbs-up',
|
||||
'thumbtack',
|
||||
'ticket-alt',
|
||||
'times',
|
||||
'times-circle',
|
||||
'tint',
|
||||
'tint-slash',
|
||||
'tired',
|
||||
'toggle-off',
|
||||
'toggle-on',
|
||||
'toilet',
|
||||
'toilet-paper',
|
||||
'toolbox',
|
||||
'tools',
|
||||
'tooth',
|
||||
'torah',
|
||||
'torii-gate',
|
||||
'tractor',
|
||||
'trademark',
|
||||
'traffic-light',
|
||||
'trailer',
|
||||
'train',
|
||||
'tram',
|
||||
'transgender',
|
||||
'transgender-alt',
|
||||
'trash',
|
||||
'trash-alt',
|
||||
'trash-restore',
|
||||
'trash-restore-alt',
|
||||
'tree',
|
||||
'trophy',
|
||||
'truck',
|
||||
'truck-loading',
|
||||
'truck-monster',
|
||||
'truck-moving',
|
||||
'truck-pickup',
|
||||
'tshirt',
|
||||
'tty',
|
||||
'tv',
|
||||
'umbrella',
|
||||
'umbrella-beach',
|
||||
'underline',
|
||||
'undo',
|
||||
'undo-alt',
|
||||
'universal-access',
|
||||
'university',
|
||||
'unlink',
|
||||
'unlock',
|
||||
'unlock-alt',
|
||||
'upload',
|
||||
'user',
|
||||
'user-alt',
|
||||
'user-alt-slash',
|
||||
'user-astronaut',
|
||||
'user-check',
|
||||
'user-circle',
|
||||
'user-clock',
|
||||
'user-cog',
|
||||
'user-edit',
|
||||
'user-friends',
|
||||
'user-graduate',
|
||||
'user-injured',
|
||||
'user-lock',
|
||||
'user-md',
|
||||
'user-minus',
|
||||
'user-ninja',
|
||||
'user-nurse',
|
||||
'user-plus',
|
||||
'user-secret',
|
||||
'user-shield',
|
||||
'user-slash',
|
||||
'user-tag',
|
||||
'user-tie',
|
||||
'user-times',
|
||||
'users',
|
||||
'users-cog',
|
||||
'utensil-spoon',
|
||||
'utensils',
|
||||
'vector-square',
|
||||
'venus',
|
||||
'venus-double',
|
||||
'venus-mars',
|
||||
'vial',
|
||||
'vials',
|
||||
'video',
|
||||
'video-slash',
|
||||
'vihara',
|
||||
'voicemail',
|
||||
'volleyball-ball',
|
||||
'volume-down',
|
||||
'volume-mute',
|
||||
'volume-off',
|
||||
'volume-up',
|
||||
'vote-yea',
|
||||
'vr-cardboard',
|
||||
'walking',
|
||||
'wallet',
|
||||
'warehouse',
|
||||
'water',
|
||||
'wave-square',
|
||||
'weight',
|
||||
'weight-hanging',
|
||||
'wheelchair',
|
||||
'wifi',
|
||||
'wind',
|
||||
'window-close',
|
||||
'window-maximize',
|
||||
'window-minimize',
|
||||
'window-restore',
|
||||
'wine-bottle',
|
||||
'wine-glass',
|
||||
'wine-glass-alt',
|
||||
'won-sign',
|
||||
'wrench',
|
||||
'x-ray',
|
||||
'yen-sign',
|
||||
'yin-yang', ];
|
||||
|
||||
$tbicons = [];
|
||||
foreach ($icons as $value) {
|
||||
$tbicons[$value] = 'fas fa-'.$value;
|
||||
}
|
||||
|
||||
// Liste d'exemples d'icônes Font Awesome
|
||||
return $tbicons;
|
||||
}
|
||||
}
|
88
src/Form/UserType.php
Normal file
88
src/Form/UserType.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Regex;
|
||||
|
||||
class UserType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('submit', SubmitType::class, [
|
||||
'label' => 'Valider',
|
||||
'attr' => ['class' => 'btn btn-success no-print'],
|
||||
])
|
||||
|
||||
->add('username', TextType::class, [
|
||||
'label' => 'Login',
|
||||
])
|
||||
|
||||
->add('apikey', TextType::class, [
|
||||
'label' => 'apikey',
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
->add('avatar', HiddenType::class)
|
||||
|
||||
->add('email', EmailType::class, [
|
||||
'label' => 'Email',
|
||||
]);
|
||||
|
||||
if ('profil' != $options['mode']) {
|
||||
$builder
|
||||
->add('roles', ChoiceType::class, [
|
||||
'choices' => ['ROLE_ADMIN' => 'ROLE_ADMIN', 'ROLE_MASTER' => 'ROLE_MASTER', 'ROLE_USER' => 'ROLE_USER'],
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
])
|
||||
|
||||
->add('projects', EntityType::class, [
|
||||
'label' => 'Projets',
|
||||
'class' => Project::class,
|
||||
'choice_label' => 'title',
|
||||
'multiple' => true,
|
||||
'attr' => ['class' => 'select2'],
|
||||
]);
|
||||
}
|
||||
|
||||
if ('SQL' === $options['modeAuth']) {
|
||||
$builder
|
||||
->add('password', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'required' => ('submit' == $options['mode'] ? true : false),
|
||||
'options' => ['always_empty' => true],
|
||||
'first_options' => ['label' => 'Mot de Passe', 'attr' => ['class' => 'form-control', 'style' => 'margin-bottom:15px', 'autocomplete' => 'new-password']],
|
||||
'second_options' => ['label' => 'Confirmer Mot de Passe', 'attr' => ['class' => 'form-control', 'style' => 'margin-bottom:15px']],
|
||||
'constraints' => [
|
||||
new Regex([
|
||||
'pattern' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\W).{8,}$/',
|
||||
'message' => 'Le mot de passe doit contenir au moins 8 caractères, une lettre majuscule, une lettre minuscule et un caractère spécial.',
|
||||
]),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
'mode' => 'submit',
|
||||
'modeAuth' => 'SQL',
|
||||
]);
|
||||
}
|
||||
}
|
11
src/Kernel.php
Normal file
11
src/Kernel.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||
|
||||
class Kernel extends BaseKernel
|
||||
{
|
||||
use MicroKernelTrait;
|
||||
}
|
0
src/Repository/.gitignore
vendored
Normal file
0
src/Repository/.gitignore
vendored
Normal file
18
src/Repository/ProjectRepository.php
Normal file
18
src/Repository/ProjectRepository.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Project;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Project>
|
||||
*/
|
||||
class ProjectRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Project::class);
|
||||
}
|
||||
}
|
35
src/Repository/UserRepository.php
Normal file
35
src/Repository/UserRepository.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<User>
|
||||
*/
|
||||
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to upgrade (rehash) the user's password automatically over time.
|
||||
*/
|
||||
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
|
||||
}
|
||||
|
||||
$user->setPassword($newHashedPassword);
|
||||
$this->getEntityManager()->persist($user);
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
87
src/Security/CasUserProvider.php
Normal file
87
src/Security/CasUserProvider.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
class CasUserProvider implements UserProviderInterface
|
||||
{
|
||||
private UserRepository $userRepository;
|
||||
private EntityManagerInterface $em;
|
||||
private ParameterBagInterface $parameterBag;
|
||||
|
||||
public function __construct(UserRepository $userRepository, EntityManagerInterface $em, ParameterBagInterface $parameterBag)
|
||||
{
|
||||
$this->userRepository = $userRepository;
|
||||
$this->em = $em;
|
||||
$this->parameterBag = $parameterBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge un utilisateur par son identifiant CAS.
|
||||
*/
|
||||
public function loadUserByIdentifier(string $identifier): UserInterface
|
||||
{
|
||||
// Récupérer l'utilisateur depuis la base de données
|
||||
$user = $this->userRepository->findOneBy(['username' => $identifier]);
|
||||
|
||||
if (!$user) {
|
||||
throw new UserNotFoundException(sprintf('User with username "%s" not found.', $identifier));
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge un utilisateur par son identifiant CAS et autosubmit autoupdate en fonction des attributs CAS
|
||||
*/
|
||||
public function loadUserByIdentifierAndAttributes(string $identifier, array $attributes): UserInterface
|
||||
{
|
||||
// Charger l'utilisateur existant depuis la base de données
|
||||
$user = $this->userRepository->findOneBy(['username' => $identifier]);
|
||||
|
||||
if (!$user) {
|
||||
// Créer un nouvel utilisateur avec les attributs CAS
|
||||
$user = new User();
|
||||
$user->setUsername($identifier);
|
||||
$user->setPassword(Uuid::uuid4()->toString());
|
||||
$user->setRoles(['ROLE_USER']);
|
||||
|
||||
// Persister l'utilisateur en base si nécessaire
|
||||
$this->em->persist($user);
|
||||
}
|
||||
|
||||
$user->setEmail($attributes[$this->parameterBag->get('casMail')] ?? null);
|
||||
$this->em->flush();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de recharger un utilisateur déjà authentifié (si nécessaire).
|
||||
*/
|
||||
public function refreshUser(UserInterface $user): UserInterface
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
throw new \InvalidArgumentException(sprintf('Instances of "%s" are not supported.', get_class($user)));
|
||||
}
|
||||
|
||||
// Recharge l'utilisateur en base si nécessaire (ex. : rôles mis à jour)
|
||||
return $this->userRepository->find($user->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indique si ce provider supporte un type d'utilisateur donné.
|
||||
*/
|
||||
public function supportsClass(string $class): bool
|
||||
{
|
||||
return User::class === $class;
|
||||
}
|
||||
}
|
130
src/Security/DynamicAuthenticator.php
Normal file
130
src/Security/DynamicAuthenticator.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Repository\UserRepository;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
|
||||
class DynamicAuthenticator extends AbstractAuthenticator
|
||||
{
|
||||
private string $modeAuth;
|
||||
private UserRepository $userRepository;
|
||||
private CasUserProvider $casUserProvider;
|
||||
private ParameterBagInterface $parameterBag;
|
||||
|
||||
public function __construct(string $modeAuth, UserRepository $userRepository, CasUserProvider $casUserProvider, ParameterBagInterface $parameterBag)
|
||||
{
|
||||
$this->modeAuth = $modeAuth;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->casUserProvider = $casUserProvider;
|
||||
$this->parameterBag = $parameterBag;
|
||||
}
|
||||
|
||||
public function supports(Request $request): ?bool
|
||||
{
|
||||
// Vérifie si l'utilisateur est déjà connecté
|
||||
if ($request->getSession()->get('_security_main')) {
|
||||
return false; // L'utilisateur est déjà authentifié
|
||||
}
|
||||
|
||||
// Exclure les routes de login et logout pour éviter les boucles
|
||||
$currentPath = $request->getPathInfo();
|
||||
if (in_array($currentPath, ['/login', '/logout'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function authenticate(Request $request): Passport
|
||||
{
|
||||
switch ($this->modeAuth) {
|
||||
case 'SQL':
|
||||
return $this->authenticateWithSql($request);
|
||||
|
||||
case 'CAS':
|
||||
return $this->authenticateWithCas($request);
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid authentication method');
|
||||
}
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
private function authenticateWithSql(Request $request): Passport
|
||||
{
|
||||
$username = $request->request->get('_username', '');
|
||||
$password = $request->request->get('_password', '');
|
||||
|
||||
if (!$username || !$password) {
|
||||
throw new AuthenticationException('Username and password are required.');
|
||||
}
|
||||
|
||||
// Charger l'utilisateur via Doctrine
|
||||
$user = $this->userRepository->findOneBy(['username' => $username]);
|
||||
|
||||
if (!$user) {
|
||||
throw new AuthenticationException('User not found.');
|
||||
}
|
||||
|
||||
return new Passport(
|
||||
new UserBadge($username, function ($userIdentifier) {
|
||||
return $this->userRepository->findOneBy(['username' => $userIdentifier]);
|
||||
}),
|
||||
new PasswordCredentials($password)
|
||||
);
|
||||
}
|
||||
|
||||
private function authenticateWithCas(Request $request): Passport
|
||||
{
|
||||
// Récupérer l'hôte d'origine derrière le reverse proxy
|
||||
$host = $request->headers->get('host');
|
||||
$scheme = $request->headers->get('X-Forwarded-Proto') ?? $request->getScheme();
|
||||
|
||||
// Construire l'URL
|
||||
$url = $scheme.'://'.$host;
|
||||
|
||||
// \phpCAS::setDebug('/tmp/logcas.log');
|
||||
\phpCAS::client(
|
||||
CAS_VERSION_2_0,
|
||||
$this->parameterBag->get('casHost'),
|
||||
(int) $this->parameterBag->get('casPort'),
|
||||
$this->parameterBag->get('casPath'),
|
||||
$url,
|
||||
false);
|
||||
|
||||
\phpCAS::setNoCasServerValidation();
|
||||
\phpCAS::forceAuthentication();
|
||||
|
||||
$username = \phpCAS::getUser();
|
||||
$attributes = \phpCAS::getAttributes();
|
||||
|
||||
if (!$username) {
|
||||
throw new AuthenticationException('CAS authentication failed.');
|
||||
}
|
||||
|
||||
$userBadge = new UserBadge($username, function ($userIdentifier) use ($attributes) {
|
||||
return $this->casUserProvider->loadUserByIdentifierAndAttributes($userIdentifier, $attributes);
|
||||
});
|
||||
|
||||
return new SelfValidatingPassport($userBadge);
|
||||
}
|
||||
}
|
139
src/Service/ImageService.php
Normal file
139
src/Service/ImageService.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
class ImageService
|
||||
{
|
||||
// Hauteur d'une image
|
||||
public function getHeight(string $image): int
|
||||
{
|
||||
$size = getimagesize($image);
|
||||
$height = $size[1];
|
||||
|
||||
return $height;
|
||||
}
|
||||
|
||||
// Largeur d'une imgage
|
||||
public function getWidth(string $image): int
|
||||
{
|
||||
$size = getimagesize($image);
|
||||
$width = $size[0];
|
||||
|
||||
return $width;
|
||||
}
|
||||
|
||||
public function resizeImage(string $image, int $maxWidth, int $maxHeight): void
|
||||
{
|
||||
list($width, $height, $imageType) = getimagesize($image);
|
||||
$imageType = image_type_to_mime_type($imageType);
|
||||
|
||||
// Définir le pourcentage de réduction de l'image
|
||||
$scale = $maxHeight / $height;
|
||||
if (($width * $scale) > $maxWidth) {
|
||||
$scale = $maxWidth / $width;
|
||||
}
|
||||
|
||||
// Création de l'image redimentionnée
|
||||
$newImageWidth = (int) ceil($width * $scale);
|
||||
$newImageHeight = (int) ceil($height * $scale);
|
||||
$newImage = imagecreatetruecolor($newImageWidth, $newImageHeight);
|
||||
|
||||
switch ($imageType) {
|
||||
case 'image/gif':
|
||||
$source = imagecreatefromgif($image);
|
||||
break;
|
||||
case 'image/pjpeg':
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
$source = imagecreatefromjpeg($image);
|
||||
break;
|
||||
case 'image/png':
|
||||
case 'image/x-png':
|
||||
$source = imagecreatefrompng($image);
|
||||
break;
|
||||
case 'image/webp':
|
||||
$source = imagecreatefromwebp($image);
|
||||
break;
|
||||
default:
|
||||
$source= imagecreatefromgif($image);
|
||||
break;
|
||||
}
|
||||
|
||||
imagecopyresampled($newImage, $source, 0, 0, 0, 0, $newImageWidth, $newImageHeight, $width, $height);
|
||||
|
||||
switch ($imageType) {
|
||||
case 'image/gif':
|
||||
imagegif($newImage, $image);
|
||||
break;
|
||||
case 'image/pjpeg':
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
imagejpeg($newImage, $image, 90);
|
||||
break;
|
||||
case 'image/png':
|
||||
case 'image/x-png':
|
||||
imagepng($newImage, $image);
|
||||
break;
|
||||
case 'image/webp':
|
||||
imagewebp($newImage, $image, 90);
|
||||
}
|
||||
}
|
||||
|
||||
public function cropImage(string $image, string $thumb, int $x, int $y, int $cropWidth, int $cropHeight, int $maxWidth, int $maxHeight): void
|
||||
{
|
||||
list($width, $height, $imageType) = getimagesize($image);
|
||||
$imageType = image_type_to_mime_type($imageType);
|
||||
|
||||
// Définir le pourcentage de réduction de l'image
|
||||
$scale = $maxHeight / $cropHeight;
|
||||
if (($cropWidth * $scale) > $maxWidth) {
|
||||
$scale = $maxWidth / $cropWidth;
|
||||
}
|
||||
|
||||
// Création de l'image redimentionnée
|
||||
$newImageWidth = (int) ceil($cropWidth * $scale);
|
||||
$newImageHeight = (int) ceil($cropHeight * $scale);
|
||||
|
||||
$newImage = imagecreatetruecolor($newImageWidth, $newImageHeight);
|
||||
|
||||
switch ($imageType) {
|
||||
case 'image/gif':
|
||||
$source = imagecreatefromgif($image);
|
||||
break;
|
||||
case 'image/pjpeg':
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
$source = imagecreatefromjpeg($image);
|
||||
break;
|
||||
case 'image/png':
|
||||
case 'image/x-png':
|
||||
$source = imagecreatefrompng($image);
|
||||
break;
|
||||
case 'image/webp':
|
||||
$source = imagecreatefromwebp($image);
|
||||
break;
|
||||
default:
|
||||
$source= imagecreatefromgif($image);
|
||||
break;
|
||||
}
|
||||
|
||||
imagecopyresampled($newImage, $source, 0, 0, $x, $y, $newImageWidth, $newImageHeight, $cropWidth, $cropHeight);
|
||||
|
||||
switch ($imageType) {
|
||||
case 'image/gif':
|
||||
imagegif($newImage, $thumb);
|
||||
break;
|
||||
case 'image/pjpeg':
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
imagejpeg($newImage, $thumb, 90);
|
||||
break;
|
||||
case 'image/png':
|
||||
case 'image/x-png':
|
||||
imagepng($newImage, $thumb);
|
||||
break;
|
||||
case 'image/webp':
|
||||
imagewebp($newImage, $thumb, 90);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user